focus fixes
This commit is contained in:
parent
d116ed7d0f
commit
f88a6c10a1
@ -155,6 +155,42 @@ public class FilePanel extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
add(tabbedPane, BorderLayout.CENTER);
|
add(tabbedPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Click on panel background or empty areas should focus the table
|
||||||
|
addMouseListenerToComponents(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively adds a mouse listener to all components (except buttons/combos)
|
||||||
|
* to request focus for the active table when clicked.
|
||||||
|
*/
|
||||||
|
private void addMouseListenerToComponents(Component comp) {
|
||||||
|
if (comp == null) return;
|
||||||
|
|
||||||
|
// Components that should NOT steal focus back to the table when clicked
|
||||||
|
boolean isInteractive = comp instanceof JButton ||
|
||||||
|
comp instanceof JComboBox ||
|
||||||
|
comp instanceof JTextField ||
|
||||||
|
comp instanceof JTable;
|
||||||
|
|
||||||
|
if (!isInteractive) {
|
||||||
|
comp.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(java.awt.event.MouseEvent e) {
|
||||||
|
FilePanelTab tab = getCurrentTab();
|
||||||
|
if (tab != null) {
|
||||||
|
tab.getFileTable().requestFocusInWindow();
|
||||||
|
tab.selectLastItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof Container) {
|
||||||
|
for (Component child : ((Container) comp).getComponents()) {
|
||||||
|
addMouseListenerToComponents(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -196,6 +232,9 @@ public class FilePanel extends JPanel {
|
|||||||
tabbedPane.addTab(tabTitle, tab);
|
tabbedPane.addTab(tabTitle, tab);
|
||||||
tabbedPane.setSelectedComponent(tab);
|
tabbedPane.setSelectedComponent(tab);
|
||||||
|
|
||||||
|
// Ensure clicking on empty areas of the tab/scrollpane focuses the table
|
||||||
|
addMouseListenerToComponents(tab);
|
||||||
|
|
||||||
// Update path field
|
// Update path field
|
||||||
updatePathField();
|
updatePathField();
|
||||||
updateTabStyles();
|
updateTabStyles();
|
||||||
@ -225,6 +264,9 @@ public class FilePanel extends JPanel {
|
|||||||
tabbedPane.addTab(tabTitle, tab);
|
tabbedPane.addTab(tabTitle, tab);
|
||||||
tabbedPane.setSelectedComponent(tab);
|
tabbedPane.setSelectedComponent(tab);
|
||||||
|
|
||||||
|
// Ensure clicking on empty areas of the tab/scrollpane focuses the table
|
||||||
|
addMouseListenerToComponents(tab);
|
||||||
|
|
||||||
updatePathField();
|
updatePathField();
|
||||||
updateTabStyles();
|
updateTabStyles();
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,10 @@ public class FilePanelTab extends JPanel {
|
|||||||
// Sorting state for FULL mode header clicks
|
// Sorting state for FULL mode header clicks
|
||||||
private int sortColumn = -1; // 0=name,1=size,2=date
|
private int sortColumn = -1; // 0=name,1=size,2=date
|
||||||
private boolean sortAscending = true;
|
private boolean sortAscending = true;
|
||||||
private com.kfmanager.config.AppConfig persistedConfig;
|
private com.kfmanager.config.AppConfig persistedConfig;
|
||||||
|
// Track last selection to restore it if focus is requested on empty area
|
||||||
|
private int lastValidRow = 0;
|
||||||
|
private int lastValidBriefColumn = 0;
|
||||||
// If an archive was opened, we may extract it to a temp directory; track it so
|
// If an archive was opened, we may extract it to a temp directory; track it so
|
||||||
// we can cleanup older temp directories when navigation changes.
|
// we can cleanup older temp directories when navigation changes.
|
||||||
private Path currentArchiveTempDir = null;
|
private Path currentArchiveTempDir = null;
|
||||||
@ -239,10 +242,14 @@ public class FilePanelTab extends JPanel {
|
|||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request focus on table even if clicking on empty area
|
||||||
|
fileTable.requestFocusInWindow();
|
||||||
|
|
||||||
// Single left-click should focus/select the item under cursor but
|
// Single left-click should focus/select the item under cursor but
|
||||||
// should NOT toggle its marked state. This preserves keyboard
|
// should NOT toggle its marked state. This preserves keyboard
|
||||||
// marking (Insert) while making mouse clicks act as simple focus.
|
// marking (Insert) while making mouse clicks act as simple focus.
|
||||||
if (e.getClickCount() == 1 && javax.swing.SwingUtilities.isLeftMouseButton(e)) {
|
if (e.getClickCount() == 1 && javax.swing.SwingUtilities.isLeftMouseButton(e)) {
|
||||||
|
boolean selected = false;
|
||||||
if (row >= 0) {
|
if (row >= 0) {
|
||||||
// Convert brief layout coordinates to absolute index where needed
|
// Convert brief layout coordinates to absolute index where needed
|
||||||
if (viewMode == ViewMode.BRIEF) {
|
if (viewMode == ViewMode.BRIEF) {
|
||||||
@ -255,17 +262,23 @@ public class FilePanelTab extends JPanel {
|
|||||||
briefCurrentColumn = selCol;
|
briefCurrentColumn = selCol;
|
||||||
fileTable.setRowSelectionInterval(selRow, selRow);
|
fileTable.setRowSelectionInterval(selRow, selRow);
|
||||||
fileTable.scrollRectToVisible(fileTable.getCellRect(selRow, selCol, true));
|
fileTable.scrollRectToVisible(fileTable.getCellRect(selRow, selCol, true));
|
||||||
|
selected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// FULL mode: rows map directly
|
// FULL mode: rows map directly
|
||||||
fileTable.setRowSelectionInterval(row, row);
|
fileTable.setRowSelectionInterval(row, row);
|
||||||
fileTable.scrollRectToVisible(fileTable.getCellRect(row, 0, true));
|
fileTable.scrollRectToVisible(fileTable.getCellRect(row, 0, true));
|
||||||
|
selected = true;
|
||||||
}
|
}
|
||||||
fileTable.requestFocusInWindow();
|
|
||||||
repaint();
|
|
||||||
updateStatus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
// Clicked on empty area (row < 0 or empty cell in BRIEF): select the last item in the panel
|
||||||
|
selectLastItem();
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-click opens the item under cursor (directories)
|
// Double-click opens the item under cursor (directories)
|
||||||
@ -281,9 +294,11 @@ public class FilePanelTab extends JPanel {
|
|||||||
// Allow MOUSE_PRESSED for drag initiating gestures, but block standard selection change.
|
// Allow MOUSE_PRESSED for drag initiating gestures, but block standard selection change.
|
||||||
// We'll process selection manually in MOUSE_CLICKED above.
|
// We'll process selection manually in MOUSE_CLICKED above.
|
||||||
if (e.getID() == java.awt.event.MouseEvent.MOUSE_PRESSED) {
|
if (e.getID() == java.awt.event.MouseEvent.MOUSE_PRESSED) {
|
||||||
|
fileTable.requestFocusInWindow();
|
||||||
// Start selection logic on press to support DnD initiate
|
// Start selection logic on press to support DnD initiate
|
||||||
int col = columnAtPoint(e.getPoint());
|
int col = columnAtPoint(e.getPoint());
|
||||||
int row = rowAtPoint(e.getPoint());
|
int row = rowAtPoint(e.getPoint());
|
||||||
|
boolean selected = false;
|
||||||
if (row >= 0) {
|
if (row >= 0) {
|
||||||
if (viewMode == ViewMode.BRIEF) {
|
if (viewMode == ViewMode.BRIEF) {
|
||||||
FileItem item = tableModel.getItemFromBriefLayout(row, col);
|
FileItem item = tableModel.getItemFromBriefLayout(row, col);
|
||||||
@ -293,14 +308,25 @@ public class FilePanelTab extends JPanel {
|
|||||||
int selRow = index % tableModel.briefRowsPerColumn;
|
int selRow = index % tableModel.briefRowsPerColumn;
|
||||||
fileTable.setRowSelectionInterval(selRow, selRow);
|
fileTable.setRowSelectionInterval(selRow, selRow);
|
||||||
briefCurrentColumn = index / tableModel.briefRowsPerColumn;
|
briefCurrentColumn = index / tableModel.briefRowsPerColumn;
|
||||||
|
selected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileTable.setRowSelectionInterval(row, row);
|
fileTable.setRowSelectionInterval(row, row);
|
||||||
|
selected = true;
|
||||||
}
|
}
|
||||||
fileTable.requestFocusInWindow();
|
|
||||||
repaint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
// Clicked on empty area of the table: select the last item
|
||||||
|
selectLastItem();
|
||||||
|
// For empty area, we MUST consume the event to prevent JTable from clearing selection
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
// If we are on a row, we let it pass to super so Drag and Drop can be initiated,
|
||||||
|
// but since we already set selection, JTable will just confirm it.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.getID() == java.awt.event.MouseEvent.MOUSE_RELEASED) {
|
if (e.getID() == java.awt.event.MouseEvent.MOUSE_RELEASED) {
|
||||||
@ -308,7 +334,9 @@ public class FilePanelTab extends JPanel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.processMouseEvent(e);
|
if (!e.isConsumed()) {
|
||||||
|
super.processMouseEvent(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected void processMouseMotionEvent(java.awt.event.MouseEvent e) {
|
protected void processMouseMotionEvent(java.awt.event.MouseEvent e) {
|
||||||
@ -433,6 +461,40 @@ public class FilePanelTab extends JPanel {
|
|||||||
fileTable.setBackground(this.getBackground());
|
fileTable.setBackground(this.getBackground());
|
||||||
fileTable.setOpaque(true);
|
fileTable.setOpaque(true);
|
||||||
|
|
||||||
|
fileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
// Enforce that at least one item is always active (selected) if the table is not empty.
|
||||||
|
fileTable.getSelectionModel().addListSelectionListener(e -> {
|
||||||
|
int row = fileTable.getSelectedRow();
|
||||||
|
if (row >= 0) {
|
||||||
|
if (!e.getValueIsAdjusting()) {
|
||||||
|
lastValidRow = row;
|
||||||
|
lastValidBriefColumn = briefCurrentColumn;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Selection became empty. Attempt to restore it.
|
||||||
|
// We do this even if e.getValueIsAdjusting() is true to prevent temporary selection loss.
|
||||||
|
if (fileTable.getRowCount() > 0) {
|
||||||
|
int targetRow = Math.min(lastValidRow, fileTable.getRowCount() - 1);
|
||||||
|
if (targetRow < 0) targetRow = 0;
|
||||||
|
|
||||||
|
final int finalRow = targetRow;
|
||||||
|
final int finalCol = lastValidBriefColumn;
|
||||||
|
|
||||||
|
// Use invokeLater to avoid potential re-entrancy issues with selection model
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (fileTable != null && fileTable.getSelectionModel().isSelectionEmpty() && fileTable.getRowCount() > 0) {
|
||||||
|
briefCurrentColumn = finalCol;
|
||||||
|
fileTable.setRowSelectionInterval(finalRow, finalRow);
|
||||||
|
// Ensure the restored selection is visible
|
||||||
|
try {
|
||||||
|
fileTable.scrollRectToVisible(fileTable.getCellRect(finalRow, finalCol, true));
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane(fileTable);
|
JScrollPane scrollPane = new JScrollPane(fileTable);
|
||||||
// Enable horizontal scrollbar when needed so BRIEF mode can scroll left-right
|
// Enable horizontal scrollbar when needed so BRIEF mode can scroll left-right
|
||||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
@ -728,6 +790,8 @@ public class FilePanelTab extends JPanel {
|
|||||||
|
|
||||||
this.currentDirectory = directory;
|
this.currentDirectory = directory;
|
||||||
briefCurrentColumn = 0;
|
briefCurrentColumn = 0;
|
||||||
|
lastValidRow = 0;
|
||||||
|
lastValidBriefColumn = 0;
|
||||||
|
|
||||||
File[] files = directory.listFiles();
|
File[] files = directory.listFiles();
|
||||||
List<FileItem> items = new ArrayList<>();
|
List<FileItem> items = new ArrayList<>();
|
||||||
@ -1293,6 +1357,28 @@ public class FilePanelTab extends JPanel {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark/Select the very last item in the list
|
||||||
|
*/
|
||||||
|
public void selectLastItem() {
|
||||||
|
int count = tableModel.getRowCount();
|
||||||
|
if (count > 0) {
|
||||||
|
if (viewMode == ViewMode.BRIEF) {
|
||||||
|
int lastIndex = tableModel.items.size() - 1;
|
||||||
|
briefCurrentColumn = lastIndex / tableModel.briefRowsPerColumn;
|
||||||
|
int row = lastIndex % tableModel.briefRowsPerColumn;
|
||||||
|
fileTable.setRowSelectionInterval(row, row);
|
||||||
|
fileTable.scrollRectToVisible(fileTable.getCellRect(row, briefCurrentColumn, true));
|
||||||
|
} else {
|
||||||
|
int last = count - 1;
|
||||||
|
fileTable.setRowSelectionInterval(last, last);
|
||||||
|
fileTable.scrollRectToVisible(fileTable.getCellRect(last, 0, true));
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setViewMode(ViewMode mode) {
|
public void setViewMode(ViewMode mode) {
|
||||||
if (this.viewMode != mode) {
|
if (this.viewMode != mode) {
|
||||||
|
|||||||
@ -144,20 +144,33 @@ public class MainWindow extends JFrame {
|
|||||||
// ignore and keep default
|
// ignore and keep default
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus listeners to track active panel
|
// Global focus listener to track which panel is active based on focused component
|
||||||
|
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("permanentFocusOwner", evt -> {
|
||||||
|
Component focused = (Component) evt.getNewValue();
|
||||||
|
if (focused != null) {
|
||||||
|
if (SwingUtilities.isDescendingFrom(focused, leftPanel)) {
|
||||||
|
activePanel = leftPanel;
|
||||||
|
updateActivePanelBorder();
|
||||||
|
leftPanel.getFileTable().repaint();
|
||||||
|
rightPanel.getFileTable().repaint();
|
||||||
|
} else if (SwingUtilities.isDescendingFrom(focused, rightPanel)) {
|
||||||
|
activePanel = rightPanel;
|
||||||
|
updateActivePanelBorder();
|
||||||
|
leftPanel.getFileTable().repaint();
|
||||||
|
rightPanel.getFileTable().repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus listeners to track active panel and ensure selection
|
||||||
leftPanel.getFileTable().addFocusListener(new FocusAdapter() {
|
leftPanel.getFileTable().addFocusListener(new FocusAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void focusGained(FocusEvent e) {
|
public void focusGained(FocusEvent e) {
|
||||||
activePanel = leftPanel;
|
|
||||||
updateActivePanelBorder();
|
|
||||||
// Ensure some row is selected
|
// Ensure some row is selected
|
||||||
JTable leftTable = leftPanel.getFileTable();
|
JTable leftTable = leftPanel.getFileTable();
|
||||||
if (leftTable.getSelectedRow() == -1 && leftTable.getRowCount() > 0) {
|
if (leftTable.getSelectedRow() == -1 && leftTable.getRowCount() > 0) {
|
||||||
leftTable.setRowSelectionInterval(0, 0);
|
leftTable.setRowSelectionInterval(0, 0);
|
||||||
}
|
}
|
||||||
// Repaint both panels
|
|
||||||
leftPanel.getFileTable().repaint();
|
|
||||||
rightPanel.getFileTable().repaint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -170,16 +183,11 @@ public class MainWindow extends JFrame {
|
|||||||
rightPanel.getFileTable().addFocusListener(new FocusAdapter() {
|
rightPanel.getFileTable().addFocusListener(new FocusAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void focusGained(FocusEvent e) {
|
public void focusGained(FocusEvent e) {
|
||||||
activePanel = rightPanel;
|
|
||||||
updateActivePanelBorder();
|
|
||||||
// Ensure some row is selected
|
// Ensure some row is selected
|
||||||
JTable rightTable = rightPanel.getFileTable();
|
JTable rightTable = rightPanel.getFileTable();
|
||||||
if (rightTable.getSelectedRow() == -1 && rightTable.getRowCount() > 0) {
|
if (rightTable.getSelectedRow() == -1 && rightTable.getRowCount() > 0) {
|
||||||
rightTable.setRowSelectionInterval(0, 0);
|
rightTable.setRowSelectionInterval(0, 0);
|
||||||
}
|
}
|
||||||
// Repaint both panels
|
|
||||||
leftPanel.getFileTable().repaint();
|
|
||||||
rightPanel.getFileTable().repaint();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user