toolbar shortcuts added
This commit is contained in:
parent
2c32493e73
commit
f63636eae3
@ -561,4 +561,54 @@ public class AppConfig {
|
||||
properties.remove("cmd.history." + i);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Toolbar shortcuts persistence ---
|
||||
public static class ToolbarShortcut implements Serializable {
|
||||
public String command;
|
||||
public String label;
|
||||
public String iconPath;
|
||||
public String workingDir;
|
||||
|
||||
public ToolbarShortcut(String command, String label, String iconPath, String workingDir) {
|
||||
this.command = command;
|
||||
this.label = label;
|
||||
this.iconPath = iconPath;
|
||||
this.workingDir = workingDir;
|
||||
}
|
||||
}
|
||||
|
||||
public java.util.List<ToolbarShortcut> getToolbarShortcuts() {
|
||||
java.util.List<ToolbarShortcut> list = new java.util.ArrayList<>();
|
||||
int count = Integer.parseInt(properties.getProperty("toolbar.shortcuts.count", "0"));
|
||||
for (int i = 0; i < count; i++) {
|
||||
String cmd = properties.getProperty("toolbar.shortcut." + i + ".command");
|
||||
String label = properties.getProperty("toolbar.shortcut." + i + ".label");
|
||||
String icon = properties.getProperty("toolbar.shortcut." + i + ".iconPath");
|
||||
String workDir = properties.getProperty("toolbar.shortcut." + i + ".workingDir");
|
||||
if (cmd != null) {
|
||||
list.add(new ToolbarShortcut(cmd, label, icon, workDir));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void saveToolbarShortcuts(java.util.List<ToolbarShortcut> shortcuts) {
|
||||
// remove old entries first
|
||||
int old = Integer.parseInt(properties.getProperty("toolbar.shortcuts.count", "0"));
|
||||
for (int i = 0; i < old; i++) {
|
||||
properties.remove("toolbar.shortcut." + i + ".command");
|
||||
properties.remove("toolbar.shortcut." + i + ".label");
|
||||
properties.remove("toolbar.shortcut." + i + ".iconPath");
|
||||
properties.remove("toolbar.shortcut." + i + ".workingDir");
|
||||
}
|
||||
|
||||
properties.setProperty("toolbar.shortcuts.count", String.valueOf(shortcuts.size()));
|
||||
for (int i = 0; i < shortcuts.size(); i++) {
|
||||
ToolbarShortcut s = shortcuts.get(i);
|
||||
properties.setProperty("toolbar.shortcut." + i + ".command", s.command != null ? s.command : "");
|
||||
properties.setProperty("toolbar.shortcut." + i + ".label", s.label != null ? s.label : "");
|
||||
properties.setProperty("toolbar.shortcut." + i + ".iconPath", s.iconPath != null ? s.iconPath : "");
|
||||
properties.setProperty("toolbar.shortcut." + i + ".workingDir", s.workingDir != null ? s.workingDir : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,10 @@ import javax.swing.*;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@ -275,10 +278,32 @@ public class FilePanelTab extends JPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore mouse pressed/released/dragged events to prevent selection changes
|
||||
if (e.getID() == java.awt.event.MouseEvent.MOUSE_PRESSED ||
|
||||
e.getID() == java.awt.event.MouseEvent.MOUSE_RELEASED ||
|
||||
e.getID() == java.awt.event.MouseEvent.MOUSE_DRAGGED) {
|
||||
// Allow MOUSE_PRESSED for drag initiating gestures, but block standard selection change.
|
||||
// We'll process selection manually in MOUSE_CLICKED above.
|
||||
if (e.getID() == java.awt.event.MouseEvent.MOUSE_PRESSED) {
|
||||
// Start selection logic on press to support DnD initiate
|
||||
int col = columnAtPoint(e.getPoint());
|
||||
int row = rowAtPoint(e.getPoint());
|
||||
if (row >= 0) {
|
||||
if (viewMode == ViewMode.BRIEF) {
|
||||
FileItem item = tableModel.getItemFromBriefLayout(row, col);
|
||||
if (item != null) {
|
||||
int index = tableModel.items.indexOf(item);
|
||||
if (index >= 0) {
|
||||
int selRow = index % tableModel.briefRowsPerColumn;
|
||||
fileTable.setRowSelectionInterval(selRow, selRow);
|
||||
briefCurrentColumn = index / tableModel.briefRowsPerColumn;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fileTable.setRowSelectionInterval(row, row);
|
||||
}
|
||||
fileTable.requestFocusInWindow();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.getID() == java.awt.event.MouseEvent.MOUSE_RELEASED) {
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
@ -287,20 +312,60 @@ public class FilePanelTab extends JPanel {
|
||||
}
|
||||
@Override
|
||||
protected void processMouseMotionEvent(java.awt.event.MouseEvent e) {
|
||||
// Block mouse-dragged events so dragging cannot change selection
|
||||
// or initiate any drag behavior. Consume the event and do nothing.
|
||||
if (e.getID() == java.awt.event.MouseEvent.MOUSE_DRAGGED) {
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
// Allow mouse movement to pass through to support Drag and Drop
|
||||
super.processMouseMotionEvent(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Disable Swing drag-and-drop support and transfer handling on the table
|
||||
// to ensure no drag gestures or DnD occur.
|
||||
fileTable.setDragEnabled(false);
|
||||
fileTable.setTransferHandler(null);
|
||||
// Enable Drag and Drop on the table
|
||||
fileTable.setDragEnabled(true);
|
||||
fileTable.setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public int getSourceActions(JComponent c) {
|
||||
return COPY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Transferable createTransferable(JComponent c) {
|
||||
List<FileItem> selected = getSelectedItems();
|
||||
if (selected.isEmpty()) {
|
||||
// If nothing explicitly marked, use the focused item
|
||||
FileItem focused = getFocusedItem();
|
||||
if (focused != null && !focused.getName().equals("..")) {
|
||||
selected = new ArrayList<>();
|
||||
selected.add(focused);
|
||||
}
|
||||
}
|
||||
|
||||
final List<File> files = new ArrayList<>();
|
||||
for (FileItem item : selected) {
|
||||
if (!item.getName().equals("..")) {
|
||||
files.add(item.getFile());
|
||||
}
|
||||
}
|
||||
|
||||
if (files.isEmpty()) return null;
|
||||
|
||||
return new Transferable() {
|
||||
@Override
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return new DataFlavor[]{DataFlavor.javaFileListFlavor};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
||||
return DataFlavor.javaFileListFlavor.equals(flavor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
||||
if (!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor);
|
||||
return files;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
fileTable.setFocusTraversalKeysEnabled(false);
|
||||
try {
|
||||
fileTable.setDropMode(null);
|
||||
|
||||
@ -7,8 +7,11 @@ import com.kfmanager.service.FileOperations;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.event.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -20,6 +23,7 @@ public class MainWindow extends JFrame {
|
||||
private FilePanel rightPanel;
|
||||
private FilePanel activePanel;
|
||||
private JPanel buttonPanel;
|
||||
private JToolBar toolBar;
|
||||
private JComboBox<String> commandLine;
|
||||
private AppConfig config;
|
||||
|
||||
@ -270,9 +274,37 @@ public class MainWindow extends JFrame {
|
||||
* Create toolbar with buttons for changing view mode
|
||||
*/
|
||||
private void createToolBar() {
|
||||
JToolBar toolBar = new JToolBar();
|
||||
toolBar.setFloatable(false);
|
||||
toolBar.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
if (toolBar == null) {
|
||||
toolBar = new JToolBar();
|
||||
toolBar.setFloatable(false);
|
||||
toolBar.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
add(toolBar, BorderLayout.NORTH);
|
||||
|
||||
// Enable Drag and Drop for adding shortcuts
|
||||
toolBar.setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public boolean canImport(TransferSupport support) {
|
||||
return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean importData(TransferSupport support) {
|
||||
if (!canImport(support)) return false;
|
||||
try {
|
||||
List<File> files = (List<File>) support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
|
||||
if (files != null && !files.isEmpty()) {
|
||||
showAddToolbarShortcutDialog(files.get(0));
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toolBar.removeAll();
|
||||
|
||||
// Button for BRIEF mode
|
||||
JButton btnBrief = new JButton("☰ Brief");
|
||||
@ -299,12 +331,165 @@ public class MainWindow extends JFrame {
|
||||
|
||||
toolBar.addSeparator();
|
||||
|
||||
// Info label
|
||||
JLabel infoLabel = new JLabel(" View Mode ");
|
||||
infoLabel.setFont(infoLabel.getFont().deriveFont(Font.PLAIN, 10f));
|
||||
toolBar.add(infoLabel);
|
||||
// Load custom shortcuts from config
|
||||
List<AppConfig.ToolbarShortcut> shortcuts = config.getToolbarShortcuts();
|
||||
for (AppConfig.ToolbarShortcut s : shortcuts) {
|
||||
JButton btn = new JButton();
|
||||
boolean hasIcon = false;
|
||||
if (s.iconPath != null && !s.iconPath.isEmpty()) {
|
||||
try {
|
||||
File iconFile = new File(s.iconPath);
|
||||
if (iconFile.exists()) {
|
||||
btn.setIcon(new ImageIcon(new ImageIcon(s.iconPath).getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)));
|
||||
hasIcon = true;
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
add(toolBar, BorderLayout.NORTH);
|
||||
// If no icon found, use the label text, otherwise use label as tooltip
|
||||
if (!hasIcon) {
|
||||
btn.setText(s.label);
|
||||
}
|
||||
btn.setToolTipText(s.label + " (" + s.command + ")");
|
||||
|
||||
btn.setFocusable(false);
|
||||
btn.addActionListener(e -> executeNative(s.command, s.workingDir));
|
||||
|
||||
// Context menu for Edit/Delete
|
||||
JPopupMenu shortcutPopup = new JPopupMenu();
|
||||
JMenuItem editItem = new JMenuItem("Edit");
|
||||
editItem.addActionListener(ae -> showEditToolbarShortcutDialog(s));
|
||||
JMenuItem deleteItem = new JMenuItem("Delete");
|
||||
deleteItem.addActionListener(ae -> {
|
||||
int choice = JOptionPane.showConfirmDialog(MainWindow.this, "Remove this shortcut?", "Toolbar", JOptionPane.YES_NO_OPTION);
|
||||
if (choice == JOptionPane.YES_OPTION) {
|
||||
removeToolbarShortcut(s);
|
||||
}
|
||||
});
|
||||
shortcutPopup.add(editItem);
|
||||
shortcutPopup.add(deleteItem);
|
||||
|
||||
btn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
shortcutPopup.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
shortcutPopup.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
toolBar.add(btn);
|
||||
}
|
||||
|
||||
toolBar.revalidate();
|
||||
toolBar.repaint();
|
||||
}
|
||||
|
||||
private void showAddToolbarShortcutDialog(File file) {
|
||||
showToolbarShortcutEditor(null, file);
|
||||
}
|
||||
|
||||
private void showEditToolbarShortcutDialog(AppConfig.ToolbarShortcut shortcut) {
|
||||
showToolbarShortcutEditor(shortcut, null);
|
||||
}
|
||||
|
||||
private void showToolbarShortcutEditor(AppConfig.ToolbarShortcut existing, File file) {
|
||||
String initialLabel = (existing != null) ? existing.label : (file != null ? file.getName() : "");
|
||||
String initialCmd = (existing != null) ? existing.command : (file != null ? file.getAbsolutePath() : "");
|
||||
String initialWorkDir = (existing != null) ? existing.workingDir :
|
||||
(file != null ? (file.isDirectory() ? file.getAbsolutePath() : file.getParent()) : "");
|
||||
String initialIcon = (existing != null) ? existing.iconPath : "";
|
||||
|
||||
JTextField labelField = new JTextField(initialLabel);
|
||||
JTextField cmdField = new JTextField(initialCmd);
|
||||
JTextField workingDirField = new JTextField(initialWorkDir);
|
||||
JTextField iconField = new JTextField(initialIcon);
|
||||
|
||||
JPanel panel = new JPanel(new GridLayout(0, 1));
|
||||
panel.add(new JLabel("Label:"));
|
||||
panel.add(labelField);
|
||||
panel.add(new JLabel("Command:"));
|
||||
panel.add(cmdField);
|
||||
panel.add(new JLabel("Working directory:"));
|
||||
|
||||
JPanel workDirPanel = new JPanel(new BorderLayout());
|
||||
workDirPanel.add(workingDirField, BorderLayout.CENTER);
|
||||
JButton browseWorkDir = new JButton("...");
|
||||
browseWorkDir.addActionListener(e -> {
|
||||
JFileChooser chooser = new JFileChooser(workingDirField.getText());
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
workingDirField.setText(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
});
|
||||
workDirPanel.add(browseWorkDir, BorderLayout.EAST);
|
||||
panel.add(workDirPanel);
|
||||
|
||||
panel.add(new JLabel("Icon path (optional):"));
|
||||
|
||||
JPanel iconPanel = new JPanel(new BorderLayout());
|
||||
iconPanel.add(iconField, BorderLayout.CENTER);
|
||||
JButton browseIcon = new JButton("...");
|
||||
browseIcon.addActionListener(e -> {
|
||||
String startPath = iconField.getText().trim();
|
||||
if (startPath.isEmpty()) {
|
||||
startPath = workingDirField.getText().trim();
|
||||
if (startPath.isEmpty()) {
|
||||
startPath = cmdField.getText().trim();
|
||||
}
|
||||
}
|
||||
|
||||
JFileChooser chooser = new JFileChooser(startPath);
|
||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
iconField.setText(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
});
|
||||
iconPanel.add(browseIcon, BorderLayout.EAST);
|
||||
panel.add(iconPanel);
|
||||
|
||||
String title = (existing != null) ? "Edit Toolbar Shortcut" : "Add Toolbar Shortcut";
|
||||
int result = JOptionPane.showConfirmDialog(this, panel, title, JOptionPane.OK_CANCEL_OPTION);
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
String label = labelField.getText().trim();
|
||||
String command = cmdField.getText().trim();
|
||||
String icon = iconField.getText().trim();
|
||||
String workDir = workingDirField.getText().trim();
|
||||
|
||||
if (!command.isEmpty()) {
|
||||
if (label.isEmpty()) label = (file != null ? file.getName() : "Shortcut");
|
||||
List<AppConfig.ToolbarShortcut> shortcuts = config.getToolbarShortcuts();
|
||||
|
||||
if (existing != null) {
|
||||
// Find and replace
|
||||
for (int i = 0; i < shortcuts.size(); i++) {
|
||||
AppConfig.ToolbarShortcut s = shortcuts.get(i);
|
||||
// Identify by original command/label if they haven't changed yet in memory
|
||||
if (s.command.equals(existing.command) && s.label.equals(existing.label)) {
|
||||
shortcuts.set(i, new AppConfig.ToolbarShortcut(command, label, icon, workDir));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shortcuts.add(new AppConfig.ToolbarShortcut(command, label, icon, workDir));
|
||||
}
|
||||
|
||||
config.saveToolbarShortcuts(shortcuts);
|
||||
createToolBar(); // refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeToolbarShortcut(AppConfig.ToolbarShortcut shortcut) {
|
||||
List<AppConfig.ToolbarShortcut> shortcuts = config.getToolbarShortcuts();
|
||||
shortcuts.removeIf(s -> s.command.equals(shortcut.command) && s.label.equals(shortcut.label));
|
||||
config.saveToolbarShortcuts(shortcuts);
|
||||
createToolBar();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1361,6 +1546,82 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
private void executeNative(String command, String workingDir) {
|
||||
if (command == null || command.trim().isEmpty()) return;
|
||||
|
||||
File currentDir;
|
||||
if (workingDir != null && !workingDir.trim().isEmpty()) {
|
||||
currentDir = new File(workingDir);
|
||||
} else {
|
||||
currentDir = activePanel.getCurrentDirectory();
|
||||
}
|
||||
|
||||
if (currentDir == null || !currentDir.exists()) {
|
||||
currentDir = new File(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if it's a file path that exists (and not a complex command)
|
||||
if (!command.contains(" ") || (command.startsWith("\"") && command.endsWith("\"") && !command.substring(1, command.length()-1).contains("\""))) {
|
||||
String path = command.startsWith("\"") ? command.substring(1, command.length()-1) : command;
|
||||
File file = new File(path);
|
||||
if (file.exists()) {
|
||||
// Start executable files directly, otherwise use Desktop.open
|
||||
if (file.canExecute() && !file.isDirectory()) {
|
||||
new ProcessBuilder(file.getAbsolutePath()).directory(currentDir).start();
|
||||
return;
|
||||
}
|
||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
Desktop.getDesktop().open(file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise try running as a native process
|
||||
List<String> cmdList = parseCommand(command);
|
||||
try {
|
||||
new ProcessBuilder(cmdList).directory(currentDir).start();
|
||||
} catch (IOException ex) {
|
||||
// Fallback for Linux/macOS: try via shell
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
if (osName.contains("linux") || osName.contains("mac")) {
|
||||
new ProcessBuilder("sh", "-c", command).directory(currentDir).start();
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Error executing native command: " + e.getMessage(),
|
||||
"Error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> parseCommand(String command) {
|
||||
List<String> list = new ArrayList<>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
for (int i = 0; i < command.length(); i++) {
|
||||
char c = command.charAt(i);
|
||||
if (c == '\"') {
|
||||
inQuotes = !inQuotes;
|
||||
} else if (c == ' ' && !inQuotes) {
|
||||
if (sb.length() > 0) {
|
||||
list.add(sb.toString());
|
||||
sb.setLength(0);
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
list.add(sb.toString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void addCommandToHistory(String command) {
|
||||
if (command == null || command.isEmpty()) return;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user