diff --git a/pom.xml b/pom.xml index 3d719c0..e137126 100644 --- a/pom.xml +++ b/pom.xml @@ -97,5 +97,15 @@ junrar 7.4.1 + + com.formdev + flatlaf + 3.5.1 + + + com.formdev + flatlaf-extras + 3.5.1 + diff --git a/src/main/java/cz/kamma/kfmanager/MainApp.java b/src/main/java/cz/kamma/kfmanager/MainApp.java index aa9ce4c..1a51a98 100644 --- a/src/main/java/cz/kamma/kfmanager/MainApp.java +++ b/src/main/java/cz/kamma/kfmanager/MainApp.java @@ -1,10 +1,14 @@ package cz.kamma.kfmanager; import cz.kamma.kfmanager.ui.MainWindow; +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatLightLaf; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; +import java.io.BufferedReader; +import java.io.InputStreamReader; /** * Main application class for KF File Manager @@ -17,11 +21,26 @@ public class MainApp { // Set application name for X11/Wayland WM_CLASS System.setProperty("awt.app.name", "cz-kamma-kfmanager-MainApp"); - // Set look and feel to system default + // Use FlatLaf for modern look and better system theme support try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + if (System.getProperty("os.name").toLowerCase().contains("win")) { + // On Windows, use FlatLaf and detect system theme (light/dark) + if (isWindowsDarkMode()) { + FlatDarkLaf.setup(); + } else { + FlatLightLaf.setup(); + } + } else { + // On Linux and macOS, use FlatLaf as a better-looking alternative + // to the default system L&F + FlatLightLaf.setup(); + } } catch (Exception e) { - e.printStackTrace(); + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ex) { + ex.printStackTrace(); + } } // Enable arrow key navigation in JOptionPane dialogs @@ -34,6 +53,22 @@ public class MainApp { }); } + private static boolean isWindowsDarkMode() { + try { + Process process = Runtime.getRuntime().exec("reg query \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\" /v AppsUseLightTheme"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("AppsUseLightTheme")) { + return line.contains("0x0"); + } + } + } catch (Exception e) { + // ignore and fallback to light + } + return false; + } + private static void setupGlobalKeyNavigation() { Toolkit.getDefaultToolkit().addAWTEventListener(event -> { if (event instanceof KeyEvent) { diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java index a584c25..42661ae 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java @@ -774,6 +774,13 @@ public class FilePanel extends JPanel { } } + public void navigateUp() { + FilePanelTab tab = getCurrentTab(); + if (tab != null) { + tab.navigateUp(); + } + } + public void showArchiveFile(File archive, String entryName) { FilePanelTab tab = getCurrentTab(); if (tab != null) { diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index fbc8803..95bb889 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -63,7 +63,7 @@ public class FilePanelTab extends JPanel { private Runnable onDirectoryChanged; private Runnable onSwitchPanelRequested; // Appearance customization - private Color selectionColor = new Color(184, 207, 229); + private Color selectionColor = null; private Color markedColor = new Color(204, 153, 0); // Sorting state for FULL mode header clicks private int sortColumn = -1; // 0=name,1=size,2=date @@ -1462,7 +1462,21 @@ public class FilePanelTab extends JPanel { cmdList.add(fullPath); } - new ProcessBuilder(cmdList).directory(file.getParentFile()).start(); + try { + new ProcessBuilder(cmdList).directory(file.getParentFile()).start(); + } catch (IOException ex) { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) { + // Try via cmd.exe for Windows shell commands/scripts + new ProcessBuilder("cmd", "/c", trimmedCmd + (hasPlaceholder ? "" : " \"" + fullPath + "\"")) + .directory(file.getParentFile()).start(); + } else if (osName.contains("linux") || osName.contains("mac")) { + new ProcessBuilder("sh", "-c", trimmedCmd + (hasPlaceholder ? "" : " '" + fullPath + "'")) + .directory(file.getParentFile()).start(); + } else { + throw ex; + } + } } catch (Exception ex) { try { JOptionPane.showMessageDialog(this, "Cannot execute command: " + ex.getMessage()); } catch (Exception ignore) {} } @@ -2368,6 +2382,15 @@ public class FilePanelTab extends JPanel { // Hide table header in BRIEF mode to save vertical space and match requirements if (fileTable.getTableHeader() != null) { fileTable.getTableHeader().setVisible(mode != ViewMode.BRIEF); + // In JScrollPane, manually managing columnHeader might be necessary to reclaim space + Component parent = fileTable.getParent(); + if (parent instanceof JViewport) { + Component scroll = parent.getParent(); + if (scroll instanceof JScrollPane) { + JScrollPane sp = (JScrollPane) scroll; + sp.setColumnHeaderView(mode == ViewMode.BRIEF ? null : fileTable.getTableHeader()); + } + } } briefCurrentColumn = 0; updateColumnRenderers(); @@ -2557,7 +2580,7 @@ public class FilePanelTab extends JPanel { // Show selection highlight even when the table doesn't have focus, but only // if this panel/tab is the active one. if (isCurrentCell && FilePanelTab.this.active) { - setBackground(selectionColor); + setBackground(selectionColor != null ? selectionColor : table.getSelectionBackground()); } else { setBackground(FilePanelTab.this.getBackground()); } @@ -2581,7 +2604,11 @@ public class FilePanelTab extends JPanel { setFont(baseFont.deriveFont(baseStyle)); // Automatically adjust foreground contrast if (isCurrentCell && FilePanelTab.this.active) { - setForeground(isDark(selectionColor) ? Color.WHITE : Color.BLACK); + if (selectionColor != null) { + setForeground(isDark(selectionColor) ? Color.WHITE : Color.BLACK); + } else { + setForeground(table.getSelectionForeground()); + } } else { setForeground(isDark(FilePanelTab.this.getBackground()) ? Color.WHITE : Color.BLACK); } diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index 338a35c..a9649c1 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -2138,8 +2138,21 @@ public class MainWindow extends JFrame { ProcessBuilder pb = null; if (osName.contains("win")) { - // Windows - pb = new ProcessBuilder("cmd.exe", "/c", "start", "cmd.exe"); + // Windows - try Windows Terminal first, then PowerShell, then cmd + try { + // Try to use Windows Terminal if available + new ProcessBuilder("wt.exe", "-d", currentDir.getAbsolutePath()).start(); + return; + } catch (Exception e1) { + try { + // Fallback to PowerShell + new ProcessBuilder("powershell.exe").directory(currentDir).start(); + return; + } catch (Exception e2) { + // Final fallback to cmd + pb = new ProcessBuilder("cmd.exe", "/c", "start", "cmd.exe"); + } + } } else if (osName.contains("mac")) { // macOS pb = new ProcessBuilder("open", "-a", "Terminal", currentDir.getAbsolutePath()); @@ -2191,15 +2204,46 @@ public class MainWindow extends JFrame { return; } + String trimmed = command.trim(); // Add to history - addCommandToHistory(command.trim()); + addCommandToHistory(trimmed); + // Handle internal commands like 'cd' + if (trimmed.toLowerCase().startsWith("cd ") || trimmed.toLowerCase().equals("cd..") || trimmed.equals("..")) { + String targetPath = null; + if (trimmed.equals("..") || trimmed.toLowerCase().equals("cd..")) { + targetPath = ".."; + } else { + targetPath = trimmed.substring(3).trim(); + if (targetPath.startsWith("\"") && targetPath.endsWith("\"")) { + targetPath = targetPath.substring(1, targetPath.length() - 1); + } + } + + if (activePanel != null) { + if ("..".equals(targetPath)) { + activePanel.navigateUp(); + } else { + File targetDir = new File(activePanel.getCurrentDirectory(), targetPath); + if (targetDir.exists() && targetDir.isDirectory()) { + activePanel.loadDirectory(targetDir); + } else { + // try absolute path + targetDir = new File(targetPath); + if (targetDir.exists() && targetDir.isDirectory()) { + activePanel.loadDirectory(targetDir); + } + } + } + } + } else { + // Execute natively for other commands + executeNative(trimmed, null); + } + // Final prompt update after command execution (path might have changed) updateCommandLinePrompt(); - // Execute natively, not via bash wrapper - executeNative(command.trim(), null); - // Clear after execution and return focus Component editorComp = commandLine.getEditor().getEditorComponent(); if (editorComp instanceof JTextField) { @@ -2224,7 +2268,20 @@ public class MainWindow extends JFrame { currentDir = new File(System.getProperty("user.home")); } + String osName = System.getProperty("os.name").toLowerCase(); try { + String trimmedCmd = command.trim(); + + // Special case for Windows terminal commands - launch in a new window + if (osName.contains("win")) { + String cmdLower = trimmedCmd.toLowerCase(); + if (cmdLower.equals("cmd") || cmdLower.equals("powershell") || + cmdLower.equals("pwsh") || cmdLower.equals("wt")) { + new ProcessBuilder("cmd", "/c", "start", cmdLower).directory(currentDir).start(); + return; + } + } + // Check if it's a file path that exists (and not a complex command) if (!command.contains(" ") || (command.startsWith("\"") && command.endsWith("\"") && !command.substring(1, command.length()-1).contains("\""))) { String path = command.startsWith("\"") ? command.substring(1, command.length()-1) : command; @@ -2247,10 +2304,11 @@ public class MainWindow extends JFrame { try { new ProcessBuilder(cmdList).directory(currentDir).start(); } catch (IOException ex) { - // Fallback for Linux/macOS: try via shell - String osName = System.getProperty("os.name").toLowerCase(); + // Fallback for different OS: try via shell if (osName.contains("linux") || osName.contains("mac")) { new ProcessBuilder("sh", "-c", command).directory(currentDir).start(); + } else if (osName.contains("win")) { + new ProcessBuilder("cmd", "/c", command).directory(currentDir).start(); } else { throw ex; }