added features
This commit is contained in:
parent
e543320f97
commit
fd47239832
@ -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");
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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<FileItem> 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<FileItem> 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<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);
|
||||
editor.setVisible(true);
|
||||
}
|
||||
|
||||
@ -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<String, JPanel> 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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user