added features

This commit is contained in:
Radek Davidek 2025-11-17 19:38:05 +01:00
parent e543320f97
commit fd47239832
5 changed files with 203 additions and 27 deletions

View File

@ -213,6 +213,20 @@ public class AppConfig {
setEditorFontStyle(font.getStyle()); 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 --- // --- Appearance (global) settings ---
public String getGlobalFontName() { public String getGlobalFontName() {
return properties.getProperty("global.font.name", "Monospaced"); return properties.getProperty("global.font.name", "Monospaced");

View File

@ -24,6 +24,12 @@ public class FilePanel extends JPanel {
addNewTab(initialPath); 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; private Runnable switchPanelCallback;
public void setSwitchPanelCallback(Runnable cb) { public void setSwitchPanelCallback(Runnable cb) {

View File

@ -52,6 +52,36 @@ public class FilePanelTab extends JPanel {
loadDirectory(currentDirectory); 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 * 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. * and the ColumnModel may not yet be in its final state.
@ -1438,6 +1468,53 @@ public class FilePanelTab extends JPanel {
default: return ""; 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) { public FileItem getItem(int index) {
if (index >= 0 && index < items.size()) { if (index >= 0 && index < items.size()) {

View File

@ -261,7 +261,7 @@ public class MainWindow extends JFrame {
JButton btnDelete = new JButton("F8 Delete"); JButton btnDelete = new JButton("F8 Delete");
btnDelete.addActionListener(e -> deleteFiles()); btnDelete.addActionListener(e -> deleteFiles());
JButton btnRename = new JButton("F9 Rename"); JButton btnRename = new JButton("Shift+F6 Rename");
btnRename.addActionListener(e -> renameFile()); btnRename.addActionListener(e -> renameFile());
JButton btnExit = new JButton("F10 Exit"); JButton btnExit = new JButton("F10 Exit");
@ -432,11 +432,17 @@ public class MainWindow extends JFrame {
rootPane.registerKeyboardAction(e -> deleteFiles(), rootPane.registerKeyboardAction(e -> deleteFiles(),
KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW); 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 // No direct F9 keyboard binding: inline rename should only be triggered by Shift+F6
rootPane.registerKeyboardAction(e -> renameFile(),
KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
// TAB - switch panel (global) - works even when additional tabs are opened // TAB - switch panel (global) - works even when additional tabs are opened
rootPane.registerKeyboardAction(e -> switchPanels(), rootPane.registerKeyboardAction(e -> switchPanels(),
@ -567,6 +573,12 @@ public class MainWindow extends JFrame {
deleteFiles(); 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); JOptionPane.INFORMATION_MESSAGE);
return; 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"); StringBuilder message = new StringBuilder("Really delete the following items?\n\n");
for (FileItem item : selectedItems) { for (FileItem item : selectedItems) {
message.append(item.getName()).append("\n"); message.append(item.getName()).append("\n");
@ -657,6 +672,22 @@ public class MainWindow extends JFrame {
performFileOperation(() -> { performFileOperation(() -> {
FileOperations.delete(selectedItems, null); FileOperations.delete(selectedItems, null);
}, "Mazání dokončeno", activePanel); }, "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 * Rename selected file
*/ */
private void renameFile() { private void renameFile() {
List<FileItem> selectedItems = activePanel.getSelectedItems(); // Start inline rename in the active panel (it will handle validation)
if (selectedItems.size() != 1) { if (activePanel != null) {
JOptionPane.showMessageDialog(this, try {
"Select one file to rename", activePanel.startInlineRename();
"Rename", } catch (Exception ex) {
JOptionPane.INFORMATION_MESSAGE); // Fallback to dialog-based rename if inline fails for some reason
return; List<FileItem> selectedItems = activePanel.getSelectedItems();
} if (selectedItems.size() != 1) {
JOptionPane.showMessageDialog(this,
FileItem item = selectedItems.get(0); "Select one file to rename",
String newName = JOptionPane.showInputDialog(this, "Rename",
"New name:", JOptionPane.INFORMATION_MESSAGE);
item.getName()); return;
}
if (newName != null && !newName.trim().isEmpty() && !newName.equals(item.getName())) { FileItem item = selectedItems.get(0);
performFileOperation(() -> { String newName = JOptionPane.showInputDialog(this,
FileOperations.rename(item.getFile(), newName.trim()); "New name:",
}, "Přejmenování dokončeno", activePanel); 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(); 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<String> 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); FileEditor editor = new FileEditor(this, file, config, false);
editor.setVisible(true); editor.setVisible(true);
} }

View File

@ -4,6 +4,7 @@ import com.kfmanager.config.AppConfig;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -25,6 +26,7 @@ public class SettingsDialog extends JDialog {
// Editor controls // Editor controls
private JButton editorFontBtn; private JButton editorFontBtn;
private JTextField externalEditorField;
private final Map<String, JPanel> panels = new HashMap<>(); private final Map<String, JPanel> panels = new HashMap<>();
@ -146,7 +148,7 @@ public class SettingsDialog extends JDialog {
private JPanel buildEditorPanel() { private JPanel buildEditorPanel() {
JPanel p = new JPanel(new BorderLayout(8, 8)); 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.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
grid.add(new JLabel("Editor font:")); grid.add(new JLabel("Editor font:"));
@ -160,6 +162,30 @@ public class SettingsDialog extends JDialog {
} }
}); });
grid.add(editorFontBtn); 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); p.add(grid, BorderLayout.NORTH);
panels.put("Editor", p); panels.put("Editor", p);