From af98d9f1121a201b9704c749af440e9f708ae6a2 Mon Sep 17 00:00:00 2001 From: rdavidek Date: Mon, 19 Jan 2026 21:45:01 +0100 Subject: [PATCH] backgroun file operations, error handling --- src/main/java/cz/kamma/kfmanager/MainApp.java | 2 +- .../kfmanager/service/FileOperations.java | 136 ++- .../cz/kamma/kfmanager/ui/FilePanel.java.bak | 1043 ----------------- .../cz/kamma/kfmanager/ui/FilePanelTab.java | 71 +- .../cz/kamma/kfmanager/ui/MainWindow.java | 87 +- .../cz/kamma/kfmanager/ui/ProgressDialog.java | 6 +- 6 files changed, 264 insertions(+), 1081 deletions(-) delete mode 100644 src/main/java/cz/kamma/kfmanager/ui/FilePanel.java.bak diff --git a/src/main/java/cz/kamma/kfmanager/MainApp.java b/src/main/java/cz/kamma/kfmanager/MainApp.java index f250c94..94e97a4 100644 --- a/src/main/java/cz/kamma/kfmanager/MainApp.java +++ b/src/main/java/cz/kamma/kfmanager/MainApp.java @@ -11,7 +11,7 @@ import java.awt.event.KeyEvent; */ public class MainApp { - public static final String APP_VERSION = "0.0.4"; + public static final String APP_VERSION = "0.0.5"; public static void main(String[] args) { // Set application name for X11/Wayland WM_CLASS diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java index 3cd29cd..29d06fc 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java @@ -49,10 +49,24 @@ public class FileOperations { } } - if (source.isDirectory()) { - copyDirectory(source.toPath(), target.toPath(), totalSize, currentCopied, callback, globalResponse); - } else { - copyFileWithProgress(source.toPath(), target.toPath(), totalSize, currentCopied, callback); + while (true) { + try { + if (source.isDirectory()) { + copyDirectory(source.toPath(), target.toPath(), totalSize, currentCopied, callback, globalResponse); + } else { + copyFileWithProgress(source.toPath(), target.toPath(), totalSize, currentCopied, callback); + } + break; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(source, e); + if (res == ErrorResponse.ABORT) throw e; + if (res == ErrorResponse.RETRY) continue; + break; + } else { + throw e; + } + } } } } @@ -84,6 +98,7 @@ public class FileOperations { } private static void copyFileWithProgress(Path source, Path target, long totalSize, long[] totalCopied, ProgressCallback callback) throws IOException { + long initialTotalCopied = totalCopied[0]; try (InputStream in = Files.newInputStream(source); OutputStream out = Files.newOutputStream(target)) { byte[] buffer = new byte[8192]; @@ -96,6 +111,9 @@ public class FileOperations { callback.onProgress(totalCopied[0], totalSize, source.getFileName().toString()); } } + } catch (IOException e) { + totalCopied[0] = initialTotalCopied; + throw e; } Files.setLastModifiedTime(target, Files.getLastModifiedTime(source)); } @@ -105,8 +123,20 @@ public class FileOperations { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetDir = target.resolve(source.relativize(dir)); - Files.createDirectories(targetDir); - return FileVisitResult.CONTINUE; + while (true) { + try { + Files.createDirectories(targetDir); + return FileVisitResult.CONTINUE; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(dir.toFile(), e); + if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; + if (res == ErrorResponse.RETRY) continue; + return FileVisitResult.SKIP_SUBTREE; + } + throw e; + } + } } @Override @@ -128,8 +158,20 @@ public class FileOperations { } } - copyFileWithProgress(file, targetFile, totalSize, totalCopied, callback); - return FileVisitResult.CONTINUE; + while (true) { + try { + copyFileWithProgress(file, targetFile, totalSize, totalCopied, callback); + return FileVisitResult.CONTINUE; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(file.toFile(), e); + if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; + if (res == ErrorResponse.RETRY) continue; + return FileVisitResult.CONTINUE; + } + throw e; + } + } } }); } @@ -170,7 +212,21 @@ public class FileOperations { callback.onProgress(current, total, source.getName()); } - Files.move(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + while (true) { + try { + Files.move(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + break; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(source, e); + if (res == ErrorResponse.ABORT) throw e; + if (res == ErrorResponse.RETRY) continue; + break; + } else { + throw e; + } + } + } } } @@ -190,10 +246,24 @@ public class FileOperations { callback.onProgress(current, total, file.getName()); } - if (file.isDirectory()) { - deleteDirectory(file.toPath()); - } else { - Files.delete(file.toPath()); + while (true) { + try { + if (file.isDirectory()) { + deleteDirectory(file.toPath(), callback); + } else { + Files.delete(file.toPath()); + } + break; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(file, e); + if (res == ErrorResponse.ABORT) throw e; + if (res == ErrorResponse.RETRY) continue; + break; + } else { + throw e; + } + } } } } @@ -219,18 +289,43 @@ public class FileOperations { /** * Delete directory recursively */ - private static void deleteDirectory(Path directory) throws IOException { + private static void deleteDirectory(Path directory, ProgressCallback callback) throws IOException { Files.walkFileTree(directory, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; + while (true) { + try { + Files.delete(file); + return FileVisitResult.CONTINUE; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(file.toFile(), e); + if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; + if (res == ErrorResponse.RETRY) continue; + return FileVisitResult.CONTINUE; + } + throw e; + } + } } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; + if (exc != null) throw exc; + while (true) { + try { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } catch (IOException e) { + if (callback != null) { + ErrorResponse res = callback.onError(dir.toFile(), e); + if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; + if (res == ErrorResponse.RETRY) continue; + return FileVisitResult.CONTINUE; + } + throw e; + } + } } }); } @@ -572,10 +667,15 @@ public class FileOperations { YES, NO, YES_TO_ALL, NO_TO_ALL, CANCEL } + public enum ErrorResponse { + SKIP, RETRY, ABORT + } + public interface ProgressCallback { void onProgress(long current, long total, String currentFile); default boolean isCancelled() { return false; } default OverwriteResponse confirmOverwrite(File file) { return OverwriteResponse.YES; } + default ErrorResponse onError(File file, Exception e) { return ErrorResponse.ABORT; } } /** diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java.bak b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java.bak deleted file mode 100644 index 9f52bb1..0000000 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java.bak +++ /dev/null @@ -1,1043 +0,0 @@ -package cz.kamma.kfmanager.ui; - -import cz.kamma.kfmanager.model.FileItem; - -import javax.swing.*; -import javax.swing.filechooser.FileSystemView; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.DefaultTableCellRenderer; -import java.awt.*; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -/** - * Panel zobrazující seznam souborů a adresářů - */ -public class FilePanel extends JPanel { - - public enum ViewMode { - FULL, // Plné informace (název, velikost, datum) - BRIEF // Pouze názvy - } - - private File currentDirectory; - private JTable fileTable; - private FileTableModel tableModel; - private JTextField pathField; - private JLabel statusLabel; - private ViewMode viewMode = ViewMode.FULL; - private int briefCurrentColumn = 0; // Aktuální sloupec v BRIEF módu - private int briefColumnBeforeEnter = 0; // Sloupec před vstupem do adresáře (pro návrat) - - public FilePanel(String initialPath) { - this.currentDirectory = new File(initialPath); - initComponents(); - loadDirectory(currentDirectory); - } - - private void initComponents() { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - // Panel s cestou - JPanel topPanel = new JPanel(new BorderLayout()); - pathField = new JTextField(); - pathField.setEditable(false); - pathField.setFont(new Font("Monospaced", Font.PLAIN, 12)); - topPanel.add(pathField, BorderLayout.CENTER); - - // Tlačítko pro nadřazený adresář - JButton upButton = new JButton("↑"); - upButton.setToolTipText("Nadřazený adresář (Backspace)"); - upButton.addActionListener(e -> navigateUp()); - topPanel.add(upButton, BorderLayout.EAST); - - add(topPanel, BorderLayout.NORTH); - - // Tabulka se soubory - tableModel = new FileTableModel(); - fileTable = new JTable(tableModel); - fileTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - fileTable.setFont(new Font("Monospaced", Font.PLAIN, 12)); - fileTable.setRowHeight(20); - fileTable.setShowGrid(false); - fileTable.setIntercellSpacing(new Dimension(0, 0)); - - // Nastavit renderery podle výchozího režimu - updateColumnRenderers(); - updateColumnWidths(); - - // Double-click pro otevření adresáře - fileTable.addMouseListener(new java.awt.event.MouseAdapter() { - @Override - public void mouseClicked(java.awt.event.MouseEvent e) { - // V BRIEF módu zaznamenat aktuální sloupec - if (viewMode == ViewMode.BRIEF) { - int col = fileTable.columnAtPoint(e.getPoint()); - if (col >= 0) { - briefCurrentColumn = col; - fileTable.repaint(); - } - } - - // Aktualizovat status bar po kliknutí - updateStatus(); - - if (e.getClickCount() == 2) { - openSelectedItem(); - } - } - }); - - // Enter pro otevření - fileTable.addKeyListener(new java.awt.event.KeyAdapter() { - @Override - public void keyPressed(java.awt.event.KeyEvent e) { - if (e.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) { - openSelectedItem(); - e.consume(); - } else if (e.getKeyCode() == java.awt.event.KeyEvent.VK_BACK_SPACE) { - navigateUp(); - e.consume(); - } else if (e.getKeyCode() == java.awt.event.KeyEvent.VK_HOME) { - // Skok na první položku - if (viewMode == ViewMode.BRIEF) { - // V BRIEF módu skočit na první položku (index 0) - if (tableModel.items.size() > 0) { - briefCurrentColumn = 0; - fileTable.setRowSelectionInterval(0, 0); - fileTable.scrollRectToVisible(fileTable.getCellRect(0, 0, true)); - fileTable.repaint(); - } - } else { - // Ve FULL módu první řádek - if (fileTable.getRowCount() > 0) { - fileTable.setRowSelectionInterval(0, 0); - fileTable.scrollRectToVisible(fileTable.getCellRect(0, 0, true)); - } - } - e.consume(); - } else if (e.getKeyCode() == java.awt.event.KeyEvent.VK_END) { - // Skok na poslední položku - if (viewMode == ViewMode.BRIEF) { - // V BRIEF módu skočit na poslední položku - int totalItems = tableModel.items.size(); - if (totalItems > 0) { - int lastIndex = totalItems - 1; - int lastCol = lastIndex / tableModel.briefRowsPerColumn; - int lastRow = lastIndex % tableModel.briefRowsPerColumn; - briefCurrentColumn = lastCol; - fileTable.setRowSelectionInterval(lastRow, lastRow); - fileTable.scrollRectToVisible(fileTable.getCellRect(lastRow, lastCol, true)); - fileTable.repaint(); - } - } else { - // Ve FULL módu poslední řádek - int lastRow = fileTable.getRowCount() - 1; - if (lastRow >= 0) { - fileTable.setRowSelectionInterval(lastRow, lastRow); - fileTable.scrollRectToVisible(fileTable.getCellRect(lastRow, 0, true)); - } - } - e.consume(); - } else if (e.getKeyCode() == java.awt.event.KeyEvent.VK_PAGE_UP) { - // Page Up - posun o počet viditelných řádků nahoru - if (viewMode == ViewMode.BRIEF) { - handleBriefPageNavigation(true); - } else { - // Ve FULL módu posun o počet viditelných řádků - Rectangle visible = fileTable.getVisibleRect(); - int rowHeight = fileTable.getRowHeight(); - int visibleRows = visible.height / rowHeight; - - int currentRow = fileTable.getSelectedRow(); - int newRow = Math.max(0, currentRow - visibleRows); - - fileTable.setRowSelectionInterval(newRow, newRow); - fileTable.scrollRectToVisible(fileTable.getCellRect(newRow, 0, true)); - } - e.consume(); - } else if (e.getKeyCode() == java.awt.event.KeyEvent.VK_PAGE_DOWN) { - // Page Down - posun o počet viditelných řádků dolů - if (viewMode == ViewMode.BRIEF) { - handleBriefPageNavigation(false); - } else { - // Ve FULL módu posun o počet viditelných řádků - Rectangle visible = fileTable.getVisibleRect(); - int rowHeight = fileTable.getRowHeight(); - int visibleRows = visible.height / rowHeight; - - int currentRow = fileTable.getSelectedRow(); - int newRow = Math.min(fileTable.getRowCount() - 1, currentRow + visibleRows); - - fileTable.setRowSelectionInterval(newRow, newRow); - fileTable.scrollRectToVisible(fileTable.getCellRect(newRow, 0, true)); - } - e.consume(); - } else if (e.getKeyCode() == java.awt.event.KeyEvent.VK_INSERT) { - // Insert - přepnout označení aktuálního řádku a posunout se dolů - toggleSelectionAndMoveDown(); - e.consume(); - } else if (viewMode == ViewMode.BRIEF && - (e.getKeyCode() == java.awt.event.KeyEvent.VK_DOWN || - e.getKeyCode() == java.awt.event.KeyEvent.VK_UP)) { - // V BRIEF módu vlastní navigace - pohyb mezi položkami, ne řádky - handleBriefNavigation(e.getKeyCode() == java.awt.event.KeyEvent.VK_DOWN); - e.consume(); - } else if (viewMode == ViewMode.BRIEF && - (e.getKeyCode() == java.awt.event.KeyEvent.VK_LEFT || - e.getKeyCode() == java.awt.event.KeyEvent.VK_RIGHT)) { - // V BRIEF módu šipky vlevo/vpravo - pohyb mezi sloupci - handleBriefHorizontalNavigation(e.getKeyCode() == java.awt.event.KeyEvent.VK_RIGHT); - e.consume(); - } - } - }); - - JScrollPane scrollPane = new JScrollPane(fileTable); - add(scrollPane, BorderLayout.CENTER); - - // Přidat listener pro změnu velikosti - potřebné pro přepočet BRIEF layoutu - scrollPane.addComponentListener(new java.awt.event.ComponentAdapter() { - @Override - public void componentResized(java.awt.event.ComponentEvent e) { - if (viewMode == ViewMode.BRIEF) { - // Zapamatovat si aktuálně vybranou položku před přepočtem - int selectedRow = fileTable.getSelectedRow(); - FileItem selectedItem = null; - if (selectedRow >= 0) { - selectedItem = tableModel.getItemFromBriefLayout(selectedRow, briefCurrentColumn); - } - - final FileItem itemToReselect = selectedItem; - - SwingUtilities.invokeLater(() -> { - tableModel.calculateBriefLayout(); - tableModel.fireTableStructureChanged(); - updateColumnRenderers(); - updateColumnWidths(); - - // Znovu najít a vybrat položku po přepočtu layoutu - if (itemToReselect != null) { - selectItemByName(itemToReselect.getName()); - } - }); - } - } - }); - - // Stavový řádek - statusLabel = new JLabel(" "); - statusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); - add(statusLabel, BorderLayout.SOUTH); - } - - /** - * Načte obsah adresáře - */ - public void loadDirectory(File directory) { - loadDirectory(directory, true); - } - - /** - * Načte obsah adresáře - * @param directory adresář k načtení - * @param autoSelectFirst true pokud má být automaticky vybrán první řádek - */ - private void loadDirectory(File directory, boolean autoSelectFirst) { - if (directory == null || !directory.isDirectory()) { - return; - } - - this.currentDirectory = directory; - pathField.setText(directory.getAbsolutePath()); - - // Resetovat aktuální sloupec v BRIEF módu na první sloupec - briefCurrentColumn = 0; - - File[] files = directory.listFiles(); - List items = new ArrayList<>(); - - // Vždy přidat položku pro nadřazený adresář (pokud existuje) - File parent = directory.getParentFile(); - if (parent != null) { - items.add(new FileItem(parent) { - @Override - public String getName() { - return ".."; - } - }); - } - - if (files != null && files.length > 0) { - // Seřadit: nejdříve adresáře, pak soubory, abecedně - Arrays.sort(files, Comparator - .comparing((File f) -> !f.isDirectory()) - .thenComparing(File::getName, String.CASE_INSENSITIVE_ORDER)); - - for (File file : files) { - items.add(new FileItem(file)); - } - } - - tableModel.setItems(items); - - // V BRIEF módu zajistit správné překreslení po načtení - if (viewMode == ViewMode.BRIEF) { - boolean selectFirst = autoSelectFirst; - SwingUtilities.invokeLater(() -> { - tableModel.calculateBriefLayout(); - tableModel.fireTableStructureChanged(); - updateColumnRenderers(); - updateColumnWidths(); - fileTable.revalidate(); - fileTable.repaint(); - - // Automaticky vybrat první řádek po přepočtu layoutu - if (selectFirst && fileTable.getRowCount() > 0) { - fileTable.setRowSelectionInterval(0, 0); - fileTable.scrollRectToVisible(fileTable.getCellRect(0, 0, true)); - } - }); - } else { - // Automaticky vybrat první řádek (pokud je to požadováno) - if (autoSelectFirst && fileTable.getRowCount() > 0) { - fileTable.setRowSelectionInterval(0, 0); - } - } - - updateStatus(); - } - - /** - * Otevře vybranou položku (adresář) - */ - private void openSelectedItem() { - int selectedRow = fileTable.getSelectedRow(); - if (selectedRow >= 0) { - FileItem item; - if (viewMode == ViewMode.BRIEF) { - item = tableModel.getItemFromBriefLayout(selectedRow, briefCurrentColumn); - } else { - item = tableModel.getItem(selectedRow); - } - - if (item == null) { - return; - } - - if (item.getName().equals("..")) { - // Pro nadřazený adresář použít navigateUp() pro správné chování - navigateUp(); - } else if (item.isDirectory()) { - // Před vstupem do adresáře si zapamatovat aktuální sloupec (pro návrat zpět) - briefColumnBeforeEnter = briefCurrentColumn; - loadDirectory(item.getFile()); - } - } - } - - /** - * Přejde do nadřazeného adresáře - */ - public void navigateUp() { - File parent = currentDirectory.getParentFile(); - if (parent != null) { - // Zapamatovat si aktuální adresář - String previousDirName = currentDirectory.getName(); - - // Načíst nadřazený adresář bez auto-výběru prvního řádku - loadDirectory(parent, false); - - // Stejný mechanismus jako při resize - přepočítat layout a znovu vybrat položku - if (viewMode == ViewMode.BRIEF) { - SwingUtilities.invokeLater(() -> { - tableModel.calculateBriefLayout(); - tableModel.fireTableStructureChanged(); - updateColumnRenderers(); - updateColumnWidths(); - - // Znovu najít a vybrat položku po přepočtu layoutu - selectItemByName(previousDirName); - }); - } else { - SwingUtilities.invokeLater(() -> selectItemByName(previousDirName)); - } - } - } - - /** - * Vybere položku v tabulce podle názvu - */ - private void selectItemByName(String name) { - // V BRIEF režimu musíme hledat přes všechny položky - if (viewMode == ViewMode.BRIEF) { - for (int row = 0; row < tableModel.getRowCount(); row++) { - for (int col = 0; col < tableModel.getColumnCount(); col++) { - FileItem item = tableModel.getItemFromBriefLayout(row, col); - if (item != null && item.getName().equals(name)) { - // Obnovit uložený sloupec - briefCurrentColumn = col; - fileTable.setRowSelectionInterval(row, row); - fileTable.scrollRectToVisible(fileTable.getCellRect(row, col, true)); - fileTable.repaint(); - return; - } - } - } - } else { - for (int i = 0; i < tableModel.getRowCount(); i++) { - FileItem item = tableModel.getItem(i); - if (item != null && item.getName().equals(name)) { - fileTable.setRowSelectionInterval(i, i); - fileTable.scrollRectToVisible(fileTable.getCellRect(i, 0, true)); - break; - } - } - } - } - - /** - * Přepne označení aktuálního řádku a posune se o řádek dolů - */ - private void toggleSelectionAndMoveDown() { - int currentRow = fileTable.getSelectedRow(); - if (currentRow < 0) { - return; - } - - // Přepnout označení v FileItem - FileItem item; - if (viewMode == ViewMode.BRIEF) { - item = tableModel.getItemFromBriefLayout(currentRow, briefCurrentColumn); - } else { - item = tableModel.getItem(currentRow); - } - - if (item != null) { - item.toggleMarked(); - - // Překreslit tabulku - fileTable.repaint(); - } - - // Posunout se na další položku - if (viewMode == ViewMode.BRIEF) { - handleBriefNavigation(true); - } else { - // Posunout se na další řádek ve FULL módu - int nextRow = currentRow + 1; - if (nextRow < fileTable.getRowCount()) { - fileTable.setRowSelectionInterval(nextRow, nextRow); - fileTable.scrollRectToVisible(fileTable.getCellRect(nextRow, 0, true)); - } - } - - updateStatus(); - } - - /** - * Obsluha navigace v BRIEF módu - pohybuje se mezi položkami správným směrem - * @param down true pro pohyb dolů, false pro pohyb nahoru - */ - private void handleBriefNavigation(boolean down) { - int currentRow = fileTable.getSelectedRow(); - - if (currentRow < 0) { - return; - } - - // Vypočítat aktuální index položky - použít uložený sloupec - int currentIndex = briefCurrentColumn * tableModel.briefRowsPerColumn + currentRow; - int totalItems = tableModel.items.size(); - - if (currentIndex < 0 || currentIndex >= totalItems) { - return; - } - - // Vypočítat nový index - int newIndex = down ? currentIndex + 1 : currentIndex - 1; - - // Kontrola hranic - if (newIndex < 0 || newIndex >= totalItems) { - return; - } - - // Převést nový index na řádek a sloupec - int newCol = newIndex / tableModel.briefRowsPerColumn; - int newRow = newIndex % tableModel.briefRowsPerColumn; - - // Uložit aktuální sloupec - briefCurrentColumn = newCol; - - // Přesunout výběr na nový řádek - fileTable.setRowSelectionInterval(newRow, newRow); - fileTable.scrollRectToVisible(fileTable.getCellRect(newRow, briefCurrentColumn, true)); - - // Překreslit pro aktualizaci zvýraznění - fileTable.repaint(); - - updateStatus(); - } - - /** - * Obsluha horizontální navigace v BRIEF módu - pohyb mezi sloupci - * @param right true pro pohyb doprava, false pro pohyb doleva - */ - private void handleBriefHorizontalNavigation(boolean right) { - int currentRow = fileTable.getSelectedRow(); - - if (currentRow < 0) { - return; - } - - // Spočítat nový sloupec - int newColumn = right ? briefCurrentColumn + 1 : briefCurrentColumn - 1; - - // Zkontrolovat, zda nový sloupec existuje - if (newColumn < 0 || newColumn >= tableModel.getColumnCount()) { - return; - } - - // Zkontrolovat, zda existuje položka na této pozici - FileItem item = tableModel.getItemFromBriefLayout(currentRow, newColumn); - int targetRow = currentRow; - - // Pokud na aktuálním řádku není položka, najít nejbližší položku směrem nahoru - if (item == null) { - // Hledat směrem nahoru (nižší řádky) - for (int row = currentRow - 1; row >= 0; row--) { - item = tableModel.getItemFromBriefLayout(row, newColumn); - if (item != null) { - targetRow = row; - break; - } - } - - // Pokud jsme nenašli položku ani směrem nahoru, nepřesouvat se - if (item == null) { - return; - } - } - - // Přesunout se na nový sloupec a řádek - briefCurrentColumn = newColumn; - fileTable.setRowSelectionInterval(targetRow, targetRow); - - // Zajistit viditelnost - fileTable.scrollRectToVisible(fileTable.getCellRect(targetRow, briefCurrentColumn, true)); - fileTable.repaint(); - - updateStatus(); - } - - /** - * Obsluha Page Up/Down v BRIEF módu - posun o počet viditelných položek - * @param pageUp true pro Page Up, false pro Page Down - */ - private void handleBriefPageNavigation(boolean pageUp) { - int currentRow = fileTable.getSelectedRow(); - if (currentRow < 0) { - return; - } - - // Vypočítat aktuální index položky - int currentIndex = briefCurrentColumn * tableModel.briefRowsPerColumn + currentRow; - - // Vypočítat počet viditelných položek - Rectangle visible = fileTable.getVisibleRect(); - int rowHeight = fileTable.getRowHeight(); - int visibleRows = visible.height / rowHeight; - - // Počet viditelných sloupců - int visibleColumns = tableModel.getColumnCount(); - - // Celkový počet viditelných položek - int visibleItems = visibleRows * visibleColumns; - - // Vypočítat nový index - int newIndex; - if (pageUp) { - newIndex = Math.max(0, currentIndex - visibleItems); - } else { - newIndex = Math.min(tableModel.items.size() - 1, currentIndex + visibleItems); - } - - // Převést nový index na řádek a sloupec - int newCol = newIndex / tableModel.briefRowsPerColumn; - int newRow = newIndex % tableModel.briefRowsPerColumn; - - // Uložit aktuální sloupec - briefCurrentColumn = newCol; - - // Přesunout výběr a zajistit viditelnost - fileTable.setRowSelectionInterval(newRow, newRow); - Rectangle cellRect = fileTable.getCellRect(newRow, newCol, true); - fileTable.scrollRectToVisible(cellRect); - fileTable.repaint(); - - updateStatus(); - } - - /** - * Obnoví zobrazení aktuálního adresáře - */ - public void refresh() { - loadDirectory(currentDirectory); - } - - /** - * Nastaví režim zobrazení - */ - public void setViewMode(ViewMode mode) { - if (this.viewMode != mode) { - // Zapamatovat si aktuální vybranou položku - String selectedItemName = null; - int selectedRow = fileTable.getSelectedRow(); - if (selectedRow >= 0) { - FileItem item = null; - if (this.viewMode == ViewMode.BRIEF) { - item = tableModel.getItemFromBriefLayout(selectedRow, briefCurrentColumn); - } else { - item = tableModel.getItem(selectedRow); - } - if (item != null) { - selectedItemName = item.getName(); - } - } - - this.viewMode = mode; - - final String itemToSelect = selectedItemName; - - // V BRIEF režimu musíme počkat, až bude panel zobrazen - SwingUtilities.invokeLater(() -> { - tableModel.updateViewMode(mode); - - // Resetovat aktuální sloupec při přepnutí režimu - briefCurrentColumn = 0; - - // Aktualizovat renderery podle režimu - updateColumnRenderers(); - - // Aktualizovat šířky sloupců - updateColumnWidths(); - - fileTable.revalidate(); - fileTable.repaint(); - - // Znovu vybrat původní položku - if (itemToSelect != null) { - selectItemByName(itemToSelect); - } else if (fileTable.getRowCount() > 0) { - fileTable.setRowSelectionInterval(0, 0); - } - - // Vrátit focus na tabulku - fileTable.requestFocusInWindow(); - }); - } - } - - public ViewMode getViewMode() { - return viewMode; - } - - /** - * Aktualizuje renderery sloupců podle režimu zobrazení - */ - private void updateColumnRenderers() { - int columnCount = viewMode == ViewMode.FULL ? 3 : tableModel.getColumnCount(); - - // Zkontrolovat, že máme správný počet sloupců v modelu - int actualColumnCount = fileTable.getColumnModel().getColumnCount(); - if (actualColumnCount < columnCount) { - // Model ještě nemá správný počet sloupců, zkusíme později - return; - } - - for (int i = 0; i < columnCount; i++) { - final int colIndex = i; - DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() { - @Override - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) { - super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - // Získat položku podle aktuálního režimu - FileItem item = null; - if (viewMode == ViewMode.BRIEF) { - item = tableModel.getItemFromBriefLayout(row, column); - } else { - item = tableModel.getItem(row); - } - - if (item == null) { - // Prázdná buňka v BRIEF režimu - setBackground(FilePanel.this.getBackground()); - setText(""); - setIcon(null); - return this; - } - - boolean isMarked = item.isMarked(); - - // V BRIEF módu kontrolovat konkrétní buňku, ne celý řádek - boolean isCurrentCell = false; - if (viewMode == ViewMode.BRIEF) { - isCurrentCell = isSelected && column == briefCurrentColumn; - } else { - isCurrentCell = isSelected; - } - - // Nejdřív nastavit pozadí podle aktuálního stavu - if (isCurrentCell && table.hasFocus()) { - setBackground(new Color(184, 207, 229)); - } else if (isCurrentCell) { - setBackground(new Color(220, 220, 220)); - } else { - // Použít background panelu - setBackground(FilePanel.this.getBackground()); - } - - // Pak nastavit barvu textu a font podle označení - if (isMarked) { - setForeground(new Color(204, 153, 0)); - setFont(new Font("Monospaced", Font.BOLD, 12)); - } else { - setForeground(Color.BLACK); - setFont(new Font("Monospaced", Font.PLAIN, 12)); - } - - // Nastavit ikonu pro první sloupec (název) - if ((viewMode == ViewMode.BRIEF) || (viewMode == ViewMode.FULL && colIndex == 0)) { - Icon icon = FileSystemView.getFileSystemView().getSystemIcon(item.getFile()); - setIcon(icon); - } else { - setIcon(null); - } - - // Zbavit se borderu - setBorder(null); - - return this; - } - }; - - if (viewMode == ViewMode.FULL && colIndex == 1) { - renderer.setHorizontalAlignment(SwingConstants.RIGHT); - } - - fileTable.getColumnModel().getColumn(i).setCellRenderer(renderer); - } - } - - /** - * Aktualizuje šířky sloupců podle režimu zobrazení - */ - private void updateColumnWidths() { - // Zkontrolovat, že máme sloupce v modelu - if (fileTable.getColumnModel().getColumnCount() == 0) { - return; - } - - if (viewMode == ViewMode.FULL) { - if (fileTable.getColumnModel().getColumnCount() >= 3) { - fileTable.getColumnModel().getColumn(0).setPreferredWidth(300); - fileTable.getColumnModel().getColumn(1).setPreferredWidth(100); - fileTable.getColumnModel().getColumn(2).setPreferredWidth(150); - } - } else { - // V BRIEF režimu vypočítat šířku podle nejdelšího názvu - int columnCount = fileTable.getColumnCount(); - if (columnCount > 0 && fileTable.getColumnModel().getColumnCount() == columnCount) { - // Najít nejdelší název - String longestName = ""; - for (FileItem item : tableModel.items) { - String name = item.getName(); - if (name.length() > longestName.length()) { - longestName = name; - } - } - - // Vypočítat šířku sloupce podle nejdelšího názvu - // Použít FontMetrics pro přesnější výpočet - java.awt.FontMetrics fm = fileTable.getFontMetrics(fileTable.getFont()); - int columnWidth = fm.stringWidth(longestName.isEmpty() ? "WWWWWWWWWW" : longestName) + 30; // +30 pro padding - - // Nastavit všem sloupcům stejnou preferovanou šířku - for (int i = 0; i < columnCount; i++) { - fileTable.getColumnModel().getColumn(i).setPreferredWidth(columnWidth); - } - } - } - } - - /** - * Aktualizuje stavový řádek - */ - private void updateStatus() { - // Spočítat označené položky - long totalSize = 0; - int fileCount = 0; - int dirCount = 0; - int markedCount = 0; - - // V BRIEF režimu musíme projít všechny items, ne podle řádků tabulky - if (viewMode == ViewMode.BRIEF) { - for (int i = 0; i < tableModel.items.size(); i++) { - FileItem item = tableModel.items.get(i); - if (item.isMarked() && !item.getName().equals("..")) { - markedCount++; - if (item.isDirectory()) { - dirCount++; - } else { - fileCount++; - totalSize += item.getSize(); - } - } - } - } else { - // V FULL režimu procházíme podle řádků - for (int i = 0; i < tableModel.getRowCount(); i++) { - FileItem item = tableModel.getItem(i); - if (item != null && item.isMarked() && !item.getName().equals("..")) { - markedCount++; - if (item.isDirectory()) { - dirCount++; - } else { - fileCount++; - totalSize += item.getSize(); - } - } - } - } - - if (markedCount > 0) { - // Zobrazit souhrn označených položek - statusLabel.setText(String.format(" Označeno: %d souborů, %d adresářů (%s)", - fileCount, dirCount, formatSize(totalSize))); - } else { - // Zobrazit informace o aktuálně vybrané položce - int selectedRow = fileTable.getSelectedRow(); - if (selectedRow >= 0) { - FileItem item = null; - if (viewMode == ViewMode.BRIEF) { - item = tableModel.getItemFromBriefLayout(selectedRow, briefCurrentColumn); - } else { - item = tableModel.getItem(selectedRow); - } - - if (item != null && !item.getName().equals("..")) { - if (item.isDirectory()) { - // Pro adresář zobrazit název a datum - statusLabel.setText(String.format(" [ADRESÁŘ] %s | %s", - item.getName(), - item.getFormattedDate())); - } else { - // Pro soubor zobrazit název, velikost a datum - statusLabel.setText(String.format(" %s | %s | %s", - item.getName(), - formatSize(item.getSize()), - item.getFormattedDate())); - } - } else { - // Zobrazit celkový počet položek - statusLabel.setText(String.format(" Položek: %d", tableModel.items.size())); - } - } else { - // Zobrazit celkový počet položek - statusLabel.setText(String.format(" Položek: %d", tableModel.items.size())); - } - } - } - - private String formatSize(long size) { - if (size < 1024) { - return size + " B"; - } else if (size < 1024 * 1024) { - return String.format("%.1f KB", size / 1024.0); - } else if (size < 1024 * 1024 * 1024) { - return String.format("%.1f MB", size / (1024.0 * 1024.0)); - } else { - return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0)); - } - } - - // Gettery - - public File getCurrentDirectory() { - return currentDirectory; - } - - public List getSelectedItems() { - List selected = new ArrayList<>(); - boolean hasMarkedItems = false; - - // Projít všechny položky a najít označené - musíme procházet přes items, ne přes řádky - for (FileItem item : tableModel.items) { - if (item.isMarked() && !item.getName().equals("..")) { - selected.add(item); - hasMarkedItems = true; - } - } - - // Pokud není nic označeno, vrátit aktuální vybraný řádek - if (!hasMarkedItems) { - int currentRow = fileTable.getSelectedRow(); - if (currentRow >= 0) { - FileItem item; - if (viewMode == ViewMode.BRIEF) { - item = tableModel.getItemFromBriefLayout(currentRow, briefCurrentColumn); - } else { - item = tableModel.getItem(currentRow); - } - - if (item != null && !item.getName().equals("..")) { - selected.add(item); - } - } - } - - return selected; - } - - public JTable getFileTable() { - return fileTable; - } - - /** - * Model tabulky pro soubory - */ - private class FileTableModel extends AbstractTableModel { - - private final String[] fullColumnNames = {"Název", "Velikost", "Datum změny"}; - private List items = new ArrayList<>(); - private ViewMode currentViewMode = ViewMode.FULL; - private int briefColumns = 1; // Počet sloupců v BRIEF režimu - private int briefRowsPerColumn = 20; // Počet řádků na sloupec v BRIEF režimu - - public void setItems(List items) { - this.items = items; - if (currentViewMode == ViewMode.BRIEF) { - calculateBriefLayout(); - } - fireTableDataChanged(); - } - - public void updateViewMode(ViewMode mode) { - this.currentViewMode = mode; - if (mode == ViewMode.BRIEF) { - calculateBriefLayout(); - } - fireTableStructureChanged(); - } - - /** - * Vypočítá layout pro BRIEF režim - */ - public void calculateBriefLayout() { - if (items.size() == 0) { - briefColumns = 1; - briefRowsPerColumn = 1; - return; - } - - // Zjistit dostupnou výšku pro tabulku - int availableHeight = 0; - if (fileTable.getParent() != null) { - availableHeight = fileTable.getParent().getHeight(); - } - - if (availableHeight <= 0) { - availableHeight = 400; // Výchozí hodnota - } - - int rowHeight = fileTable.getRowHeight(); - if (rowHeight <= 0) { - rowHeight = 20; - } - - // Vypočítat maximální počet řádků na sloupec - briefRowsPerColumn = Math.max(1, availableHeight / rowHeight); - - // Vypočítat potřebný počet sloupců - briefColumns = (int) Math.ceil((double) items.size() / briefRowsPerColumn); - briefColumns = Math.max(1, briefColumns); - } - - public FileItem getItem(int row) { - if (currentViewMode == ViewMode.BRIEF) { - return getItemFromBriefLayout(row, 0); - } - if (row >= 0 && row < items.size()) { - return items.get(row); - } - return null; - } - - /** - * Získá položku z BRIEF layoutu podle řádku a sloupce - */ - public FileItem getItemFromBriefLayout(int row, int column) { - int index = column * briefRowsPerColumn + row; - if (index >= 0 && index < items.size()) { - return items.get(index); - } - return null; - } - - @Override - public int getRowCount() { - if (currentViewMode == ViewMode.BRIEF) { - return briefRowsPerColumn; - } - return items.size(); - } - - @Override - public int getColumnCount() { - if (currentViewMode == ViewMode.FULL) { - return fullColumnNames.length; - } else { - return briefColumns; - } - } - - @Override - public String getColumnName(int column) { - if (currentViewMode == ViewMode.FULL) { - return fullColumnNames[column]; - } else { - return ""; // V BRIEF režimu žádné názvy sloupců - } - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - if (currentViewMode == ViewMode.BRIEF) { - FileItem item = getItemFromBriefLayout(rowIndex, columnIndex); - return item != null ? item.getName() : ""; - } - - if (rowIndex >= items.size()) { - return null; - } - - FileItem item = items.get(rowIndex); - switch (columnIndex) { - case 0: - return item.getName(); - case 1: - return item.getFormattedSize(); - case 2: - return item.getFormattedDate(); - default: - return null; - } - } - } -} diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index 7039a1f..8844f6c 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -1387,6 +1387,35 @@ public class FilePanelTab extends JPanel { public boolean isCancelled() { return progressDialog.isCancelled(); } + @Override + public FileOperations.ErrorResponse onError(File file, Exception e) { + final FileOperations.ErrorResponse[] result = new FileOperations.ErrorResponse[1]; + try { + SwingUtilities.invokeAndWait(() -> { + Object[] options = {"Skip", "Retry", "Abort"}; + int n = JOptionPane.showOptionDialog(progressDialog, + "Error deleting file: " + file.getName() + "\n" + e.getMessage(), + "Error", + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + options, + options[0]); + + switch (n) { + case 0: result[0] = FileOperations.ErrorResponse.SKIP; break; + case 1: result[0] = FileOperations.ErrorResponse.RETRY; break; + default: + result[0] = FileOperations.ErrorResponse.ABORT; + progressDialog.cancel(); + break; + } + }); + } catch (Exception ex) { + result[0] = FileOperations.ErrorResponse.ABORT; + } + return result[0]; + } }); SwingUtilities.invokeLater(() -> { progressDialog.dispose(); @@ -1781,14 +1810,44 @@ public class FilePanelTab extends JPanel { } return result[0]; } - }; - if (action == ClipboardService.ClipboardAction.CUT) { - FileOperations.move(itemsToPaste, targetDir, callback); - } else { - FileOperations.copy(itemsToPaste, targetDir, callback); + @Override + public FileOperations.ErrorResponse onError(File file, Exception e) { + final FileOperations.ErrorResponse[] result = new FileOperations.ErrorResponse[1]; + try { + SwingUtilities.invokeAndWait(() -> { + Object[] options = {"Skip", "Retry", "Abort"}; + int n = JOptionPane.showOptionDialog(progressDialog, + "Error operating on file: " + file.getName() + "\n" + e.getMessage(), + "Error", + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + options, + options[0]); + + switch (n) { + case 0: result[0] = FileOperations.ErrorResponse.SKIP; break; + case 1: result[0] = FileOperations.ErrorResponse.RETRY; break; + default: + result[0] = FileOperations.ErrorResponse.ABORT; + progressDialog.cancel(); + break; + } + }); + } catch (Exception ex) { + result[0] = FileOperations.ErrorResponse.ABORT; } - SwingUtilities.invokeLater(() -> { + return result[0]; + } + }; + + if (action == ClipboardService.ClipboardAction.CUT) { + FileOperations.move(itemsToPaste, targetDir, callback); + } else { + FileOperations.copy(itemsToPaste, targetDir, callback); + } + SwingUtilities.invokeLater(() -> { progressDialog.dispose(); loadDirectory(targetDir, false); if (!itemsToPaste.isEmpty()) { diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index f470684..c2901e5 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -1267,15 +1267,15 @@ public class MainWindow extends JFrame { FilePanel targetPanel = (activePanel == leftPanel) ? rightPanel : leftPanel; File targetDir = targetPanel.getCurrentDirectory(); - int result = JOptionPane.showConfirmDialog(this, + int result = showConfirmWithBackground( String.format("Copy %d items to:\n%s", selectedItems.size(), targetDir.getAbsolutePath()), - "Copy", - JOptionPane.OK_CANCEL_OPTION); + "Copy"); - if (result == JOptionPane.OK_OPTION) { + if (result == 0 || result == 1) { + boolean modal = (result == 0); performFileOperation((callback) -> { FileOperations.copy(selectedItems, targetDir, callback); - }, "Copy completed", true, targetPanel); + }, "Copy completed", true, modal, targetPanel); } else { if (activePanel != null && activePanel.getFileTable() != null) { activePanel.getFileTable().requestFocusInWindow(); @@ -1300,21 +1300,43 @@ public class MainWindow extends JFrame { FilePanel targetPanel = (activePanel == leftPanel) ? rightPanel : leftPanel; File targetDir = targetPanel.getCurrentDirectory(); - int result = JOptionPane.showConfirmDialog(this, + int result = showConfirmWithBackground( String.format("Move %d items to:\n%s", selectedItems.size(), targetDir.getAbsolutePath()), - "Move", - JOptionPane.OK_CANCEL_OPTION); + "Move"); - if (result == JOptionPane.OK_OPTION) { + if (result == 0 || result == 1) { + boolean modal = (result == 0); performFileOperation((callback) -> { FileOperations.move(selectedItems, targetDir, callback); - }, "Move completed", false, activePanel, targetPanel); + }, "Move completed", false, modal, activePanel, targetPanel); } else { if (activePanel != null && activePanel.getFileTable() != null) { activePanel.getFileTable().requestFocusInWindow(); } } } + + private int showConfirmWithBackground(String message, String title) { + Object[] options = {"OK", "Background (F2)", "Cancel"}; + JOptionPane pane = new JOptionPane(message, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[0]); + JDialog dialog = pane.createDialog(this, title); + + pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "background"); + pane.getActionMap().put("background", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + pane.setValue(options[1]); + dialog.dispose(); + } + }); + + dialog.setVisible(true); + Object selectedValue = pane.getValue(); + if (selectedValue == null) return 2; // Cancel + if (selectedValue.equals(options[0])) return 0; // OK + if (selectedValue.equals(options[1])) return 1; // Background + return 2; // Cancel + } /** * Delete selected files @@ -1960,14 +1982,25 @@ public class MainWindow extends JFrame { * Execute file operation with error handling */ private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, FilePanel... panelsToRefresh) { - performFileOperation(operation, successMessage, showBytes, null, panelsToRefresh); + performFileOperation(operation, successMessage, showBytes, true, null, panelsToRefresh); + } + + private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, boolean modal, FilePanel... panelsToRefresh) { + performFileOperation(operation, successMessage, showBytes, modal, null, panelsToRefresh); } /** * Execute file operation with error handling and a task to run after completion and refresh. */ private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, Runnable postTask, FilePanel... panelsToRefresh) { - ProgressDialog progressDialog = new ProgressDialog(this, "File Operation"); + performFileOperation(operation, successMessage, showBytes, true, postTask, panelsToRefresh); + } + + /** + * Execute file operation with error handling and a task to run after completion and refresh. + */ + private void performFileOperation(FileOperation operation, String successMessage, boolean showBytes, boolean modal, Runnable postTask, FilePanel... panelsToRefresh) { + ProgressDialog progressDialog = new ProgressDialog(this, "File Operation", modal); progressDialog.setDisplayAsBytes(showBytes); FileOperations.ProgressCallback callback = new FileOperations.ProgressCallback() { @@ -2012,6 +2045,36 @@ public class MainWindow extends JFrame { } return result[0]; } + + @Override + public FileOperations.ErrorResponse onError(File file, Exception e) { + final FileOperations.ErrorResponse[] result = new FileOperations.ErrorResponse[1]; + try { + SwingUtilities.invokeAndWait(() -> { + Object[] options = {"Skip", "Retry", "Abort"}; + int n = JOptionPane.showOptionDialog(progressDialog, + "Error operating on file: " + file.getName() + "\n" + e.getMessage(), + "Error", + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + options, + options[0]); + + switch (n) { + case 0: result[0] = FileOperations.ErrorResponse.SKIP; break; + case 1: result[0] = FileOperations.ErrorResponse.RETRY; break; + default: + result[0] = FileOperations.ErrorResponse.ABORT; + progressDialog.cancel(); + break; + } + }); + } catch (Exception ex) { + result[0] = FileOperations.ErrorResponse.ABORT; + } + return result[0]; + } }; // Run operation in a background thread diff --git a/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java b/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java index d715fe7..b5ed9c8 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java +++ b/src/main/java/cz/kamma/kfmanager/ui/ProgressDialog.java @@ -17,7 +17,11 @@ public class ProgressDialog extends JDialog { private long startTime = -1; public ProgressDialog(Frame owner, String title) { - super(owner, title, true); + this(owner, title, true); + } + + public ProgressDialog(Frame owner, String title, boolean modal) { + super(owner, title, modal); setLayout(new BorderLayout(10, 10)); ((JPanel)getContentPane()).setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));