From fd4723983211c73fcd4b67eb8403864c63403d04 Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Mon, 17 Nov 2025 19:38:05 +0100 Subject: [PATCH] added features --- .../java/com/kfmanager/config/AppConfig.java | 14 +++ src/main/java/com/kfmanager/ui/FilePanel.java | 6 + .../java/com/kfmanager/ui/FilePanelTab.java | 77 +++++++++++++ .../java/com/kfmanager/ui/MainWindow.java | 105 +++++++++++++----- .../java/com/kfmanager/ui/SettingsDialog.java | 28 ++++- 5 files changed, 203 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/kfmanager/config/AppConfig.java b/src/main/java/com/kfmanager/config/AppConfig.java index cfb8605..29ae206 100644 --- a/src/main/java/com/kfmanager/config/AppConfig.java +++ b/src/main/java/com/kfmanager/config/AppConfig.java @@ -213,6 +213,20 @@ public class AppConfig { setEditorFontStyle(font.getStyle()); } + // --- External editor configuration --- + /** Path to external editor binary (empty = use internal editor) */ + public String getExternalEditorPath() { + return properties.getProperty("editor.external.path", ""); + } + + public void setExternalEditorPath(String path) { + if (path == null || path.isEmpty()) { + properties.remove("editor.external.path"); + } else { + properties.setProperty("editor.external.path", path); + } + } + // --- Appearance (global) settings --- public String getGlobalFontName() { return properties.getProperty("global.font.name", "Monospaced"); diff --git a/src/main/java/com/kfmanager/ui/FilePanel.java b/src/main/java/com/kfmanager/ui/FilePanel.java index 60022c0..2484698 100644 --- a/src/main/java/com/kfmanager/ui/FilePanel.java +++ b/src/main/java/com/kfmanager/ui/FilePanel.java @@ -24,6 +24,12 @@ public class FilePanel extends JPanel { addNewTab(initialPath); } + /** Start inline rename on the currently selected tab/table. */ + public void startInlineRename() { + FilePanelTab tab = getCurrentTab(); + if (tab != null) tab.startInlineRename(); + } + private Runnable switchPanelCallback; public void setSwitchPanelCallback(Runnable cb) { diff --git a/src/main/java/com/kfmanager/ui/FilePanelTab.java b/src/main/java/com/kfmanager/ui/FilePanelTab.java index 46e7e0c..3b4b878 100644 --- a/src/main/java/com/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/com/kfmanager/ui/FilePanelTab.java @@ -52,6 +52,36 @@ public class FilePanelTab extends JPanel { loadDirectory(currentDirectory); } + /** Start inline rename for currently selected item (if single selection). */ + public void startInlineRename() { + // Determine selected row/column according to view mode + int selRow = fileTable.getSelectedRow(); + if (selRow < 0) return; + + int editCol = 0; + if (viewMode == ViewMode.BRIEF) { + editCol = briefCurrentColumn; + // If briefCurrentColumn is out of range, pick column 0 + if (editCol < 0 || editCol >= tableModel.getColumnCount()) editCol = 0; + } else { + editCol = 0; // name column in FULL + } + + // Only allow editing if the cell represents a real item + if (!tableModel.isCellEditable(selRow, editCol)) return; + + // Start editing and select all text in editor + boolean started = fileTable.editCellAt(selRow, editCol); + if (started) { + Component ed = fileTable.getEditorComponent(); + if (ed instanceof JTextField) { + JTextField tf = (JTextField) ed; + tf.requestFocusInWindow(); + tf.selectAll(); + } + } + } + /** * Ensure that column renderers are attached. Useful when the tab was just added to a container * and the ColumnModel may not yet be in its final state. @@ -1438,6 +1468,53 @@ public class FilePanelTab extends JPanel { default: return ""; } } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + if (viewMode == ViewMode.BRIEF) { + FileItem it = getItemFromBriefLayout(rowIndex, columnIndex); + return it != null; // allow editing name in brief cells + } + // In FULL mode only the name column is editable + return columnIndex == 0; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + String newName = aValue != null ? aValue.toString().trim() : null; + if (newName == null || newName.isEmpty()) return; + + FileItem item = null; + if (viewMode == ViewMode.BRIEF) { + item = getItemFromBriefLayout(rowIndex, columnIndex); + } else { + item = getItem(rowIndex); + } + + if (item == null) return; + + // Perform rename using FileOperations and refresh the directory + try { + com.kfmanager.service.FileOperations.rename(item.getFile(), newName); + // reload current directory to reflect updated names + FilePanelTab.this.loadDirectory(FilePanelTab.this.getCurrentDirectory()); + // After reload, select the renamed item and focus the table + SwingUtilities.invokeLater(() -> { + try { + FilePanelTab.this.selectItem(newName); + FilePanelTab.this.getFileTable().requestFocusInWindow(); + } catch (Exception ignore) {} + }); + } catch (Exception ex) { + // show error to user + try { + JOptionPane.showMessageDialog(FilePanelTab.this, + "Rename failed: " + ex.getMessage(), + "Error", + JOptionPane.ERROR_MESSAGE); + } catch (Exception ignore) {} + } + } public FileItem getItem(int index) { if (index >= 0 && index < items.size()) { diff --git a/src/main/java/com/kfmanager/ui/MainWindow.java b/src/main/java/com/kfmanager/ui/MainWindow.java index dcdf43c..83f9426 100644 --- a/src/main/java/com/kfmanager/ui/MainWindow.java +++ b/src/main/java/com/kfmanager/ui/MainWindow.java @@ -261,7 +261,7 @@ public class MainWindow extends JFrame { JButton btnDelete = new JButton("F8 Delete"); btnDelete.addActionListener(e -> deleteFiles()); - JButton btnRename = new JButton("F9 Rename"); + JButton btnRename = new JButton("Shift+F6 Rename"); btnRename.addActionListener(e -> renameFile()); JButton btnExit = new JButton("F10 Exit"); @@ -432,11 +432,17 @@ public class MainWindow extends JFrame { rootPane.registerKeyboardAction(e -> deleteFiles(), KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + + // Delete key - global delete binding (also added per-table) + rootPane.registerKeyboardAction(e -> deleteFiles(), + KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), + JComponent.WHEN_IN_FOCUSED_WINDOW); + // Shift+Delete - treat as delete as well + rootPane.registerKeyboardAction(e -> deleteFiles(), + KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_DOWN_MASK), + JComponent.WHEN_IN_FOCUSED_WINDOW); - // F9 - Rename - rootPane.registerKeyboardAction(e -> renameFile(), - KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0), - JComponent.WHEN_IN_FOCUSED_WINDOW); + // No direct F9 keyboard binding: inline rename should only be triggered by Shift+F6 // TAB - switch panel (global) - works even when additional tabs are opened rootPane.registerKeyboardAction(e -> switchPanels(), @@ -567,6 +573,12 @@ public class MainWindow extends JFrame { deleteFiles(); } }); + // Also map Delete key to deleteFiles on the table level so it works when table has focus + table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "deleteFiles"); + // Also map Shift+Delete on table level + table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_DOWN_MASK), "deleteFiles"); } /** @@ -637,7 +649,10 @@ public class MainWindow extends JFrame { JOptionPane.INFORMATION_MESSAGE); return; } - + // remember current selection row so we can restore selection after deletion + JTable table = activePanel != null ? activePanel.getFileTable() : null; + final int rememberedRow = (table != null) ? table.getSelectedRow() : -1; + StringBuilder message = new StringBuilder("Really delete the following items?\n\n"); for (FileItem item : selectedItems) { message.append(item.getName()).append("\n"); @@ -657,6 +672,22 @@ public class MainWindow extends JFrame { performFileOperation(() -> { FileOperations.delete(selectedItems, null); }, "Mazání dokončeno", activePanel); + + // After deletion and refresh, restore selection: stay on same row if possible, + // otherwise move selection one row up. + SwingUtilities.invokeLater(() -> { + try { + JTable t = activePanel != null ? activePanel.getFileTable() : null; + if (t == null) return; + int rowCount = t.getRowCount(); + if (rowCount == 0) return; + int targetRow = rememberedRow; + if (targetRow < 0) targetRow = 0; + if (targetRow >= rowCount) targetRow = rowCount - 1; // move up if needed + t.setRowSelectionInterval(targetRow, targetRow); + t.scrollRectToVisible(t.getCellRect(targetRow, 0, true)); + } catch (Exception ignore) {} + }); } } @@ -664,24 +695,30 @@ public class MainWindow extends JFrame { * Rename selected file */ private void renameFile() { - List selectedItems = activePanel.getSelectedItems(); - if (selectedItems.size() != 1) { - JOptionPane.showMessageDialog(this, - "Select one file to rename", - "Rename", - JOptionPane.INFORMATION_MESSAGE); - return; - } - - FileItem item = selectedItems.get(0); - String newName = JOptionPane.showInputDialog(this, - "New name:", - item.getName()); - - if (newName != null && !newName.trim().isEmpty() && !newName.equals(item.getName())) { - performFileOperation(() -> { - FileOperations.rename(item.getFile(), newName.trim()); - }, "Přejmenování dokončeno", activePanel); + // Start inline rename in the active panel (it will handle validation) + if (activePanel != null) { + try { + activePanel.startInlineRename(); + } catch (Exception ex) { + // Fallback to dialog-based rename if inline fails for some reason + List selectedItems = activePanel.getSelectedItems(); + if (selectedItems.size() != 1) { + JOptionPane.showMessageDialog(this, + "Select one file to rename", + "Rename", + JOptionPane.INFORMATION_MESSAGE); + return; + } + FileItem item = selectedItems.get(0); + String newName = JOptionPane.showInputDialog(this, + "New name:", + item.getName()); + if (newName != null && !newName.trim().isEmpty() && !newName.equals(item.getName())) { + performFileOperation(() -> { + FileOperations.rename(item.getFile(), newName.trim()); + }, "Přejmenování dokončeno", activePanel); + } + } } } @@ -792,8 +829,24 @@ public class MainWindow extends JFrame { File file = item.getFile(); - // Removed previous 10 MB limit: allow opening large files in the editor. The editor may still choose hex/paged mode for large binaries. - + // If an external editor is configured, try launching it with the file path + String ext = config.getExternalEditorPath(); + if (ext != null && !ext.trim().isEmpty()) { + try { + java.util.List cmd = new java.util.ArrayList<>(); + cmd.add(ext); + cmd.add(file.getAbsolutePath()); + new ProcessBuilder(cmd).start(); + return; + } catch (Exception ex) { + // Fall back to internal editor if external fails + JOptionPane.showMessageDialog(this, + "Nelze spustit externí editor: " + ex.getMessage() + "\nPoužije se interní editor.", + "Chyba", JOptionPane.ERROR_MESSAGE); + } + } + + // Removed previous 10 MB limit: allow opening large files in the editor. The editor may still choose hex/paged mode for large binaries. FileEditor editor = new FileEditor(this, file, config, false); editor.setVisible(true); } diff --git a/src/main/java/com/kfmanager/ui/SettingsDialog.java b/src/main/java/com/kfmanager/ui/SettingsDialog.java index b0a1add..6a76870 100644 --- a/src/main/java/com/kfmanager/ui/SettingsDialog.java +++ b/src/main/java/com/kfmanager/ui/SettingsDialog.java @@ -4,6 +4,7 @@ import com.kfmanager.config.AppConfig; import javax.swing.*; import java.awt.*; +import java.io.File; import java.util.HashMap; import java.util.Map; @@ -25,6 +26,7 @@ public class SettingsDialog extends JDialog { // Editor controls private JButton editorFontBtn; + private JTextField externalEditorField; private final Map panels = new HashMap<>(); @@ -146,7 +148,7 @@ public class SettingsDialog extends JDialog { private JPanel buildEditorPanel() { JPanel p = new JPanel(new BorderLayout(8, 8)); - JPanel grid = new JPanel(new GridLayout(1, 2, 8, 8)); + JPanel grid = new JPanel(new GridLayout(2, 2, 8, 8)); grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); grid.add(new JLabel("Editor font:")); @@ -160,6 +162,30 @@ public class SettingsDialog extends JDialog { } }); grid.add(editorFontBtn); + // External editor path + grid.add(new JLabel("External editor:")); + JPanel extPanel = new JPanel(new BorderLayout(6, 0)); + externalEditorField = new JTextField(config.getExternalEditorPath()); + JButton browse = new JButton("Browse..."); + browse.addActionListener(e -> { + JFileChooser fc = new JFileChooser(); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setDialogTitle("Select external editor executable"); + if (!externalEditorField.getText().isEmpty()) { + File f = new File(externalEditorField.getText()); + if (f.exists()) fc.setSelectedFile(f); + } + int r = fc.showOpenDialog(this); + if (r == JFileChooser.APPROVE_OPTION) { + File sel = fc.getSelectedFile(); + externalEditorField.setText(sel.getAbsolutePath()); + config.setExternalEditorPath(sel.getAbsolutePath()); + // config.saveConfig() will be called when OK is pressed + } + }); + extPanel.add(externalEditorField, BorderLayout.CENTER); + extPanel.add(browse, BorderLayout.EAST); + grid.add(extPanel); p.add(grid, BorderLayout.NORTH); panels.put("Editor", p);