added features
This commit is contained in:
parent
e543320f97
commit
fd47239832
@ -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");
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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.
|
||||||
@ -1439,6 +1469,53 @@ public class FilePanelTab extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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()) {
|
||||||
return items.get(index);
|
return items.get(index);
|
||||||
|
|||||||
@ -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");
|
||||||
@ -433,10 +433,16 @@ public class MainWindow extends JFrame {
|
|||||||
KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0),
|
KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0),
|
||||||
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
// F9 - Rename
|
// Delete key - global delete binding (also added per-table)
|
||||||
rootPane.registerKeyboardAction(e -> renameFile(),
|
rootPane.registerKeyboardAction(e -> deleteFiles(),
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0),
|
KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
|
||||||
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
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);
|
||||||
|
|
||||||
|
// 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
|
// 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,6 +649,9 @@ 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) {
|
||||||
@ -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,6 +695,12 @@ public class MainWindow extends JFrame {
|
|||||||
* Rename selected file
|
* Rename selected file
|
||||||
*/
|
*/
|
||||||
private void renameFile() {
|
private void renameFile() {
|
||||||
|
// 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();
|
List<FileItem> selectedItems = activePanel.getSelectedItems();
|
||||||
if (selectedItems.size() != 1) {
|
if (selectedItems.size() != 1) {
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
@ -672,18 +709,18 @@ public class MainWindow extends JFrame {
|
|||||||
JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileItem item = selectedItems.get(0);
|
FileItem item = selectedItems.get(0);
|
||||||
String newName = JOptionPane.showInputDialog(this,
|
String newName = JOptionPane.showInputDialog(this,
|
||||||
"New name:",
|
"New name:",
|
||||||
item.getName());
|
item.getName());
|
||||||
|
|
||||||
if (newName != null && !newName.trim().isEmpty() && !newName.equals(item.getName())) {
|
if (newName != null && !newName.trim().isEmpty() && !newName.equals(item.getName())) {
|
||||||
performFileOperation(() -> {
|
performFileOperation(() -> {
|
||||||
FileOperations.rename(item.getFile(), newName.trim());
|
FileOperations.rename(item.getFile(), newName.trim());
|
||||||
}, "Přejmenování dokončeno", activePanel);
|
}, "Přejmenování dokončeno", activePanel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new directory
|
* Create a new directory
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user