added dir synchronization feature, complete translation to english

This commit is contained in:
rdavidek 2026-01-21 19:20:00 +01:00
parent 61b6269a5e
commit 413487f25c
9 changed files with 547 additions and 112 deletions

View File

@ -1,47 +1,51 @@
# KF File Manager # KF File Manager
Dvoupanelový souborový manažer podobný Total Commander, vytvořený v Java 11. A two-panel file manager similar to Total Commander, built with Java 11.
## Funkce ## Features
- **Two panels** for browsing files and directories - **Two panels** for browsing files and directories
- **Copying** files and directories (F5) - **Copying** files and directories (F5)
- **Moving** files and directories (F6) - **Moving** files and directories (F6)
- **Rename** (Shift+F6)
- **Create directory** (F7) - **Create directory** (F7)
- **Delete** files and directories (F8) - **Delete** files and directories (F8)
- **Přejmenování** (Shift+F6) - **Search** for files (Ctrl+F)
- **Vyhledávání** souborů (Ctrl+F) - **Sync Directories** - side-by-side folder comparison and synchronization
- **Přepínání** mezi panely (TAB) - **Toggle** between panels (TAB)
- **Navigation** - double-click or Enter to open a directory - **Navigation** - double-click or Enter to open a directory
- **Zobrazení** velikosti souborů, data modifikace - **View** file sizes, modification dates, and attributes
## Spuštění ## Running
```bash ```bash
mvn clean compile mvn clean compile
mvn exec:java -Dexec.mainClass="cz.kamma.kfmanager.MainApp" mvn exec:java -Dexec.mainClass="cz.kamma.kfmanager.MainApp"
``` ```
Nebo vytvoření JAR souboru: Or build a JAR file:
```bash ```bash
mvn clean package mvn clean package
java -jar target/kf-manager-1.0-SNAPSHOT.jar java -jar target/kf-manager-1.0-SNAPSHOT.jar
``` ```
## Klávesové zkratky ## Keyboard Shortcuts
- **F5** - Kopírovat - **F3** - View file (internal viewer)
- **F6** - Přesunout - **F4** - Edit file
- **Shift+F6** - Přejmenovat - **F5** - Copy
- **F7** - Nový adresář - **F6** - Move
- **F8** - Smazat - **Shift+F6** - Rename
- **TAB** - Přepnout mezi panely - **F7** - New directory
- **Ctrl+F** - Vyhledat soubory - **F8** - Delete
- **Enter** - Otevřít adresář - **TAB** - Toggle between panels
- **Backspace** - Nadřazený adresář - **Ctrl+F** - Search files
- **Ctrl+Y** - Sync directories
- **Enter** - Open directory
- **Backspace** - Parent directory
## Požadavky ## Requirements
- Java 11 nebo vyšší - Java 11 or higher
- Maven 3.6+ - Maven 3.6+

View File

@ -7,5 +7,5 @@ Exec=java -jar /home/kamma/projects/kf-manager/target/kf-manager-1.0-SNAPSHOT-ja
Name=KF File Manager Name=KF File Manager
Icon=/home/kamma/projects/kf-manager/src/main/resources/icon.png Icon=/home/kamma/projects/kf-manager/src/main/resources/icon.png
StartupWMClass=cz-kamma-kfmanager-MainApp StartupWMClass=cz-kamma-kfmanager-MainApp
Comment=Dvoupanelový souborový manažer pro Linux Comment=Two-panel file manager for Linux
Categories=Utility;System; Categories=Utility;System;

View File

@ -24,7 +24,7 @@ public class DriveSelector extends JDialog {
setLayout(new BorderLayout(10, 10)); setLayout(new BorderLayout(10, 10));
((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); ((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Získat seznam dostupných disků // Get list of available drives
File[] roots = File.listRoots(); File[] roots = File.listRoots();
List<DriveInfo> drives = new ArrayList<>(); List<DriveInfo> drives = new ArrayList<>();
@ -32,7 +32,7 @@ public class DriveSelector extends JDialog {
drives.add(new DriveInfo(root)); drives.add(new DriveInfo(root));
} }
// Seznam disků // List of drives
JList<DriveInfo> driveList = new JList<>(drives.toArray(new DriveInfo[0])); JList<DriveInfo> driveList = new JList<>(drives.toArray(new DriveInfo[0]));
driveList.setFont(new Font("Monospaced", Font.PLAIN, 14)); driveList.setFont(new Font("Monospaced", Font.PLAIN, 14));
driveList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); driveList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@ -53,7 +53,7 @@ public class DriveSelector extends JDialog {
} }
}); });
// Double-click pro výběr // Double-click to select
driveList.addMouseListener(new java.awt.event.MouseAdapter() { driveList.addMouseListener(new java.awt.event.MouseAdapter() {
@Override @Override
public void mouseClicked(java.awt.event.MouseEvent e) { public void mouseClicked(java.awt.event.MouseEvent e) {
@ -67,7 +67,7 @@ public class DriveSelector extends JDialog {
} }
}); });
// Enter pro výběr // Enter to select
driveList.addKeyListener(new java.awt.event.KeyAdapter() { driveList.addKeyListener(new java.awt.event.KeyAdapter() {
@Override @Override
public void keyPressed(java.awt.event.KeyEvent e) { public void keyPressed(java.awt.event.KeyEvent e) {
@ -84,7 +84,7 @@ public class DriveSelector extends JDialog {
JScrollPane scrollPane = new JScrollPane(driveList); JScrollPane scrollPane = new JScrollPane(driveList);
add(scrollPane, BorderLayout.CENTER); add(scrollPane, BorderLayout.CENTER);
// Panel s tlačítky // Panel with buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton okButton = new JButton("OK"); JButton okButton = new JButton("OK");
@ -109,7 +109,7 @@ public class DriveSelector extends JDialog {
add(buttonPanel, BorderLayout.SOUTH); add(buttonPanel, BorderLayout.SOUTH);
// Automaticky vybrat první disk // Automatically select the first drive
if (drives.size() > 0) { if (drives.size() > 0) {
driveList.setSelectedIndex(0); driveList.setSelectedIndex(0);
} }
@ -122,7 +122,7 @@ public class DriveSelector extends JDialog {
} }
/** /**
* Pomocná třída pro informace o disku * Helper class for drive information
*/ */
private static class DriveInfo { private static class DriveInfo {
private final File root; private final File root;

View File

@ -61,7 +61,7 @@ public class FileEditor extends JDialog {
} }
public FileEditor(Window parent, File file, String virtualPath, AppConfig config, boolean readOnly) { public FileEditor(Window parent, File file, String virtualPath, AppConfig config, boolean readOnly) {
super(parent, (readOnly ? "Prohlížeč - " : "Editor - ") + (virtualPath != null ? virtualPath.substring(virtualPath.lastIndexOf(File.separator) + 1) : file.getName()), ModalityType.MODELESS); super(parent, (readOnly ? "Viewer - " : "Editor - ") + (virtualPath != null ? virtualPath.substring(virtualPath.lastIndexOf(File.separator) + 1) : file.getName()), ModalityType.MODELESS);
this.file = file; this.file = file;
this.virtualPath = virtualPath; this.virtualPath = virtualPath;
this.config = config; this.config = config;
@ -101,10 +101,10 @@ public class FileEditor extends JDialog {
searchField = new JTextField(20); searchField = new JTextField(20);
searchField.addActionListener(e -> findNext()); searchField.addActionListener(e -> findNext());
JButton nextBtn = new JButton("Dále"); JButton nextBtn = new JButton("Next");
nextBtn.addActionListener(e -> findNext()); nextBtn.addActionListener(e -> findNext());
JButton prevBtn = new JButton("Zpět"); JButton prevBtn = new JButton("Back");
prevBtn.addActionListener(e -> findPrevious()); prevBtn.addActionListener(e -> findPrevious());
JButton closeBtn = new JButton("X"); JButton closeBtn = new JButton("X");
@ -113,7 +113,7 @@ public class FileEditor extends JDialog {
searchStatusLabel = new JLabel(""); searchStatusLabel = new JLabel("");
searchPanel.add(new JLabel("Hledat:")); searchPanel.add(new JLabel("Search:"));
searchPanel.add(searchField); searchPanel.add(searchField);
searchPanel.add(nextBtn); searchPanel.add(nextBtn);
searchPanel.add(prevBtn); searchPanel.add(prevBtn);
@ -199,7 +199,7 @@ public class FileEditor extends JDialog {
} catch (Exception ignore) {} } catch (Exception ignore) {}
searchStatusLabel.setText(""); searchStatusLabel.setText("");
} else { } else {
searchStatusLabel.setText("Nenalezeno"); searchStatusLabel.setText("Not found");
} }
} }
@ -232,7 +232,7 @@ public class FileEditor extends JDialog {
} catch (Exception ignore) {} } catch (Exception ignore) {}
searchStatusLabel.setText(""); searchStatusLabel.setText("");
} else { } else {
searchStatusLabel.setText("Nenalezeno"); searchStatusLabel.setText("Not found");
} }
} }
@ -259,7 +259,7 @@ public class FileEditor extends JDialog {
// Menu bar // Menu bar
createMenuBar(); createMenuBar();
// Textová oblast (editable nebo read-only) // Text area (editable or read-only)
textArea = new JTextArea(); textArea = new JTextArea();
textArea.setFont(config.getEditorFont()); textArea.setFont(config.getEditorFont());
textArea.setTabSize(4); textArea.setTabSize(4);
@ -271,7 +271,7 @@ public class FileEditor extends JDialog {
undoManager.addEdit(e.getEdit()); undoManager.addEdit(e.getEdit());
}); });
// Sledování změn (pouze pro editovatelný režim) // Track changes (editable mode only)
if (!readOnly) { if (!readOnly) {
textArea.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() { textArea.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
public void insertUpdate(javax.swing.event.DocumentEvent e) { setModified(true); } public void insertUpdate(javax.swing.event.DocumentEvent e) { setModified(true); }
@ -295,10 +295,10 @@ public class FileEditor extends JDialog {
// Apply appearance settings (colors, contrast caret etc) // Apply appearance settings (colors, contrast caret etc)
applyAppearance(); applyAppearance();
// Kontextové menu pro clipboard // Context menu for clipboard
setupContextMenu(); setupContextMenu();
// Klávesové zkratky // Keyboard shortcuts
setupKeyBindings(); setupKeyBindings();
// Caret listener to update position and selection info // Caret listener to update position and selection info
@ -314,11 +314,11 @@ public class FileEditor extends JDialog {
private void setupContextMenu() { private void setupContextMenu() {
JPopupMenu popup = new JPopupMenu(); JPopupMenu popup = new JPopupMenu();
JMenuItem undoItem = new JMenuItem("Zpět"); JMenuItem undoItem = new JMenuItem("Undo");
undoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK)); undoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK));
undoItem.addActionListener(e -> { if (undoManager.canUndo()) undoManager.undo(); }); undoItem.addActionListener(e -> { if (undoManager.canUndo()) undoManager.undo(); });
JMenuItem redoItem = new JMenuItem("Znovu"); JMenuItem redoItem = new JMenuItem("Redo");
redoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK)); redoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK));
redoItem.addActionListener(e -> { if (undoManager.canRedo()) undoManager.redo(); }); redoItem.addActionListener(e -> { if (undoManager.canRedo()) undoManager.redo(); });
@ -327,15 +327,15 @@ public class FileEditor extends JDialog {
popup.addSeparator(); popup.addSeparator();
JMenuItem cutItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CutAction()); JMenuItem cutItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CutAction());
cutItem.setText("Vyjmout"); cutItem.setText("Cut");
cutItem.setMnemonic(KeyEvent.VK_X); cutItem.setMnemonic(KeyEvent.VK_X);
JMenuItem copyItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CopyAction()); JMenuItem copyItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CopyAction());
copyItem.setText("Kopírovat"); copyItem.setText("Copy");
copyItem.setMnemonic(KeyEvent.VK_C); copyItem.setMnemonic(KeyEvent.VK_C);
JMenuItem pasteItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.PasteAction()); JMenuItem pasteItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.PasteAction());
pasteItem.setText("Vložit"); pasteItem.setText("Paste");
pasteItem.setMnemonic(KeyEvent.VK_V); pasteItem.setMnemonic(KeyEvent.VK_V);
popup.add(cutItem); popup.add(cutItem);
@ -343,7 +343,7 @@ public class FileEditor extends JDialog {
popup.add(pasteItem); popup.add(pasteItem);
popup.addSeparator(); popup.addSeparator();
JMenuItem selectAllItem = new JMenuItem("Vybrat vše"); JMenuItem selectAllItem = new JMenuItem("Select All");
selectAllItem.setMnemonic(KeyEvent.VK_A); selectAllItem.setMnemonic(KeyEvent.VK_A);
selectAllItem.addActionListener(e -> textArea.selectAll()); selectAllItem.addActionListener(e -> textArea.selectAll());
popup.add(selectAllItem); popup.add(selectAllItem);
@ -428,9 +428,9 @@ public class FileEditor extends JDialog {
JMenu fileMenu = new JMenu("File"); JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic(java.awt.event.KeyEvent.VK_S); fileMenu.setMnemonic(java.awt.event.KeyEvent.VK_S);
// Uložit - pouze v editovacím režimu // Save - editable mode only
if (!readOnly) { if (!readOnly) {
JMenuItem saveItem = new JMenuItem("Uložit"); JMenuItem saveItem = new JMenuItem("Save");
saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)); saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
saveItem.addActionListener(e -> saveFile()); saveItem.addActionListener(e -> saveFile());
fileMenu.add(saveItem); fileMenu.add(saveItem);
@ -438,23 +438,23 @@ public class FileEditor extends JDialog {
fileMenu.addSeparator(); fileMenu.addSeparator();
} }
JMenuItem closeItem = new JMenuItem("Zavřít"); JMenuItem closeItem = new JMenuItem("Close");
closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
closeItem.addActionListener(e -> closeEditor()); closeItem.addActionListener(e -> closeEditor());
fileMenu.add(closeItem); fileMenu.add(closeItem);
menuBar.add(fileMenu); menuBar.add(fileMenu);
// Menu Edit // Edit Menu
JMenu editMenu = new JMenu("Edit"); JMenu editMenu = new JMenu("Edit");
editMenu.setMnemonic(java.awt.event.KeyEvent.VK_E); editMenu.setMnemonic(java.awt.event.KeyEvent.VK_E);
JMenuItem undoItem = new JMenuItem("Zpět"); JMenuItem undoItem = new JMenuItem("Undo");
undoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK)); undoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK));
undoItem.addActionListener(e -> { if (undoManager.canUndo()) undoManager.undo(); }); undoItem.addActionListener(e -> { if (undoManager.canUndo()) undoManager.undo(); });
editMenu.add(undoItem); editMenu.add(undoItem);
JMenuItem redoItem = new JMenuItem("Znovu"); JMenuItem redoItem = new JMenuItem("Redo");
redoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK)); redoItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK));
redoItem.addActionListener(e -> { if (undoManager.canRedo()) undoManager.redo(); }); redoItem.addActionListener(e -> { if (undoManager.canRedo()) undoManager.redo(); });
editMenu.add(redoItem); editMenu.add(redoItem);
@ -462,35 +462,35 @@ public class FileEditor extends JDialog {
editMenu.addSeparator(); editMenu.addSeparator();
JMenuItem cutItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CutAction()); JMenuItem cutItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CutAction());
cutItem.setText("Vyjmout"); cutItem.setText("Cut");
cutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK)); cutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK));
editMenu.add(cutItem); editMenu.add(cutItem);
JMenuItem copyItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CopyAction()); JMenuItem copyItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.CopyAction());
copyItem.setText("Kopírovat"); copyItem.setText("Copy");
copyItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); copyItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
editMenu.add(copyItem); editMenu.add(copyItem);
JMenuItem pasteItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.PasteAction()); JMenuItem pasteItem = new JMenuItem(new javax.swing.text.DefaultEditorKit.PasteAction());
pasteItem.setText("Vložit"); pasteItem.setText("Paste");
pasteItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK)); pasteItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK));
editMenu.add(pasteItem); editMenu.add(pasteItem);
editMenu.addSeparator(); editMenu.addSeparator();
JMenuItem selectAllItem = new JMenuItem("Vybrat vše"); JMenuItem selectAllItem = new JMenuItem("Select All");
selectAllItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); selectAllItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK));
selectAllItem.addActionListener(e -> textArea.selectAll()); selectAllItem.addActionListener(e -> textArea.selectAll());
editMenu.add(selectAllItem); editMenu.add(selectAllItem);
editMenu.addSeparator(); editMenu.addSeparator();
JMenuItem findItem = new JMenuItem("Hledat..."); JMenuItem findItem = new JMenuItem("Search...");
findItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)); findItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK));
findItem.addActionListener(e -> showSearchPanel(true)); findItem.addActionListener(e -> showSearchPanel(true));
editMenu.add(findItem); editMenu.add(findItem);
JMenuItem findNextItem = new JMenuItem("Hledat další"); JMenuItem findNextItem = new JMenuItem("Find Next");
findNextItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0)); findNextItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0));
findNextItem.addActionListener(e -> findNext()); findNextItem.addActionListener(e -> findNext());
editMenu.add(findNextItem); editMenu.add(findNextItem);
@ -511,8 +511,8 @@ public class FileEditor extends JDialog {
menuBar.add(editMenu); menuBar.add(editMenu);
// Menu Nastavení // Settings Menu
JMenu settingsMenu = new JMenu("Nastavení"); JMenu settingsMenu = new JMenu("Settings");
settingsMenu.setMnemonic(java.awt.event.KeyEvent.VK_N); settingsMenu.setMnemonic(java.awt.event.KeyEvent.VK_N);
JMenuItem fontItem = new JMenuItem("Font..."); JMenuItem fontItem = new JMenuItem("Font...");
@ -556,14 +556,14 @@ public class FileEditor extends JDialog {
private void setupKeyBindings() { private void setupKeyBindings() {
JRootPane rootPane = getRootPane(); JRootPane rootPane = getRootPane();
// F2 - Uložit (pouze v editovacím režimu) // F2 - Save (editable mode only)
if (!readOnly) { if (!readOnly) {
rootPane.registerKeyboardAction(e -> saveFile(), rootPane.registerKeyboardAction(e -> saveFile(),
KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
} }
// ESC - Zavřít view // ESC - Close view
rootPane.registerKeyboardAction(e -> closeEditor(), rootPane.registerKeyboardAction(e -> closeEditor(),
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
@ -626,7 +626,7 @@ public class FileEditor extends JDialog {
if (undoManager.canRedo()) undoManager.redo(); if (undoManager.canRedo()) undoManager.redo();
}, KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); }, KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
// Doprava/Doleva a Mezerník pro listování obrázků // Right/Left arrow and Space for image cycling
rootPane.registerKeyboardAction(e -> { rootPane.registerKeyboardAction(e -> {
if (isImageFile(file)) nextImage(); if (isImageFile(file)) nextImage();
}, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
@ -644,7 +644,7 @@ public class FileEditor extends JDialog {
if (undoManager.canRedo()) undoManager.redo(); if (undoManager.canRedo()) undoManager.redo();
}, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); }, KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
// Hledání // Search
rootPane.registerKeyboardAction(e -> { rootPane.registerKeyboardAction(e -> {
if (!isImageFile(file)) showSearchPanel(true); if (!isImageFile(file)) showSearchPanel(true);
}, },
@ -821,11 +821,11 @@ public class FileEditor extends JDialog {
if (readOnly) return; if (readOnly) return;
if (!modified) { if (!modified) {
// No changes - inform the user briefly // No changes - inform the user briefly
JOptionPane.showMessageDialog(this, "Žádné změny k uložení.", "Info", JOptionPane.INFORMATION_MESSAGE); JOptionPane.showMessageDialog(this, "No changes to save.", "Info", JOptionPane.INFORMATION_MESSAGE);
return; return;
} }
int result = showSaveConfirmDialog("Soubor byl změněn. Uložit změny?", "Uložit změny"); int result = showSaveConfirmDialog("File was modified. Save changes?", "Save changes");
if (result == JOptionPane.YES_OPTION) { if (result == JOptionPane.YES_OPTION) {
saveFile(); saveFile();
@ -863,7 +863,7 @@ public class FileEditor extends JDialog {
updateStatus(); updateStatus();
return; return;
} catch (IOException e) { } catch (IOException e) {
JOptionPane.showMessageDialog(this, "Chyba při čtení z archivu: " + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(this, "Error reading from archive: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
dispose(); dispose();
return; return;
} }
@ -1078,7 +1078,7 @@ public class FileEditor extends JDialog {
} }
private void updateTitle() { private void updateTitle() {
setTitle((readOnly ? "Prohlížeč - " : "Editor - ") + file.getName() + (modified ? " *" : "")); setTitle((readOnly ? "Viewer - " : "Editor - ") + file.getName() + (modified ? " *" : ""));
} }
private void saveFile() { private void saveFile() {
@ -1088,14 +1088,14 @@ public class FileEditor extends JDialog {
modified = false; modified = false;
updateTitle(); updateTitle();
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
"Soubor uložen", "File saved",
"Úspěch", "Success",
JOptionPane.INFORMATION_MESSAGE); JOptionPane.INFORMATION_MESSAGE);
updateStatus(); updateStatus();
} catch (IOException e) { } catch (IOException e) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
"Chyba při ukládání:\n" + e.getMessage(), "Error during saving:\n" + e.getMessage(),
"Chyba", "Error",
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
} }
} }
@ -1113,7 +1113,7 @@ public class FileEditor extends JDialog {
private void closeEditor() { private void closeEditor() {
saveWindowState(); saveWindowState();
if (!readOnly && modified) { if (!readOnly && modified) {
int result = showSaveConfirmDialog("Soubor byl změněn. Uložit změny?", "Neuložené změny"); int result = showSaveConfirmDialog("File was modified. Save changes?", "Unsaved changes");
if (result == JOptionPane.YES_OPTION) { if (result == JOptionPane.YES_OPTION) {
saveFile(); saveFile();
@ -1121,7 +1121,7 @@ public class FileEditor extends JDialog {
} else if (result == JOptionPane.NO_OPTION) { } else if (result == JOptionPane.NO_OPTION) {
dispose(); dispose();
} }
// CANCEL_OPTION - nedělat nic // CANCEL_OPTION - do nothing
} else { } else {
dispose(); dispose();
} }
@ -1154,9 +1154,9 @@ public class FileEditor extends JDialog {
dlg.add(new JLabel(message), BorderLayout.CENTER); dlg.add(new JLabel(message), BorderLayout.CENTER);
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton yesBtn = new JButton("Ano"); JButton yesBtn = new JButton("Yes");
JButton noBtn = new JButton("Ne"); JButton noBtn = new JButton("No");
JButton cancelBtn = new JButton("Zrušit"); JButton cancelBtn = new JButton("Cancel");
final int[] result = {JOptionPane.CANCEL_OPTION}; final int[] result = {JOptionPane.CANCEL_OPTION};

View File

@ -5,7 +5,7 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
/** /**
* Dialog pro výběr fontu * Font selection dialog
*/ */
public class FontChooserDialog extends JDialog { public class FontChooserDialog extends JDialog {
private Font selectedFont; private Font selectedFont;
@ -17,7 +17,7 @@ public class FontChooserDialog extends JDialog {
private JTextArea previewArea; private JTextArea previewArea;
public FontChooserDialog(Window parent, Font initialFont) { public FontChooserDialog(Window parent, Font initialFont) {
super(parent, "Výběr fontu", ModalityType.APPLICATION_MODAL); super(parent, "Select Font", ModalityType.APPLICATION_MODAL);
this.selectedFont = initialFont; this.selectedFont = initialFont;
initComponents(); initComponents();
@ -31,10 +31,10 @@ public class FontChooserDialog extends JDialog {
private void initComponents() { private void initComponents() {
setLayout(new BorderLayout(10, 10)); setLayout(new BorderLayout(10, 10));
// Panel pro výběr (font, velikost, styl) // Selection panel (font, size, style)
JPanel selectionPanel = new JPanel(new GridLayout(1, 3, 10, 0)); JPanel selectionPanel = new JPanel(new GridLayout(1, 3, 10, 0));
// Seznam fontů // Font list
JPanel fontPanel = new JPanel(new BorderLayout()); JPanel fontPanel = new JPanel(new BorderLayout());
fontPanel.add(new JLabel("Font:"), BorderLayout.NORTH); fontPanel.add(new JLabel("Font:"), BorderLayout.NORTH);
@ -51,9 +51,9 @@ public class FontChooserDialog extends JDialog {
JScrollPane fontScroll = new JScrollPane(fontList); JScrollPane fontScroll = new JScrollPane(fontList);
fontPanel.add(fontScroll, BorderLayout.CENTER); fontPanel.add(fontScroll, BorderLayout.CENTER);
// Seznam velikostí // Size list
JPanel sizePanel = new JPanel(new BorderLayout()); JPanel sizePanel = new JPanel(new BorderLayout());
sizePanel.add(new JLabel("Velikost:"), BorderLayout.NORTH); sizePanel.add(new JLabel("Size:"), BorderLayout.NORTH);
Integer[] sizes = {8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 26, 28, 32, 36, 40, 48, 56, 64, 72}; Integer[] sizes = {8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 26, 28, 32, 36, 40, 48, 56, 64, 72};
sizeList = new JList<>(sizes); sizeList = new JList<>(sizes);
@ -67,9 +67,9 @@ public class FontChooserDialog extends JDialog {
JScrollPane sizeScroll = new JScrollPane(sizeList); JScrollPane sizeScroll = new JScrollPane(sizeList);
sizePanel.add(sizeScroll, BorderLayout.CENTER); sizePanel.add(sizeScroll, BorderLayout.CENTER);
// Seznam stylů (Plain, Bold, Italic, Bold Italic) // Style list (Plain, Bold, Italic, Bold Italic)
JPanel stylePanel = new JPanel(new BorderLayout()); JPanel stylePanel = new JPanel(new BorderLayout());
stylePanel.add(new JLabel("Styl:"), BorderLayout.NORTH); stylePanel.add(new JLabel("Style:"), BorderLayout.NORTH);
String[] styles = {"Plain", "Bold", "Italic", "Bold Italic"}; String[] styles = {"Plain", "Bold", "Italic", "Bold Italic"};
styleList = new JList<>(styles); styleList = new JList<>(styles);
@ -100,7 +100,7 @@ public class FontChooserDialog extends JDialog {
previewScroll.setPreferredSize(new Dimension(0, 120)); previewScroll.setPreferredSize(new Dimension(0, 120));
previewPanel.add(previewScroll, BorderLayout.CENTER); previewPanel.add(previewScroll, BorderLayout.CENTER);
// Tlačítka // Buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton okButton = new JButton("OK"); JButton okButton = new JButton("OK");
@ -109,7 +109,7 @@ public class FontChooserDialog extends JDialog {
dispose(); dispose();
}); });
JButton cancelButton = new JButton("Zrušit"); JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> { cancelButton.addActionListener(e -> {
approved = false; approved = false;
dispose(); dispose();
@ -129,13 +129,13 @@ public class FontChooserDialog extends JDialog {
} }
private void selectFont(Font font) { private void selectFont(Font font) {
// Najít a vybrat font // Find and select font
fontList.setSelectedValue(font.getName(), true); fontList.setSelectedValue(font.getName(), true);
// Najít a vybrat velikost // Find and select size
sizeList.setSelectedValue(font.getSize(), true); sizeList.setSelectedValue(font.getSize(), true);
// Najít a vybrat styl // Find and select style
int style = font.getStyle(); int style = font.getStyle();
switch (style) { switch (style) {
case Font.BOLD: case Font.BOLD:

View File

@ -452,11 +452,18 @@ public class MainWindow extends JFrame {
// Search button // Search button
JButton btnSearch = new JButton("🔍"); JButton btnSearch = new JButton("🔍");
btnSearch.setToolTipText("Hledat soubory (Alt+F7)"); btnSearch.setToolTipText("Search files (Alt+F7)");
btnSearch.setFocusable(false); btnSearch.setFocusable(false);
btnSearch.addActionListener(e -> showSearchDialog()); btnSearch.addActionListener(e -> showSearchDialog());
toolBar.add(btnSearch); toolBar.add(btnSearch);
// Sync button
JButton btnSync = new JButton("");
btnSync.setToolTipText("Compare directories");
btnSync.setFocusable(false);
btnSync.addActionListener(e -> showSyncDialog());
toolBar.add(btnSync);
toolBar.addSeparator(); toolBar.addSeparator();
// Load custom shortcuts from config // Load custom shortcuts from config
@ -1725,6 +1732,16 @@ public class MainWindow extends JFrame {
dialog.setVisible(true); dialog.setVisible(true);
} }
/**
* Show Sync Directories dialog
*/
private void showSyncDialog() {
File leftDir = leftPanel.getCurrentDirectory();
File rightDir = rightPanel.getCurrentDirectory();
SyncDirectoriesDialog dialog = new SyncDirectoriesDialog(this, leftDir, rightDir, config);
dialog.setVisible(true);
}
/** /**
* Show wildcard select dialog * Show wildcard select dialog
*/ */

View File

@ -124,7 +124,7 @@ public class SearchDialog extends JDialog {
gbc.gridx = 0; gbc.gridx = 0;
gbc.gridy = 0; gbc.gridy = 0;
searchPanel.add(new JLabel("Hledat:"), gbc); searchPanel.add(new JLabel("Search for:"), gbc);
gbc.gridx = 1; gbc.gridx = 1;
gbc.weightx = 1.0; gbc.weightx = 1.0;
@ -231,7 +231,7 @@ public class SearchDialog extends JDialog {
}); });
JScrollPane scrollPane = new JScrollPane(resultsTable); JScrollPane scrollPane = new JScrollPane(resultsTable);
scrollPane.setBorder(BorderFactory.createTitledBorder("Výsledky")); scrollPane.setBorder(BorderFactory.createTitledBorder("Results"));
add(scrollPane, BorderLayout.CENTER); add(scrollPane, BorderLayout.CENTER);
// Status bar with progress and message // Status bar with progress and message
@ -244,24 +244,24 @@ public class SearchDialog extends JDialog {
statusPanel.add(statusLabel, BorderLayout.CENTER); statusPanel.add(statusLabel, BorderLayout.CENTER);
statusPanel.add(statusProgressBar, BorderLayout.EAST); statusPanel.add(statusProgressBar, BorderLayout.EAST);
// Panel s tlačítky // Panel with buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
searchButton = new JButton("Hledat"); searchButton = new JButton("Search");
searchButton.addActionListener(e -> performSearch()); searchButton.addActionListener(e -> performSearch());
viewButton = new JButton("Zobrazit"); viewButton = new JButton("View");
viewButton.setEnabled(false); viewButton.setEnabled(false);
viewButton.addActionListener(e -> viewSelectedFile()); viewButton.addActionListener(e -> viewSelectedFile());
editButton = new JButton("Upravit"); editButton = new JButton("Edit");
editButton.setEnabled(false); editButton.setEnabled(false);
editButton.addActionListener(e -> editSelectedFile()); editButton.addActionListener(e -> editSelectedFile());
cancelButton = new JButton("Zavřít"); cancelButton = new JButton("Close");
cancelButton.addActionListener(e -> dispose()); cancelButton.addActionListener(e -> dispose());
JButton openButton = new JButton("Otevřít umístění"); JButton openButton = new JButton("Open location");
openButton.addActionListener(e -> openSelectedFile()); openButton.addActionListener(e -> openSelectedFile());
buttonPanel.add(searchButton); buttonPanel.add(searchButton);
@ -571,15 +571,15 @@ public class SearchDialog extends JDialog {
statusLabel.setText("Done — found " + foundCount + " files"); statusLabel.setText("Done — found " + foundCount + " files");
if (tableModel.getRowCount() == 0) { if (tableModel.getRowCount() == 0) {
JOptionPane.showMessageDialog(SearchDialog.this, JOptionPane.showMessageDialog(SearchDialog.this,
"Nebyly nalezeny žádné soubory", "No files found",
"Výsledek", "Result",
JOptionPane.INFORMATION_MESSAGE); JOptionPane.INFORMATION_MESSAGE);
} }
} catch (Exception e) { } catch (Exception e) {
statusLabel.setText("Error"); statusLabel.setText("Error");
JOptionPane.showMessageDialog(SearchDialog.this, JOptionPane.showMessageDialog(SearchDialog.this,
"Chyba při hledání: " + e.getMessage(), "Error during search: " + e.getMessage(),
"Chyba", "Error",
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
} }
} }
@ -589,7 +589,7 @@ public class SearchDialog extends JDialog {
} }
/** /**
* Otevře umístění vybraného souboru v exploreru * Open selected file location
*/ */
private void openSelectedFile() { private void openSelectedFile() {
int selectedRow = resultsTable.getSelectedRow(); int selectedRow = resultsTable.getSelectedRow();
@ -622,8 +622,8 @@ public class SearchDialog extends JDialog {
Desktop.getDesktop().open(item.getFile().getParentFile()); Desktop.getDesktop().open(item.getFile().getParentFile());
} catch (Exception e) { } catch (Exception e) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
"Nepodařilo se otevřít umístění: " + e.getMessage(), "Could not open location: " + e.getMessage(),
"Chyba", "Error",
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
} }
} }
@ -666,7 +666,7 @@ public class SearchDialog extends JDialog {
}); });
viewer.setVisible(true); viewer.setVisible(true);
} catch (Exception e) { } catch (Exception e) {
JOptionPane.showMessageDialog(this, "Chyba při otevírání souboru: " + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(this, "Error opening file: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
} }
} }
} }
@ -682,7 +682,7 @@ public class SearchDialog extends JDialog {
if (item != null && !item.isDirectory() && !"..".equals(item.getName())) { if (item != null && !item.isDirectory() && !"..".equals(item.getName())) {
boolean inArchive = item.getPath() != null && !item.getPath().equals(item.getFile().getAbsolutePath()); boolean inArchive = item.getPath() != null && !item.getPath().equals(item.getFile().getAbsolutePath());
if (inArchive) { if (inArchive) {
JOptionPane.showMessageDialog(this, "Editace souborů přímo v archivu není podporována. Použijte 'Otevřít umístění' pro přístup k archivu.", "Informace", JOptionPane.INFORMATION_MESSAGE); JOptionPane.showMessageDialog(this, "Editing web files directly in an archive is not supported. Use 'Go to File' to access the archive.", "Information", JOptionPane.INFORMATION_MESSAGE);
return; return;
} }
try { try {
@ -710,18 +710,18 @@ public class SearchDialog extends JDialog {
}); });
editor.setVisible(true); editor.setVisible(true);
} catch (Exception e) { } catch (Exception e) {
JOptionPane.showMessageDialog(this, "Chyba při otevírání souboru: " + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(this, "Error opening file: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
} }
} }
} }
} }
/** /**
* Model tabulky pro výsledky hledání * Table model for search results
*/ */
private class ResultsTableModel extends AbstractTableModel { private class ResultsTableModel extends AbstractTableModel {
private final String[] columnNames = {"Cesta", "Velikost", "Datum změny"}; private final String[] columnNames = {"Path", "Size", "Modified Date"};
private List<FileItem> results = new ArrayList<>(); private List<FileItem> results = new ArrayList<>();
public void addResult(FileItem item) { public void addResult(FileItem item) {

View File

@ -0,0 +1,414 @@
package cz.kamma.kfmanager.ui;
import cz.kamma.kfmanager.config.AppConfig;
import cz.kamma.kfmanager.model.FileItem;
import cz.kamma.kfmanager.service.FileOperations;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;
public class SyncDirectoriesDialog extends JDialog {
private JTextField leftPathField;
private JTextField rightPathField;
private JCheckBox subdirsCheckbox;
private JCheckBox byContentCheckbox;
private JCheckBox ignoreDateCheckbox;
private JToggleButton showLeftOnlyBtn;
private JToggleButton showEqualBtn;
private JToggleButton showDiffBtn;
private JToggleButton showRightOnlyBtn;
private List<SyncEntry> allEntries = new ArrayList<>();
private JTable resultsTable;
private SyncTableModel tableModel;
private AppConfig config;
private JLabel statusLabel;
public SyncDirectoriesDialog(Frame owner, File leftDir, File rightDir, AppConfig config) {
super(owner, "Synchronize Directories", true);
this.config = config;
initComponents(leftDir, rightDir);
setSize(1000, 600);
setLocationRelativeTo(owner);
}
private void initComponents(File leftDir, File rightDir) {
setLayout(new BorderLayout());
JPanel topPanel = new JPanel();
topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Paths row
JPanel pathsPanel = new JPanel(new GridLayout(1, 2, 10, 0));
JPanel leftP = new JPanel(new BorderLayout(5, 0));
leftPathField = new JTextField(leftDir != null ? leftDir.getAbsolutePath() : "");
JButton browseLeft = new JButton("...");
browseLeft.addActionListener(e -> browsePath(leftPathField));
leftP.add(leftPathField, BorderLayout.CENTER);
leftP.add(browseLeft, BorderLayout.EAST);
JPanel rightP = new JPanel(new BorderLayout(5, 0));
rightPathField = new JTextField(rightDir != null ? rightDir.getAbsolutePath() : "");
JButton browseRight = new JButton("...");
browseRight.addActionListener(e -> browsePath(rightPathField));
rightP.add(rightPathField, BorderLayout.CENTER);
rightP.add(browseRight, BorderLayout.EAST);
pathsPanel.add(leftP);
pathsPanel.add(rightP);
topPanel.add(pathsPanel);
topPanel.add(Box.createVerticalStrut(10));
// Options toolbar
JPanel optionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton compareBtn = new JButton("Compare");
compareBtn.addActionListener(e -> performComparison());
optionsPanel.add(compareBtn);
subdirsCheckbox = new JCheckBox("Include subdirectories");
byContentCheckbox = new JCheckBox("By content");
ignoreDateCheckbox = new JCheckBox("Ignore date");
optionsPanel.add(subdirsCheckbox);
optionsPanel.add(byContentCheckbox);
optionsPanel.add(ignoreDateCheckbox);
optionsPanel.add(Box.createHorizontalStrut(20));
optionsPanel.add(new JLabel("Show:"));
showLeftOnlyBtn = new JToggleButton("");
showEqualBtn = new JToggleButton("=");
showDiffBtn = new JToggleButton("");
showRightOnlyBtn = new JToggleButton("");
showLeftOnlyBtn.setSelected(true);
showEqualBtn.setSelected(true);
showDiffBtn.setSelected(true);
showRightOnlyBtn.setSelected(true);
java.awt.event.ActionListener filterAl = e -> applyFilters();
showLeftOnlyBtn.addActionListener(filterAl);
showEqualBtn.addActionListener(filterAl);
showDiffBtn.addActionListener(filterAl);
showRightOnlyBtn.addActionListener(filterAl);
optionsPanel.add(showLeftOnlyBtn);
optionsPanel.add(showEqualBtn);
optionsPanel.add(showDiffBtn);
optionsPanel.add(showRightOnlyBtn);
optionsPanel.add(Box.createHorizontalStrut(10));
JButton duplicatesBtn = new JButton("duplicates");
duplicatesBtn.addActionListener(e -> {
showEqualBtn.setSelected(true);
showDiffBtn.setSelected(true);
showLeftOnlyBtn.setSelected(false);
showRightOnlyBtn.setSelected(false);
applyFilters();
});
JButton singlesBtn = new JButton("singles");
singlesBtn.addActionListener(e -> {
showEqualBtn.setSelected(false);
showDiffBtn.setSelected(false);
showLeftOnlyBtn.setSelected(true);
showRightOnlyBtn.setSelected(true);
applyFilters();
});
optionsPanel.add(duplicatesBtn);
optionsPanel.add(singlesBtn);
topPanel.add(optionsPanel);
add(topPanel, BorderLayout.NORTH);
// Table
tableModel = new SyncTableModel();
resultsTable = new JTable(tableModel);
resultsTable.setShowGrid(false);
resultsTable.setIntercellSpacing(new Dimension(0, 0));
resultsTable.setDefaultRenderer(Object.class, new SyncTableCellRenderer());
// Adjust column widths
resultsTable.getColumnModel().getColumn(3).setMaxWidth(40); // Icon column
add(new JScrollPane(resultsTable), BorderLayout.CENTER);
// Bottom panel
JPanel bottomPanel = new JPanel(new BorderLayout());
statusLabel = new JLabel("Waiting for comparison...");
statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
bottomPanel.add(statusLabel, BorderLayout.WEST);
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton syncBtn = new JButton("Synchronize...");
syncBtn.addActionListener(e -> performSync());
JButton closeBtn = new JButton("Close");
closeBtn.addActionListener(e -> dispose());
btnPanel.add(syncBtn);
btnPanel.add(closeBtn);
bottomPanel.add(btnPanel, BorderLayout.EAST);
add(bottomPanel, BorderLayout.SOUTH);
}
private void browsePath(JTextField field) {
JFileChooser chooser = new JFileChooser(field.getText());
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
field.setText(chooser.getSelectedFile().getAbsolutePath());
}
}
private void performComparison() {
File leftDir = new File(leftPathField.getText());
File rightDir = new File(rightPathField.getText());
if (!leftDir.isDirectory() || !rightDir.isDirectory()) {
JOptionPane.showMessageDialog(this, "Both paths must be directories.", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
statusLabel.setText("Comparing...");
new SwingWorker<List<SyncEntry>, Void>() {
@Override
protected List<SyncEntry> doInBackground() throws Exception {
return compareDirectories(leftDir, rightDir, "", subdirsCheckbox.isSelected());
}
@Override
protected void done() {
try {
allEntries = get();
applyFilters();
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(SyncDirectoriesDialog.this, "Error during comparison: " + e.getMessage());
statusLabel.setText("Error.");
}
}
}.execute();
}
private List<SyncEntry> compareDirectories(File leftRoot, File rightRoot, String relativePath, boolean recursive) {
Map<String, SyncEntry> map = new TreeMap<>();
File leftDir = relativePath.isEmpty() ? leftRoot : new File(leftRoot, relativePath);
File rightDir = relativePath.isEmpty() ? rightRoot : new File(rightRoot, relativePath);
File[] leftFiles = leftDir.listFiles();
File[] rightFiles = rightDir.listFiles();
if (leftFiles != null) {
for (File f : leftFiles) {
String name = relativePath.isEmpty() ? f.getName() : relativePath + File.separator + f.getName();
SyncEntry entry = new SyncEntry(name);
entry.leftFile = f;
entry.leftSize = f.isDirectory() ? -1 : f.length();
entry.leftModified = f.lastModified();
entry.isDirectory = f.isDirectory();
map.put(name, entry);
}
}
if (rightFiles != null) {
for (File f : rightFiles) {
String name = relativePath.isEmpty() ? f.getName() : relativePath + File.separator + f.getName();
SyncEntry entry = map.get(name);
if (entry == null) {
entry = new SyncEntry(name);
map.put(name, entry);
}
entry.rightFile = f;
entry.rightSize = f.isDirectory() ? -1 : f.length();
entry.rightModified = f.lastModified();
entry.isDirectory = entry.isDirectory || f.isDirectory();
}
}
List<SyncEntry> resultList = new ArrayList<>();
List<String> subdirsToProcess = new ArrayList<>();
for (SyncEntry entry : map.values()) {
if (entry.isDirectory) {
if (recursive) {
subdirsToProcess.add(entry.relativePath);
}
} else {
updateRelation(entry);
resultList.add(entry);
}
}
if (recursive) {
for (String sub : subdirsToProcess) {
resultList.addAll(compareDirectories(leftRoot, rightRoot, sub, true));
}
}
return resultList;
}
private void applyFilters() {
List<SyncEntry> filtered = new ArrayList<>();
for (SyncEntry e : allEntries) {
if ("=".equals(e.relation) && showEqualBtn.isSelected()) filtered.add(e);
else if ("!=".equals(e.relation) && showDiffBtn.isSelected()) filtered.add(e);
else if ("->".equals(e.relation) && showLeftOnlyBtn.isSelected()) filtered.add(e);
else if ("<-".equals(e.relation) && showRightOnlyBtn.isSelected()) filtered.add(e);
}
tableModel.setResults(filtered);
statusLabel.setText(String.format("Showing %d of %d items.", filtered.size(), allEntries.size()));
}
private void updateRelation(SyncEntry entry) {
if (entry.leftFile != null && entry.rightFile == null) {
entry.relation = "->";
} else if (entry.leftFile == null && entry.rightFile != null) {
entry.relation = "<-";
} else {
boolean sizeMatch = entry.leftSize == entry.rightSize;
boolean dateMatch = ignoreDateCheckbox.isSelected() || (entry.leftModified / 1000 == entry.rightModified / 1000);
if (sizeMatch && dateMatch) {
if (byContentCheckbox.isSelected()) {
if (compareByContent(entry.leftFile, entry.rightFile)) {
entry.relation = "=";
} else {
entry.relation = "!=";
}
} else {
entry.relation = "=";
}
} else {
if (entry.leftModified > entry.rightModified) {
entry.relation = "->";
} else if (entry.leftModified < entry.rightModified) {
entry.relation = "<-";
} else {
entry.relation = "!=";
}
}
}
}
private boolean compareByContent(File f1, File f2) {
if (f1.length() != f2.length()) return false;
try (java.io.InputStream i1 = new java.io.FileInputStream(f1);
java.io.InputStream i2 = new java.io.FileInputStream(f2)) {
byte[] b1 = new byte[8192];
byte[] b2 = new byte[8192];
int n;
while ((n = i1.read(b1)) != -1) {
if (i2.read(b2) != n) return false;
if (!java.util.Arrays.equals(b1, 0, n, b2, 0, n)) return false;
}
return true;
} catch (Exception e) {
return false;
}
}
private void performSync() {
// Basic implementation of sync (copy missing/newer files)
// For now just show info, actual copy logic would use FileOperationQueue
JOptionPane.showMessageDialog(this, "Synchronization feature will be implemented soon. For now, only comparison is available.", "Information", JOptionPane.INFORMATION_MESSAGE);
}
private static class SyncEntry {
String relativePath;
File leftFile;
File rightFile;
long leftSize;
long rightSize;
long leftModified;
long rightModified;
String relation = ""; // "=", "!=", "->", "<-"
boolean isDirectory;
SyncEntry(String rel) { this.relativePath = rel; }
}
private static class SyncTableModel extends AbstractTableModel {
private String[] columns = {"Name (L)", "Size", "Date", "", "Date", "Size", "Name (R)"};
private List<SyncEntry> entries = new ArrayList<>();
public void setResults(List<SyncEntry> results) {
this.entries = results;
fireTableDataChanged();
}
@Override public int getRowCount() { return entries.size(); }
@Override public int getColumnCount() { return columns.length; }
@Override public String getColumnName(int col) { return columns[col]; }
@Override
public Object getValueAt(int row, int col) {
SyncEntry e = entries.get(row);
switch(col) {
case 0: return e.leftFile != null ? e.relativePath : "";
case 1: return e.leftFile != null ? formatSize(e.leftSize) : "";
case 2: return e.leftFile != null ? formatDate(e.leftModified) : "";
case 3: return e.relation;
case 4: return e.rightFile != null ? formatDate(e.rightModified) : "";
case 5: return e.rightFile != null ? formatSize(e.rightSize) : "";
case 6: return e.rightFile != null ? e.relativePath : "";
}
return "";
}
private String formatSize(long size) {
if (size < 0) return "<DIR>";
return String.format("%,d", size);
}
private String formatDate(long time) {
return new java.text.SimpleDateFormat("dd.MM.yy HH:mm:ss").format(new Date(time));
}
}
private static class SyncTableCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSel, boolean hasFocus, int row, int col) {
Component c = super.getTableCellRendererComponent(table, value, isSel, hasFocus, row, col);
SyncTableModel model = (SyncTableModel) table.getModel();
SyncEntry entry = model.entries.get(row);
if (!isSel) {
if ("=".equals(entry.relation)) {
c.setForeground(Color.BLACK);
} else if ("!=".equals(entry.relation)) {
c.setForeground(Color.RED);
} else if ("->".equals(entry.relation)) {
c.setForeground(new Color(0, 100, 0)); // Dark green
} else if ("<-".equals(entry.relation)) {
c.setForeground(Color.BLUE);
}
}
if (col == 3) {
setHorizontalAlignment(CENTER);
setFont(getFont().deriveFont(Font.BOLD));
} else if (col == 1 || col == 5) {
setHorizontalAlignment(RIGHT);
} else {
setHorizontalAlignment(LEFT);
}
return c;
}
}
}

View File

@ -14,7 +14,7 @@ public class WildcardSelectDialog extends JDialog {
private boolean confirmed = false; private boolean confirmed = false;
public WildcardSelectDialog(Frame parent) { public WildcardSelectDialog(Frame parent) {
super(parent, "Vybrat podle masky", true); super(parent, "Select by pattern", true);
initComponents(); initComponents();
MainApp.applyReflectiveCaretColor(getContentPane()); MainApp.applyReflectiveCaretColor(getContentPane());
@ -31,7 +31,7 @@ public class WildcardSelectDialog extends JDialog {
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Label // Label
JLabel label = new JLabel("Zadejte masku (např. *.txt, test*.*, file?.dat):"); JLabel label = new JLabel("Enter pattern (e.g. *.txt, test*.*, file?.dat):");
mainPanel.add(label, BorderLayout.NORTH); mainPanel.add(label, BorderLayout.NORTH);
// Text field for pattern // Text field for pattern
@ -50,7 +50,7 @@ public class WildcardSelectDialog extends JDialog {
dispose(); dispose();
}); });
JButton cancelButton = new JButton("Zrušit"); JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> { cancelButton.addActionListener(e -> {
confirmed = false; confirmed = false;
dispose(); dispose();