IND$FILE Browse!
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 36s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 47s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 1m27s

This commit is contained in:
2026-09-24 01:28:55 +00:00
parent 3d3a939ece
commit 8286af19e6
13 changed files with 664 additions and 94 deletions
@@ -78,7 +78,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
if (client != null) {
statusBar.updateStatus();
syncModeMenuItems();
if (!isAnyChildDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
if (!isModalDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
if (!terminalPanel.hasFocus()) {
terminalPanel.requestFocusInWindow();
}
@@ -87,6 +87,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
});
refreshTimer.start();
setAutoRequestFocus(true);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
@@ -104,8 +106,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
@Override
public void windowActivated(WindowEvent e) {
if (!isAnyChildDialogActive()) {
terminalPanel.requestFocusInWindow();
if (!isModalDialogActive() && terminalPanel != null) {
terminalPanel.requestFocus();
}
}
});
addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
if (!isModalDialogActive() && terminalPanel != null) {
terminalPanel.requestFocus();
}
}
});
@@ -117,16 +128,20 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
Object src = me.getSource();
if (src instanceof Component) {
Window win = SwingUtilities.getWindowAncestor((Component) src);
if (win == J3270App.this && !isAnyChildDialogActive()) {
if (win == J3270App.this && !isModalDialogActive()) {
if (!J3270App.this.isFocused() || !J3270App.this.isActive()) {
J3270App.this.toFront();
J3270App.this.requestFocus();
}
boolean isMenu = (src instanceof JMenuBar) || (src instanceof JMenuItem) || (src instanceof JPopupMenu)
|| SwingUtilities.getAncestorOfClass(JMenuBar.class, (Component) src) != null
|| SwingUtilities.getAncestorOfClass(JPopupMenu.class, (Component) src) != null;
if (!isMenu && terminalPanel != null && !terminalPanel.hasFocus()) {
terminalPanel.requestFocusInWindow();
terminalPanel.requestFocus();
SwingUtilities.invokeLater(() -> {
if (terminalPanel != null && !terminalPanel.hasFocus()) {
terminalPanel.requestFocus();
}
});
}
}
}
@@ -134,19 +149,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
}, AWTEvent.MOUSE_EVENT_MASK);
}
private boolean isAnyChildDialogActive() {
Window focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (focusedWindow != null && focusedWindow != this) {
return true;
}
if (findDialog != null && findDialog.isVisible()) return true;
if (scriptDialog != null && scriptDialog.isVisible()) return true;
if (macroDialog != null && macroDialog.isVisible()) return true;
if (fieldInspectorDialog != null && fieldInspectorDialog.isVisible()) return true;
if (printerSessionDialog != null && printerSessionDialog.isVisible()) return true;
if (screenHistoryDialog != null && screenHistoryDialog.isVisible()) return true;
private boolean isModalDialogActive() {
for (Window owned : getOwnedWindows()) {
if (owned != null && owned.isVisible()) {
if (owned instanceof Dialog && owned.isVisible() && ((Dialog) owned).isModal()) {
return true;
}
}
@@ -28,6 +28,8 @@ public class FileTransferDialog extends JDialog {
public String lrecl = "";
public String blksize = "";
public String space = "";
public FTConfig.AllocationUnit units = FTConfig.AllocationUnit.TRACKS;
public String dirBlocks = "";
public String options = "";
}
@@ -82,6 +84,8 @@ public class FileTransferDialog extends JDialog {
private JTextField lreclField;
private JTextField blksizeField;
private JTextField spaceField;
private JComboBox<FTConfig.AllocationUnit> unitsCombo;
private JTextField dirField;
// VM specific
private JTextField optionsField;
@@ -220,7 +224,7 @@ public class FileTransferDialog extends JDialog {
formPanel.add(createRow("File Options:", optionsPanel2));
// Host-specific options panel (TSO dataset allocation)
JPanel hostOptsPanel = new JPanel(new GridLayout(2, 4, 5, 5));
JPanel hostOptsPanel = new JPanel(new GridLayout(3, 4, 5, 5));
hostOptsPanel.setOpaque(false);
hostOptsPanel.setBorder(ThemeManager.createTitledBorder("TSO Allocation Options (Send Only)"));
@@ -236,10 +240,24 @@ public class FileTransferDialog extends JDialog {
blksizeField = new JTextField(5);
hostOptsPanel.add(blksizeField);
hostOptsPanel.add(new JLabel("SPACE:"));
hostOptsPanel.add(new JLabel("Units:"));
unitsCombo = new JComboBox<>(new FTConfig.AllocationUnit[]{
FTConfig.AllocationUnit.TRACKS,
FTConfig.AllocationUnit.CYLINDERS,
FTConfig.AllocationUnit.AVBLOCK,
FTConfig.AllocationUnit.DEFAULT
});
unitsCombo.setSelectedItem(FTConfig.AllocationUnit.TRACKS);
hostOptsPanel.add(unitsCombo);
hostOptsPanel.add(new JLabel("SPACE (Pri,Sec):"));
spaceField = new JTextField(15);
hostOptsPanel.add(spaceField);
hostOptsPanel.add(new JLabel("Directory (PDS):"));
dirField = new JTextField(5);
hostOptsPanel.add(dirField);
formPanel.add(Box.createRigidArea(new Dimension(0, 10)));
formPanel.add(hostOptsPanel);
@@ -306,7 +324,9 @@ public class FileTransferDialog extends JDialog {
if (lastTransferState.recfm != null) recfmField.setText(lastTransferState.recfm);
if (lastTransferState.lrecl != null) lreclField.setText(lastTransferState.lrecl);
if (lastTransferState.blksize != null) blksizeField.setText(lastTransferState.blksize);
if (lastTransferState.units != null) unitsCombo.setSelectedItem(lastTransferState.units);
if (lastTransferState.space != null) spaceField.setText(lastTransferState.space);
if (lastTransferState.dirBlocks != null) dirField.setText(lastTransferState.dirBlocks);
if (lastTransferState.options != null) optionsField.setText(lastTransferState.options);
}
}
@@ -327,7 +347,9 @@ public class FileTransferDialog extends JDialog {
recfmField.setEnabled(isSend && isTso);
lreclField.setEnabled(isSend && isTso);
blksizeField.setEnabled(isSend && isTso);
unitsCombo.setEnabled(isSend && isTso);
spaceField.setEnabled(isSend && isTso);
dirField.setEnabled(isSend && isTso);
optionsField.setEnabled(isCms);
}
@@ -410,7 +432,10 @@ public class FileTransferDialog extends JDialog {
config.setRecfm(recfmField.getText().trim());
config.setLrecl(lreclField.getText().trim());
config.setBlksize(blksizeField.getText().trim());
FTConfig.AllocationUnit selectedUnits = (FTConfig.AllocationUnit) unitsCombo.getSelectedItem();
config.setUnits(selectedUnits != null ? selectedUnits : FTConfig.AllocationUnit.DEFAULT);
config.setSpace(spaceField.getText().trim());
config.setDirectoryBlocks(dirField.getText().trim());
config.setOptions(optionsField.getText().trim());
recordSessionTransfer();
@@ -439,7 +464,9 @@ public class FileTransferDialog extends JDialog {
state.recfm = recfmField.getText().trim();
state.lrecl = lreclField.getText().trim();
state.blksize = blksizeField.getText().trim();
state.units = (FTConfig.AllocationUnit) unitsCombo.getSelectedItem();
state.space = spaceField.getText().trim();
state.dirBlocks = dirField.getText().trim();
state.options = optionsField.getText().trim();
lastTransferState = state;
}
@@ -459,7 +486,9 @@ public class FileTransferDialog extends JDialog {
public JTextField getRecfmField() { return recfmField; }
public JTextField getLreclField() { return lreclField; }
public JTextField getBlksizeField() { return blksizeField; }
public JComboBox<FTConfig.AllocationUnit> getUnitsCombo() { return unitsCombo; }
public JTextField getSpaceField() { return spaceField; }
public JTextField getDirField() { return dirField; }
public JTextField getOptionsField() { return optionsField; }
private void applyTheme(Container container) {
@@ -3,15 +3,19 @@ package haus.nightmare.j3270.ui;
import haus.nightmare.lib3270j.Telnet3270Client;
import haus.nightmare.lib3270j.ft.FTConfig;
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryEntry;
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryParser;
import haus.nightmare.lib3270j.ft.dir.HostDirectoryEntry;
import haus.nightmare.lib3270j.ft.dir.TSODirectoryEntry;
import haus.nightmare.lib3270j.ft.dir.TSODirectoryParser;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
@@ -19,6 +23,7 @@ import java.util.List;
/**
* Host Directory and Dataset Catalog browser dialog for TSO and VM/CMS environments.
* Aligned with IBM Host On-Demand presentation space queries and clipboard parsing.
*/
public class HostDirectoryDialog extends JDialog {
@@ -27,6 +32,7 @@ public class HostDirectoryDialog extends JDialog {
private JComboBox<FTConfig.HostType> hostTypeCombo;
private JTextField queryField;
private JButton queryHostBtn;
private JTable table;
private DefaultTableModel tableModel;
private JLabel statusLabel;
@@ -34,6 +40,7 @@ public class HostDirectoryDialog extends JDialog {
private String selectedHostFile = null;
private boolean confirmed = false;
private final List<HostDirectoryEntry> allEntries = new ArrayList<>();
private final List<HostDirectoryEntry> currentEntries = new ArrayList<>();
public HostDirectoryDialog(Dialog parent, Telnet3270Client client, FTConfig.HostType hostType, String initialQuery) {
@@ -43,7 +50,7 @@ public class HostDirectoryDialog extends JDialog {
AppIcon.applyTo(this);
buildUI(initialQuery);
setSize(700, 420);
setSize(780, 460);
setLocationRelativeTo(parent);
}
@@ -69,10 +76,18 @@ public class HostDirectoryDialog extends JDialog {
if (initialHostType == FTConfig.HostType.CMS) hostTypeCombo.setSelectedItem(FTConfig.HostType.CMS);
else hostTypeCombo.setSelectedItem(FTConfig.HostType.TSO);
ThemeManager.styleComboBox(hostTypeCombo);
hostTypeCombo.addActionListener(e -> {
setupTableColumns();
if (client != null && client.getConnectionState().isFullSession()) {
queryHost();
} else {
parseCurrentScreen();
}
});
topPanel.add(hostTypeCombo, gbc);
gbc.gridx = 2;
JLabel queryLabel = new JLabel("Query Pattern / Text:");
JLabel queryLabel = new JLabel("Filter / Pattern:");
queryLabel.setFont(font);
topPanel.add(queryLabel, gbc);
@@ -81,15 +96,24 @@ public class HostDirectoryDialog extends JDialog {
queryField = new JTextField(initialQuery != null ? initialQuery : "", 16);
queryField.setFont(ThemeManager.getMonospacedUiFont());
ThemeManager.styleTextField(queryField);
queryField.addActionListener(e -> onFilterOrParse());
topPanel.add(queryField, gbc);
gbc.gridx = 4;
gbc.weightx = 0;
JButton parseBtn = new JButton("Query / Parse");
ThemeManager.styleButton(parseBtn, ThemeManager.ButtonVariant.DEFAULT);
parseBtn.setFont(font);
parseBtn.addActionListener(e -> runQuery());
topPanel.add(parseBtn, gbc);
JButton filterBtn = new JButton("Filter / Parse");
ThemeManager.styleButton(filterBtn, ThemeManager.ButtonVariant.DEFAULT);
filterBtn.setFont(font);
filterBtn.addActionListener(e -> onFilterOrParse());
topPanel.add(filterBtn, gbc);
gbc.gridx = 5;
queryHostBtn = new JButton("Query Host");
ThemeManager.styleButton(queryHostBtn, ThemeManager.ButtonVariant.DEFAULT);
queryHostBtn.setFont(font);
queryHostBtn.setToolTipText("Send LISTCAT or LISTFILE query to active host session");
queryHostBtn.addActionListener(e -> queryHost());
topPanel.add(queryHostBtn, gbc);
mainPanel.add(topPanel, BorderLayout.NORTH);
@@ -129,11 +153,18 @@ public class HostDirectoryDialog extends JDialog {
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
btnPanel.setOpaque(false);
JButton pasteScreenBtn = new JButton("Parse Current Screen");
JButton pasteScreenBtn = new JButton("Parse Screen");
ThemeManager.styleButton(pasteScreenBtn, ThemeManager.ButtonVariant.DEFAULT);
pasteScreenBtn.setFont(font);
pasteScreenBtn.setToolTipText("Parse items currently visible on the 3270 presentation space");
pasteScreenBtn.addActionListener(e -> parseCurrentScreen());
JButton pasteClipBtn = new JButton("Paste Clipboard");
ThemeManager.styleButton(pasteClipBtn, ThemeManager.ButtonVariant.DEFAULT);
pasteClipBtn.setFont(font);
pasteClipBtn.setToolTipText("Parse host filenames copied to the system clipboard (HoD compatible)");
pasteClipBtn.addActionListener(e -> parseClipboard());
JButton selectBtn = new JButton("Select Dataset");
ThemeManager.styleButton(selectBtn, ThemeManager.ButtonVariant.PRIMARY);
selectBtn.setFont(font);
@@ -148,6 +179,7 @@ public class HostDirectoryDialog extends JDialog {
});
btnPanel.add(pasteScreenBtn);
btnPanel.add(pasteClipBtn);
btnPanel.add(Box.createHorizontalStrut(10));
btnPanel.add(selectBtn);
btnPanel.add(cancelBtn);
@@ -158,12 +190,18 @@ public class HostDirectoryDialog extends JDialog {
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
// Initial setup
// Initial setup: columns and screen parse or host query
setupTableColumns();
if (initialQuery != null && !initialQuery.isEmpty()) {
runQuery();
if (initialQuery != null && !initialQuery.trim().isEmpty()) {
queryField.setText(initialQuery.trim());
}
if (client != null && client.getConnectionState().isFullSession()) {
queryHost();
} else {
parseCurrentScreen();
if (initialQuery != null && !initialQuery.trim().isEmpty()) {
applyFilter();
}
}
}
@@ -181,69 +219,241 @@ public class HostDirectoryDialog extends JDialog {
}
}
private void runQuery() {
setupTableColumns();
currentEntries.clear();
private void onFilterOrParse() {
String query = queryField.getText().trim();
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
// If text contains multiple lines, treat it as pasted raw text to parse
if (query.contains("\n") || query.contains("\r") || query.toUpperCase().contains("NONVSAM")
|| query.toUpperCase().contains("IN-CAT") || query.toUpperCase().contains("FILENAME")) {
parseText(query, "query text");
} else if (allEntries.isEmpty() && client != null && client.getConnectionState().isFullSession()) {
queryHost();
} else {
applyFilter();
}
}
if (client != null && client.getXfer() != null) {
if (hostType == FTConfig.HostType.CMS) {
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(query);
currentEntries.addAll(cmsEntries);
for (CMSDirectoryEntry entry : cmsEntries) {
tableModel.addRow(new Object[]{
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
});
}
} else {
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(query);
currentEntries.addAll(tsoEntries);
for (TSODirectoryEntry entry : tsoEntries) {
tableModel.addRow(new Object[]{
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
});
}
}
private void queryHost() {
if (client == null || !client.getConnectionState().isFullSession()) {
statusLabel.setText("No active session to query.");
return;
}
statusLabel.setText(currentEntries.size() + " entries found.");
String pattern = queryField.getText().trim();
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
statusLabel.setText("Querying host (" + hostType + ")...");
if (queryHostBtn != null) queryHostBtn.setEnabled(false);
SwingWorker<String, Void> worker = new SwingWorker<>() {
@Override
protected String doInBackground() throws Exception {
if (client.getPS() == null) return "";
// 1. Clear screen and wait for host to unlock keyboard
client.getPS().SendKeys("[clear]");
waitForKeyboardUnlock(client, 3000);
// 2. Format query command
if (hostType == FTConfig.HostType.CMS) {
String cmsFilter;
if (pattern.isEmpty()) {
cmsFilter = "* * *";
} else {
String clean = pattern.replace("'", "").replace("\"", "").trim();
if (clean.contains(".")) {
int dot = clean.indexOf('.');
cmsFilter = clean.substring(0, dot).trim() + " " + clean.substring(dot + 1).trim() + " *";
} else if (clean.split("\\s+").length == 1) {
cmsFilter = clean + " * *";
} else {
cmsFilter = clean;
}
}
client.getPS().SendKeys("LISTFILE " + cmsFilter + " ( nohead alloc date[enter]");
} else {
if (pattern.isEmpty()) {
client.getPS().SendKeys("LISTCAT[enter]");
} else {
String dsnPattern = pattern.trim();
if (dsnPattern.startsWith("'") && dsnPattern.endsWith("'") && dsnPattern.length() > 2) {
dsnPattern = dsnPattern.substring(1, dsnPattern.length() - 1);
}
int paren = dsnPattern.indexOf('(');
if (paren > 0) {
dsnPattern = dsnPattern.substring(0, paren);
}
client.getPS().SendKeys("LISTCAT LEVEL(" + dsnPattern + ")[enter]");
}
}
// 3. Wait for host to process command and unlock keyboard
Thread.sleep(100);
waitForKeyboardUnlock(client, 5000);
// 4. Collect pages, handling MORE... on CMS or *** on TSO
StringBuilder allPages = new StringBuilder();
int maxPages = 20;
for (int page = 0; page < maxPages; page++) {
int rows = client.getPS().getRows();
int cols = client.getPS().getCols();
String screen = client.getPS().copyString(0, 0, rows - 1, cols - 1);
allPages.append(screen).append("\n");
boolean hasMore = false;
if (hostType == FTConfig.HostType.CMS) {
if (screen.contains("MORE...") || screen.contains("HOLDING")) {
hasMore = true;
client.getPS().SendKeys("[enter]");
}
} else {
if (screen.contains("***")) {
hasMore = true;
client.getPS().SendKeys("[enter]");
}
}
if (hasMore) {
Thread.sleep(100);
waitForKeyboardUnlock(client, 3000);
} else {
break;
}
}
return allPages.toString();
}
@Override
protected void done() {
if (queryHostBtn != null) queryHostBtn.setEnabled(true);
try {
String screenText = get();
parseText(screenText, "host query");
if (!pattern.isEmpty() && !allEntries.isEmpty()) {
applyFilter();
}
} catch (Exception ex) {
statusLabel.setText("Host query failed: " + ex.getMessage());
}
}
};
worker.execute();
}
private static boolean waitForKeyboardUnlock(Telnet3270Client client, long timeoutMs) {
if (client == null) return false;
long deadline = System.currentTimeMillis() + timeoutMs;
while (System.currentTimeMillis() < deadline) {
boolean locked = false;
if (client.getInputProcessor() != null && client.getInputProcessor().isKeyboardLocked()) {
locked = true;
}
if (client.getOIA() != null && (client.getOIA().isKeyLocked() || client.getOIA().isXSystem())) {
locked = true;
}
if (!locked) {
return true;
}
try {
Thread.sleep(40);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
private void parseCurrentScreen() {
if (client == null || !client.getConnectionState().isFullSession()) {
if (client == null || !client.getConnectionState().isFullSession() || client.getPS() == null) {
statusLabel.setText("No active session to parse.");
return;
}
int rows = client.getPS().getRows();
int cols = client.getPS().getCols();
String screenText = client.getPS().copyString(0, 0, rows - 1, cols - 1);
parseText(screenText, "presentation space");
}
private void parseClipboard() {
try {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable contents = clipboard.getContents(null);
if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
String clipText = (String) contents.getTransferData(DataFlavor.stringFlavor);
if (clipText != null && !clipText.trim().isEmpty()) {
parseText(clipText, "clipboard");
return;
}
}
statusLabel.setText("Clipboard contains no text.");
} catch (Exception ex) {
statusLabel.setText("Error reading clipboard: " + ex.getMessage());
}
}
private void parseText(String text, String source) {
setupTableColumns();
currentEntries.clear();
allEntries.clear();
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
String screenText = client.getPS().getString(0, client.getPS().getSize());
if (hostType == FTConfig.HostType.CMS) {
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(screenText);
currentEntries.addAll(cmsEntries);
for (CMSDirectoryEntry entry : cmsEntries) {
tableModel.addRow(new Object[]{
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
});
}
List<CMSDirectoryEntry> cmsEntries = CMSDirectoryParser.parse(text);
allEntries.addAll(cmsEntries);
} else {
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(screenText);
currentEntries.addAll(tsoEntries);
for (TSODirectoryEntry entry : tsoEntries) {
List<TSODirectoryEntry> tsoEntries = TSODirectoryParser.parse(text);
allEntries.addAll(tsoEntries);
}
applyFilter();
if (allEntries.isEmpty()) {
statusLabel.setText("0 entries found from " + source + ". Use 'Query Host' or 'Paste Clipboard'.");
} else {
statusLabel.setText("Parsed " + allEntries.size() + " entries from " + source + ".");
}
}
private void applyFilter() {
tableModel.setRowCount(0);
currentEntries.clear();
String filter = queryField.getText().trim();
String filterUpper = filter.toUpperCase();
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
for (HostDirectoryEntry entry : allEntries) {
if (!filter.isEmpty() && !matchesFilter(entry.getName().toUpperCase(), filterUpper)) {
continue;
}
currentEntries.add(entry);
if (hostType == FTConfig.HostType.CMS && entry instanceof CMSDirectoryEntry) {
CMSDirectoryEntry cms = (CMSDirectoryEntry) entry;
tableModel.addRow(new Object[]{
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
cms.getFilename(), cms.getFiletype(), cms.getFilemode(),
cms.getRecfm(), cms.getLrecl(), cms.getNumRecords(), cms.getDate() + " " + cms.getTime()
});
} else if (entry instanceof TSODirectoryEntry) {
TSODirectoryEntry tso = (TSODirectoryEntry) entry;
tableModel.addRow(new Object[]{
tso.getDatasetName(), tso.getVolume(), tso.getRecfm(),
tso.getLrecl(), tso.getBlksize(), tso.getTracksAllocated(), tso.getExtents()
});
}
}
statusLabel.setText("Parsed " + currentEntries.size() + " entries from presentation space.");
if (!allEntries.isEmpty()) {
statusLabel.setText(currentEntries.size() + " entries shown (" + allEntries.size() + " total).");
}
}
private boolean matchesFilter(String name, String filter) {
String cleanFilter = filter.replace("'", "").trim();
if (cleanFilter.isEmpty() || cleanFilter.equals("*") || cleanFilter.equals("* * *") || cleanFilter.equals("*.*")) {
return true;
}
if (cleanFilter.contains("*")) {
String regex = "\\Q" + cleanFilter.replace("*", "\\E.*\\Q") + "\\E";
return name.matches(regex);
}
return name.contains(cleanFilter);
}
private void onConfirmSelection() {
@@ -267,4 +477,12 @@ public class HostDirectoryDialog extends JDialog {
public String getSelectedHostFile() {
return selectedHostFile;
}
public List<HostDirectoryEntry> getAllEntries() {
return allEntries;
}
public List<HostDirectoryEntry> getCurrentEntries() {
return currentEntries;
}
}
@@ -236,11 +236,15 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I
boolean wasPanelUnfocused = !hasFocus();
isActivationClick = wasWindowInactive || wasPanelUnfocused;
if (wasWindowInactive) {
if (win != null && wasWindowInactive) {
win.toFront();
win.requestFocus();
}
requestFocus();
SwingUtilities.invokeLater(() -> {
if (!hasFocus()) {
requestFocus();
}
});
// Right-click context menu
if (SwingUtilities.isRightMouseButton(e)) {
@@ -433,6 +437,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I
@Override
public void mouseReleased(MouseEvent e) {
if (!hasFocus()) {
requestFocus();
}
if (isDragging) {
isDragging = false;
if (activeDragHandle != DragHandle.NONE) {
@@ -99,4 +99,45 @@ public class FileTransferSessionAndCmsTest {
// Headless environment; verified through state retention
}
}
@Test
public void testPdsSessionStatePersistence() {
FileTransferDialog.TransferSessionState state = new FileTransferDialog.TransferSessionState();
state.hostType = FTConfig.HostType.TSO;
state.isSend = true;
state.hostFile = "'USER.PDS.DATA'";
state.units = FTConfig.AllocationUnit.CYLINDERS;
state.space = "10,5";
state.dirBlocks = "20";
FileTransferDialog.setLastTransferState(state);
FileTransferDialog.TransferSessionState retrieved = FileTransferDialog.getLastTransferState();
assertNotNull(retrieved);
assertEquals(FTConfig.AllocationUnit.CYLINDERS, retrieved.units);
assertEquals("10,5", retrieved.space);
assertEquals("20", retrieved.dirBlocks);
try {
FileTransferDialog dialog = new FileTransferDialog(null, null);
assertEquals(FTConfig.AllocationUnit.CYLINDERS, dialog.getUnitsCombo().getSelectedItem());
assertEquals("10,5", dialog.getSpaceField().getText());
assertEquals("20", dialog.getDirField().getText());
dialog.dispose();
} catch (HeadlessException ignored) {
}
}
@Test
public void testHostDirectoryDialogStructureAndFilter() {
try {
haus.nightmare.j3270.ui.HostDirectoryDialog dialog =
new haus.nightmare.j3270.ui.HostDirectoryDialog(null, null, FTConfig.HostType.CMS, "TEST EXEC");
assertNotNull(dialog);
assertFalse(dialog.isConfirmed());
assertNull(dialog.getSelectedHostFile());
dialog.dispose();
} catch (HeadlessException ignored) {
}
}
}
@@ -98,4 +98,25 @@ public class TerminalPanelSelectionTest {
// Handled gracefully in headless environments
}
}
@Test
@DisplayName("Mouse press and release on TerminalPanel requests focus")
public void testTerminalPanelMousePressRequestsFocus() {
try {
TerminalPanel panel = new TerminalPanel();
assertTrue(panel.isFocusable());
MouseListener[] mouseListeners = panel.getMouseListeners();
assertTrue(mouseListeners.length > 0);
MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
0, 50, 50, 1, false, MouseEvent.BUTTON1);
mouseListeners[0].mousePressed(pressEvent);
MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
0, 50, 50, 1, false, MouseEvent.BUTTON1);
mouseListeners[0].mouseReleased(releaseEvent);
} catch (HeadlessException ignored) {
}
}
}