added new functionality

This commit is contained in:
Radek Davidek 2025-11-17 14:45:58 +01:00
parent 7b07d98dab
commit e543320f97
7 changed files with 672 additions and 71 deletions

View File

@ -334,4 +334,109 @@ public class AppConfig {
frame.setExtendedState(Frame.MAXIMIZED_BOTH); frame.setExtendedState(Frame.MAXIMIZED_BOTH);
} }
} }
// --- Search dialog persistence ---
public int getSearchDialogX() {
return Integer.parseInt(properties.getProperty("searchDialog.x", "100"));
}
public void setSearchDialogX(int x) {
properties.setProperty("searchDialog.x", String.valueOf(x));
}
public int getSearchDialogY() {
return Integer.parseInt(properties.getProperty("searchDialog.y", "100"));
}
public void setSearchDialogY(int y) {
properties.setProperty("searchDialog.y", String.valueOf(y));
}
public int getSearchDialogWidth() {
return Integer.parseInt(properties.getProperty("searchDialog.width", "700"));
}
public void setSearchDialogWidth(int w) {
properties.setProperty("searchDialog.width", String.valueOf(w));
}
public int getSearchDialogHeight() {
return Integer.parseInt(properties.getProperty("searchDialog.height", "500"));
}
public void setSearchDialogHeight(int h) {
properties.setProperty("searchDialog.height", String.valueOf(h));
}
/** Save search dialog bounds */
public void saveSearchDialogState(Window win) {
if (win == null) return;
setSearchDialogX(win.getX());
setSearchDialogY(win.getY());
setSearchDialogWidth(win.getWidth());
setSearchDialogHeight(win.getHeight());
}
/** Restore search dialog bounds */
public void restoreSearchDialogState(Window win) {
if (win == null) return;
win.setLocation(getSearchDialogX(), getSearchDialogY());
win.setSize(getSearchDialogWidth(), getSearchDialogHeight());
}
// --- Search history persistence ---
public java.util.List<String> getSearchHistory() {
java.util.List<String> list = new java.util.ArrayList<>();
int count = Integer.parseInt(properties.getProperty("search.history.count", "0"));
for (int i = 0; i < count; i++) {
String v = properties.getProperty("search.history." + i, null);
if (v != null && !v.isEmpty()) list.add(v);
}
return list;
}
public void saveSearchHistory(java.util.List<String> history) {
if (history == null) {
properties.setProperty("search.history.count", "0");
return;
}
int limit = Math.min(history.size(), 50); // cap stored entries
properties.setProperty("search.history.count", String.valueOf(limit));
for (int i = 0; i < limit; i++) {
properties.setProperty("search.history." + i, history.get(i));
}
// remove any old entries beyond limit
int old = Integer.parseInt(properties.getProperty("search.history.count", "0"));
for (int i = limit; i < old; i++) {
properties.remove("search.history." + i);
}
}
// --- Content search history persistence ---
public java.util.List<String> getContentSearchHistory() {
java.util.List<String> list = new java.util.ArrayList<>();
int count = Integer.parseInt(properties.getProperty("search.content.history.count", "0"));
for (int i = 0; i < count; i++) {
String v = properties.getProperty("search.content.history." + i, null);
if (v != null && !v.isEmpty()) list.add(v);
}
return list;
}
public void saveContentSearchHistory(java.util.List<String> history) {
if (history == null) {
properties.setProperty("search.content.history.count", "0");
return;
}
int limit = Math.min(history.size(), 50);
properties.setProperty("search.content.history.count", String.valueOf(limit));
for (int i = 0; i < limit; i++) {
properties.setProperty("search.content.history." + i, history.get(i));
}
// remove old entries beyond limit
int old = Integer.parseInt(properties.getProperty("search.content.history.count", "0"));
for (int i = limit; i < old; i++) {
properties.remove("search.content.history." + i);
}
}
} }

View File

@ -6,6 +6,8 @@ import java.io.*;
import java.nio.file.*; import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Service for file operations - copy, move, delete, etc. * Service for file operations - copy, move, delete, etc.
@ -156,19 +158,47 @@ public class FileOperations {
* Search files by pattern * Search files by pattern
*/ */
public static void search(File directory, String pattern, boolean recursive, SearchCallback callback) throws IOException { public static void search(File directory, String pattern, boolean recursive, SearchCallback callback) throws IOException {
searchRecursive(directory.toPath(), pattern.toLowerCase(), recursive, callback); if (pattern == null) return;
// Prepare a compiled regex if the pattern contains wildcards to avoid recompiling per-file
Pattern filenameRegex = null;
if (pattern.contains("*") || pattern.contains("?")) {
String regex = pattern
.replace(".", "\\.")
.replace("*", ".*")
.replace("?", ".");
filenameRegex = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
searchRecursive(directory.toPath(), pattern.toLowerCase(), filenameRegex, recursive, callback);
}
/**
* Search file contents for a text fragment (case-insensitive).
* Calls callback.onFileFound(file) when a file contains the text.
*/
public static void searchContents(File directory, String text, boolean recursive, SearchCallback callback) throws IOException {
if (text == null) return;
// Precompile a case-insensitive pattern for content search to avoid per-line lowercasing
Pattern contentPattern = Pattern.compile(Pattern.quote(text), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
searchContentsRecursive(directory.toPath(), contentPattern, recursive, callback);
} }
private static void searchRecursive(Path directory, String pattern, boolean recursive, SearchCallback callback) throws IOException { private static void searchRecursive(Path directory, String patternLower, Pattern filenameRegex, boolean recursive, SearchCallback callback) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
for (Path entry : stream) { for (Path entry : stream) {
if (Files.isDirectory(entry)) { if (Files.isDirectory(entry)) {
if (recursive) { if (recursive) {
searchRecursive(entry, pattern, recursive, callback); searchRecursive(entry, patternLower, filenameRegex, recursive, callback);
} }
} else { } else {
String fileName = entry.getFileName().toString().toLowerCase(); String fileName = entry.getFileName().toString();
if (fileName.contains(pattern) || matchesPattern(fileName, pattern)) { String fileNameLower = fileName.toLowerCase();
boolean matched = false;
if (fileNameLower.contains(patternLower)) matched = true;
else if (filenameRegex != null) {
Matcher m = filenameRegex.matcher(fileName);
if (m.matches()) matched = true;
}
if (matched) {
callback.onFileFound(entry.toFile()); callback.onFileFound(entry.toFile());
} }
} }
@ -177,18 +207,38 @@ public class FileOperations {
// Ignore directories without access // Ignore directories without access
} }
} }
/** private static void searchContentsRecursive(Path directory, Pattern contentPattern, boolean recursive, SearchCallback callback) throws IOException {
* Check whether filename matches the pattern (supports * and ?) try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
*/ for (Path entry : stream) {
private static boolean matchesPattern(String fileName, String pattern) { if (Files.isDirectory(entry)) {
String regex = pattern if (recursive) {
.replace(".", "\\.") searchContentsRecursive(entry, contentPattern, recursive, callback);
.replace("*", ".*") }
.replace("?", "."); } else {
return fileName.matches(regex); // Try reading file as text line-by-line and search for pattern (case-insensitive via compiled Pattern)
try (BufferedReader br = Files.newBufferedReader(entry)) {
String line;
boolean found = false;
while ((line = br.readLine()) != null) {
if (contentPattern.matcher(line).find()) {
found = true;
break;
}
}
if (found) callback.onFileFound(entry.toFile());
} catch (IOException ex) {
// Skip files that cannot be read as text
}
}
}
} catch (AccessDeniedException e) {
// Ignore directories without access
}
} }
// legacy matchesPattern removed filename wildcard handling is done via a precompiled Pattern
/** /**
* Callback pro progress operací * Callback pro progress operací
*/ */

View File

@ -311,6 +311,23 @@ public class FileEditor extends JDialog {
} }
// Ensure byteTextOffsets length equals fileBytes length // Ensure byteTextOffsets length equals fileBytes length
// If some bytes were missing due to empty file, leave as is // If some bytes were missing due to empty file, leave as is
// After building the view text, position caret to the start of the rendered data
SwingUtilities.invokeLater(() -> {
try {
if (byteTextOffsets != null && !byteTextOffsets.isEmpty()) {
int pos = Math.max(0, byteTextOffsets.get(0));
textArea.setCaretPosition(pos);
// ensure the caret is visible at top-left
Rectangle vis = textArea.getVisibleRect();
Rectangle r = textArea.modelToView2D(pos).getBounds();
if (r != null) {
textArea.scrollRectToVisible(new Rectangle(r.x, r.y, vis.width, vis.height));
}
} else {
textArea.setCaretPosition(0);
}
} catch (Exception ignore) {}
});
} }
private void loadHexPage() { private void loadHexPage() {
@ -607,17 +624,14 @@ public class FileEditor extends JDialog {
// makes the displayed offset follow scrolling (PgUp/PgDn or scrollbar) // makes the displayed offset follow scrolling (PgUp/PgDn or scrollbar)
// rather than only caret movement. // rather than only caret movement.
int refPos; int refPos;
if (raf != null) {
try { try {
Rectangle vis = textArea.getVisibleRect(); Rectangle vis = textArea.getVisibleRect();
Point topLeft = new Point(vis.x, vis.y); Point topLeft = new Point(vis.x, vis.y);
refPos = textArea.viewToModel2D(topLeft); refPos = textArea.viewToModel2D(topLeft);
} catch (Exception ex) { } catch (Exception ex) {
// fallback to caret position if viewToModel fails
refPos = textArea.getCaretPosition(); refPos = textArea.getCaretPosition();
} }
} else {
refPos = textArea.getCaretPosition();
}
int caret = refPos; int caret = refPos;
long byteIndex = mapCaretToByteIndex(caret); long byteIndex = mapCaretToByteIndex(caret);
long totalBytes = (raf != null && fileLength > 0) ? fileLength : (fileBytes != null ? fileBytes.length : 0); long totalBytes = (raf != null && fileLength > 0) ? fileLength : (fileBytes != null ? fileBytes.length : 0);

View File

@ -413,7 +413,7 @@ public class FilePanel extends JPanel {
long free = drive.getUsableSpace(); long free = drive.getUsableSpace();
String freeGb = formatGbShort(free); String freeGb = formatGbShort(free);
String totalGb = formatGbShort(total); String totalGb = formatGbShort(total);
String info = String.format("%s %s free of %s GB", name, freeGb, totalGb); String info = String.format("%s %s GB free of %s GB", name, freeGb, totalGb);
driveInfoLabel.setText(info); driveInfoLabel.setText(info);
} catch (Exception ex) { } catch (Exception ex) {
driveInfoLabel.setText(""); driveInfoLabel.setText("");

View File

@ -861,6 +861,14 @@ public class FilePanelTab extends JPanel {
} }
} }
} }
/**
* Public wrapper to select an item by name from outside this class.
* Useful for other UI components to request focusing a specific file.
*/
public void selectItem(String name) {
selectItemByName(name);
}
public void toggleSelectionAndMoveDown() { public void toggleSelectionAndMoveDown() {
int selectedRow = fileTable.getSelectedRow(); int selectedRow = fileTable.getSelectedRow();

View File

@ -453,9 +453,9 @@ public class MainWindow extends JFrame {
KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.ALT_DOWN_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.ALT_DOWN_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
// Ctrl+F - Search // Alt+F7 - Search (changed from Ctrl+F)
rootPane.registerKeyboardAction(e -> showSearchDialog(), rootPane.registerKeyboardAction(e -> showSearchDialog(),
KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), KeyStroke.getKeyStroke(KeyEvent.VK_F7, InputEvent.ALT_DOWN_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
// Ctrl+F1 - Full details // Ctrl+F1 - Full details
@ -704,9 +704,55 @@ public class MainWindow extends JFrame {
* Show search dialog * Show search dialog
*/ */
private void showSearchDialog() { private void showSearchDialog() {
SearchDialog dialog = new SearchDialog(this, activePanel.getCurrentDirectory()); SearchDialog dialog = new SearchDialog(this, activePanel.getCurrentDirectory(), config);
dialog.setVisible(true); dialog.setVisible(true);
} }
/**
* Show the given file's parent directory in the panel that currently has focus
* and select the file in that panel.
*/
public void showFileInFocusedPanel(File file) {
if (file == null) return;
// Determine which panel currently has focus
java.awt.Component owner = java.awt.KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
FilePanel target = null;
if (owner != null) {
Component p = owner;
while (p != null) {
if (p == leftPanel) {
target = leftPanel;
break;
}
if (p == rightPanel) {
target = rightPanel;
break;
}
p = p.getParent();
}
}
if (target == null) target = activePanel != null ? activePanel : leftPanel;
final FilePanel chosen = target;
final File parentDir = file.getParentFile();
if (parentDir == null) return;
// Load directory and then select item by name on the EDT
SwingUtilities.invokeLater(() -> {
chosen.loadDirectory(parentDir);
// mark this panel active and refresh borders
activePanel = chosen;
updateActivePanelBorder();
// After loading, select the file name
SwingUtilities.invokeLater(() -> {
FilePanelTab tab = chosen.getCurrentTab();
if (tab != null) {
tab.selectItem(file.getName());
tab.getFileTable().requestFocusInWindow();
}
});
});
}
/** /**
* Show file in internal viewer * Show file in internal viewer

View File

@ -4,9 +4,12 @@ import com.kfmanager.model.FileItem;
import com.kfmanager.service.FileOperations; import com.kfmanager.service.FileOperations;
import javax.swing.*; import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.awt.Desktop;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -15,21 +18,45 @@ import java.util.List;
*/ */
public class SearchDialog extends JDialog { public class SearchDialog extends JDialog {
private JTextField patternField; private JComboBox<String> patternCombo;
private JComboBox<String> contentPatternCombo;
private JCheckBox recursiveCheckBox; private JCheckBox recursiveCheckBox;
private JCheckBox contentSearchCheckBox;
private JTable resultsTable; private JTable resultsTable;
private ResultsTableModel tableModel; private ResultsTableModel tableModel;
private JButton searchButton; private JButton searchButton;
private JButton cancelButton; private JButton cancelButton;
private JButton viewButton;
private JButton editButton;
private JProgressBar statusProgressBar;
private JLabel statusLabel;
private volatile int foundCount = 0;
private File searchDirectory; private File searchDirectory;
private volatile boolean searching = false; private volatile boolean searching = false;
private com.kfmanager.config.AppConfig config;
public SearchDialog(Frame parent, File searchDirectory) { public SearchDialog(Frame parent, File searchDirectory, com.kfmanager.config.AppConfig config) {
super(parent, "Search files", true); // Make the dialog modeless so it does not remain forced above other windows
super(parent, "Search files", false);
this.searchDirectory = searchDirectory; this.searchDirectory = searchDirectory;
this.config = config;
initComponents(); initComponents();
setSize(700, 500); // Allow the user to resize the search dialog
setLocationRelativeTo(parent); setResizable(true);
// sensible minimum size so layout remains usable
setMinimumSize(new Dimension(480, 320));
// Restore previous bounds if config present
if (this.config != null) {
try {
this.config.restoreSearchDialogState(this);
} catch (Exception ignore) {
setSize(700, 500);
setLocationRelativeTo(parent);
}
} else {
setSize(700, 500);
setLocationRelativeTo(parent);
}
} }
private void initComponents() { private void initComponents() {
@ -48,18 +75,51 @@ public class SearchDialog extends JDialog {
gbc.gridx = 1; gbc.gridx = 1;
gbc.weightx = 1.0; gbc.weightx = 1.0;
patternField = new JTextField(); // Pattern input is an editable combo box populated from history
patternField.setToolTipText("Enter filename or pattern (* for any chars, ? for single char)"); java.util.List<String> history = config != null ? config.getSearchHistory() : java.util.Collections.emptyList();
searchPanel.add(patternField, gbc); javax.swing.DefaultComboBoxModel<String> historyModel = new javax.swing.DefaultComboBoxModel<>();
for (String h : history) historyModel.addElement(h);
patternCombo = new JComboBox<>(historyModel);
patternCombo.setEditable(true);
// start with an empty editor so the field is blank on open
try {
patternCombo.setSelectedItem("");
patternCombo.getEditor().setItem("");
} catch (Exception ignore) {}
patternCombo.setToolTipText("Enter filename or pattern (* for any chars, ? for single char)");
searchPanel.add(patternCombo, gbc);
gbc.gridx = 0; gbc.gridx = 0;
gbc.gridy = 1; gbc.gridy = 1;
gbc.gridwidth = 2; gbc.gridwidth = 2;
recursiveCheckBox = new JCheckBox("Include subdirectories", true); recursiveCheckBox = new JCheckBox("Include subdirectories", true);
searchPanel.add(recursiveCheckBox, gbc); searchPanel.add(recursiveCheckBox, gbc);
// Row for content text pattern
gbc.gridx = 0;
gbc.gridy = 2; gbc.gridy = 2;
JLabel pathLabel = new JLabel("Directory: " + searchDirectory.getAbsolutePath()); gbc.gridwidth = 1;
searchPanel.add(new JLabel("Text:"), gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
java.util.List<String> contentHistory = config != null ? config.getContentSearchHistory() : java.util.Collections.emptyList();
javax.swing.DefaultComboBoxModel<String> contentHistoryModel = new javax.swing.DefaultComboBoxModel<>();
for (String h : contentHistory) contentHistoryModel.addElement(h);
contentPatternCombo = new JComboBox<>(contentHistoryModel);
contentPatternCombo.setEditable(true);
try { contentPatternCombo.setSelectedItem(""); contentPatternCombo.getEditor().setItem(""); } catch (Exception ignore) {}
contentPatternCombo.setToolTipText("Text to search inside files");
searchPanel.add(contentPatternCombo, gbc);
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = 2;
contentSearchCheckBox = new JCheckBox("Search inside file contents", false);
searchPanel.add(contentSearchCheckBox, gbc);
gbc.gridy = 4;
JLabel pathLabel = new JLabel("Directory: " + searchDirectory.getAbsolutePath());
pathLabel.setFont(pathLabel.getFont().deriveFont(Font.ITALIC)); pathLabel.setFont(pathLabel.getFont().deriveFont(Font.ITALIC));
searchPanel.add(pathLabel, gbc); searchPanel.add(pathLabel, gbc);
@ -70,6 +130,8 @@ public class SearchDialog extends JDialog {
resultsTable = new JTable(tableModel); resultsTable = new JTable(tableModel);
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
resultsTable.setFont(new Font("Monospaced", Font.PLAIN, 12)); resultsTable.setFont(new Font("Monospaced", Font.PLAIN, 12));
// let the table columns adjust when the dialog is resized
resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
resultsTable.getColumnModel().getColumn(0).setPreferredWidth(400); resultsTable.getColumnModel().getColumn(0).setPreferredWidth(400);
resultsTable.getColumnModel().getColumn(1).setPreferredWidth(100); resultsTable.getColumnModel().getColumn(1).setPreferredWidth(100);
@ -84,61 +146,315 @@ public class SearchDialog extends JDialog {
} }
} }
}); });
// Enable/disable view/edit buttons depending on selection
resultsTable.getSelectionModel().addListSelectionListener(e -> {
int sel = resultsTable.getSelectedRow();
boolean ok = false;
if (sel >= 0) {
FileItem it = tableModel.getResult(sel);
ok = it != null && !it.isDirectory() && !"..".equals(it.getName());
}
viewButton.setEnabled(ok);
editButton.setEnabled(ok);
});
JScrollPane scrollPane = new JScrollPane(resultsTable); JScrollPane scrollPane = new JScrollPane(resultsTable);
scrollPane.setBorder(BorderFactory.createTitledBorder("Výsledky")); scrollPane.setBorder(BorderFactory.createTitledBorder("Výsledky"));
add(scrollPane, BorderLayout.CENTER); add(scrollPane, BorderLayout.CENTER);
// Panel s tlačítky // Status bar with progress and message
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); statusLabel = new JLabel("Ready");
statusProgressBar = new JProgressBar();
statusProgressBar.setVisible(false);
statusProgressBar.setIndeterminate(false);
JPanel statusPanel = new JPanel(new BorderLayout(6, 6));
statusPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
statusPanel.add(statusLabel, BorderLayout.CENTER);
statusPanel.add(statusProgressBar, BorderLayout.EAST);
// Panel s tlačítky
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
searchButton = new JButton("Hledat"); searchButton = new JButton("Hledat");
searchButton.addActionListener(e -> performSearch()); searchButton.addActionListener(e -> performSearch());
viewButton = new JButton("Zobrazit");
viewButton.setEnabled(false);
viewButton.addActionListener(e -> viewSelectedFile());
editButton = new JButton("Upravit");
editButton.setEnabled(false);
editButton.addActionListener(e -> editSelectedFile());
cancelButton = new JButton("Zavřít");
cancelButton.addActionListener(e -> dispose());
JButton openButton = new JButton("Otevřít umístění");
openButton.addActionListener(e -> openSelectedFile());
buttonPanel.add(searchButton);
buttonPanel.add(viewButton);
buttonPanel.add(editButton);
buttonPanel.add(openButton);
buttonPanel.add(cancelButton);
cancelButton = new JButton("Zavřít"); // Compose bottom area: status bar above buttons
cancelButton.addActionListener(e -> dispose()); JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(statusPanel, BorderLayout.NORTH);
bottomPanel.add(buttonPanel, BorderLayout.SOUTH);
add(bottomPanel, BorderLayout.SOUTH);
JButton openButton = new JButton("Otevřít umístění"); // Require explicit Enter to start search when choosing from history.
openButton.addActionListener(e -> openSelectedFile()); // Bind Enter on the combo editor component so selecting an item from the
// popup does not automatically trigger search until user confirms.
buttonPanel.add(searchButton); java.awt.Component editorComp = patternCombo.getEditor().getEditorComponent();
buttonPanel.add(openButton); if (editorComp instanceof javax.swing.text.JTextComponent) {
buttonPanel.add(cancelButton); javax.swing.text.JTextComponent tc = (javax.swing.text.JTextComponent) editorComp;
tc.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "confirmSearch");
add(buttonPanel, BorderLayout.SOUTH); tc.getActionMap().put("confirmSearch", new AbstractAction() {
@Override
// Enter pro spuštění hledání public void actionPerformed(java.awt.event.ActionEvent e) {
patternField.addActionListener(e -> performSearch()); try {
// If the popup is visible, treat Enter as "confirm selection": fill editor and close popup
if (patternCombo.isPopupVisible()) {
Object sel = patternCombo.getSelectedItem();
if (sel != null) {
patternCombo.getEditor().setItem(sel.toString());
}
patternCombo.hidePopup();
return; // do not start search yet
}
} catch (Exception ignore) {}
// Popup not visible -> actual confirm to start search
performSearch();
}
});
}
// Same explicit-Enter behavior for content text pattern combo
try {
java.awt.Component contentEditorComp = contentPatternCombo.getEditor().getEditorComponent();
if (contentEditorComp instanceof javax.swing.text.JTextComponent) {
javax.swing.text.JTextComponent tc2 = (javax.swing.text.JTextComponent) contentEditorComp;
tc2.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "confirmSearchContent");
tc2.getActionMap().put("confirmSearchContent", new AbstractAction() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
try {
if (contentPatternCombo.isPopupVisible()) {
Object sel = contentPatternCombo.getSelectedItem();
if (sel != null) contentPatternCombo.getEditor().setItem(sel.toString());
contentPatternCombo.hidePopup();
return;
}
} catch (Exception ignore) {}
performSearch();
}
});
}
} catch (Exception ignore) {}
// Alt+F focuses filename (pattern) input; Alt+T focuses content text input
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.ALT_DOWN_MASK), "focusFilename");
getRootPane().getActionMap().put("focusFilename", new AbstractAction() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
try {
java.awt.Component ed = patternCombo.getEditor().getEditorComponent();
if (ed != null) {
ed.requestFocusInWindow();
if (ed instanceof javax.swing.text.JTextComponent) {
((javax.swing.text.JTextComponent) ed).selectAll();
}
}
} catch (Exception ignore) {}
}
});
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.ALT_DOWN_MASK), "focusContentText");
getRootPane().getActionMap().put("focusContentText", new AbstractAction() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
try {
// Toggle the content-search checkbox (on/off)
try {
if (contentSearchCheckBox != null) {
contentSearchCheckBox.setSelected(!contentSearchCheckBox.isSelected());
}
} catch (Exception ignore) {}
java.awt.Component ed = contentPatternCombo.getEditor().getEditorComponent();
if (ed != null) {
ed.requestFocusInWindow();
if (ed instanceof javax.swing.text.JTextComponent) {
((javax.swing.text.JTextComponent) ed).selectAll();
}
}
} catch (Exception ignore) {}
}
});
// Escape closes the dialog (and cancels ongoing search)
getRootPane().registerKeyboardAction(e -> {
searching = false;
dispose();
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
// F3 = view, F4 = edit when dialog is active
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), "viewResult");
getRootPane().getActionMap().put("viewResult", new AbstractAction() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
viewSelectedFile();
}
});
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0), "editResult");
getRootPane().getActionMap().put("editResult", new AbstractAction() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
editSelectedFile();
}
});
}
@Override
public void dispose() {
// Persist bounds before disposing
try {
if (config != null) {
// also persist current pattern into history
try {
Object it = patternCombo != null ? patternCombo.getEditor().getItem() : null;
String cur = it != null ? it.toString().trim() : null;
if (cur != null && !cur.isEmpty()) {
java.util.List<String> hist = new java.util.ArrayList<>(config.getSearchHistory());
hist.remove(cur);
hist.add(0, cur);
int max = 20;
while (hist.size() > max) hist.remove(hist.size() - 1);
config.saveSearchHistory(hist);
}
} catch (Exception ignore) {}
// also persist current content search pattern into history
try {
Object cit = contentPatternCombo != null ? contentPatternCombo.getEditor().getItem() : null;
String ccur = cit != null ? cit.toString().trim() : null;
if (ccur != null && !ccur.isEmpty()) {
java.util.List<String> chist = new java.util.ArrayList<>(config.getContentSearchHistory());
chist.remove(ccur);
chist.add(0, ccur);
int maxc = 20;
while (chist.size() > maxc) chist.remove(chist.size() - 1);
config.saveContentSearchHistory(chist);
}
} catch (Exception ignore) {}
config.saveSearchDialogState(this);
config.saveConfig();
}
} catch (Exception ignore) {}
super.dispose();
} }
/** /**
* Provede vyhledávání * Provede vyhledávání
*/ */
private void performSearch() { private void performSearch() {
String pattern = patternField.getText().trim(); String namePat = "";
if (pattern.isEmpty()) { try {
JOptionPane.showMessageDialog(this, Object it = patternCombo.getEditor().getItem();
"Zadejte hledaný vzor", namePat = it != null ? it.toString().trim() : "";
"Chyba", } catch (Exception ex) { namePat = ""; }
JOptionPane.WARNING_MESSAGE);
return; String contentPat = "";
try {
Object cit = contentPatternCombo.getEditor().getItem();
contentPat = cit != null ? cit.toString().trim() : "";
} catch (Exception ex) { contentPat = ""; }
final boolean isContentSearch = contentSearchCheckBox != null && contentSearchCheckBox.isSelected();
if (isContentSearch) {
if (contentPat.isEmpty()) {
JOptionPane.showMessageDialog(this,
"Zadejte hledaný text",
"Chyba",
JOptionPane.WARNING_MESSAGE);
return;
}
} else {
if (namePat.isEmpty()) {
JOptionPane.showMessageDialog(this,
"Zadejte hledaný vzor",
"Chyba",
JOptionPane.WARNING_MESSAGE);
return;
}
} }
tableModel.clear(); tableModel.clear();
searchButton.setEnabled(false); searchButton.setEnabled(false);
searching = true; searching = true;
// Spustit vyhledávání v samostatném vlákně // Persist the chosen pattern into the appropriate history (most-recent-first)
if (config != null) {
try {
if (isContentSearch) {
java.util.List<String> chist = new java.util.ArrayList<>(config.getContentSearchHistory());
chist.remove(contentPat);
chist.add(0, contentPat);
int max = 20;
while (chist.size() > max) chist.remove(chist.size() - 1);
config.saveContentSearchHistory(chist);
config.saveConfig();
// update content combo model
javax.swing.DefaultComboBoxModel<String> cm = (javax.swing.DefaultComboBoxModel<String>) contentPatternCombo.getModel();
cm.removeAllElements();
for (String s : chist) cm.addElement(s);
contentPatternCombo.setSelectedItem(contentPat);
} else {
java.util.List<String> hist = new java.util.ArrayList<>(config.getSearchHistory());
hist.remove(namePat);
hist.add(0, namePat);
int max = 20;
while (hist.size() > max) hist.remove(hist.size() - 1);
config.saveSearchHistory(hist);
config.saveConfig();
// update combo model
javax.swing.DefaultComboBoxModel<String> m = (javax.swing.DefaultComboBoxModel<String>) patternCombo.getModel();
m.removeAllElements();
for (String s : hist) m.addElement(s);
patternCombo.setSelectedItem(namePat);
}
} catch (Exception ignore) {}
}
final String pattern = isContentSearch ? contentPat : namePat;
// Reset and show status
foundCount = 0;
statusLabel.setText("Searching...");
statusProgressBar.setVisible(true);
statusProgressBar.setIndeterminate(true);
// Spustit vyhledávání v samostatném vlákně
SwingWorker<Void, File> worker = new SwingWorker<Void, File>() { SwingWorker<Void, File> worker = new SwingWorker<Void, File>() {
@Override @Override
protected Void doInBackground() throws Exception { protected Void doInBackground() throws Exception {
FileOperations.search(searchDirectory, pattern, recursiveCheckBox.isSelected(), if (isContentSearch) {
file -> { FileOperations.searchContents(searchDirectory, pattern, recursiveCheckBox.isSelected(), file -> {
if (!searching) { if (!searching) return;
return; publish(file);
} });
publish(file); } else {
}); FileOperations.search(searchDirectory, pattern, recursiveCheckBox.isSelected(), file -> {
if (!searching) return;
publish(file);
});
}
return null; return null;
} }
@ -146,6 +462,16 @@ public class SearchDialog extends JDialog {
protected void process(List<File> chunks) { protected void process(List<File> chunks) {
for (File file : chunks) { for (File file : chunks) {
tableModel.addResult(new FileItem(file)); tableModel.addResult(new FileItem(file));
// update found count and status
foundCount++;
statusLabel.setText("Found " + foundCount + " — searching...");
// If this is the first found file, select it and focus the table
if (foundCount == 1) {
try {
resultsTable.setRowSelectionInterval(0, 0);
resultsTable.requestFocusInWindow();
} catch (Exception ignore) {}
}
} }
} }
@ -153,8 +479,12 @@ public class SearchDialog extends JDialog {
protected void done() { protected void done() {
searchButton.setEnabled(true); searchButton.setEnabled(true);
searching = false; searching = false;
// finalize status
statusProgressBar.setIndeterminate(false);
statusProgressBar.setVisible(false);
try { try {
get(); get();
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", "Nebyly nalezeny žádné soubory",
@ -162,6 +492,7 @@ public class SearchDialog extends JDialog {
JOptionPane.INFORMATION_MESSAGE); JOptionPane.INFORMATION_MESSAGE);
} }
} catch (Exception e) { } catch (Exception e) {
statusLabel.setText("Error");
JOptionPane.showMessageDialog(SearchDialog.this, JOptionPane.showMessageDialog(SearchDialog.this,
"Chyba při hledání: " + e.getMessage(), "Chyba při hledání: " + e.getMessage(),
"Chyba", "Chyba",
@ -181,6 +512,15 @@ public class SearchDialog extends JDialog {
if (selectedRow >= 0) { if (selectedRow >= 0) {
FileItem item = tableModel.getResult(selectedRow); FileItem item = tableModel.getResult(selectedRow);
try { try {
// Open location inside the application: show parent directory in the focused panel and select the file
java.awt.Window w = SwingUtilities.getWindowAncestor(this);
if (w instanceof MainWindow) {
MainWindow mw = (MainWindow) w;
mw.showFileInFocusedPanel(item.getFile());
dispose();
return;
}
// Fallback to opening in system explorer
Desktop.getDesktop().open(item.getFile().getParentFile()); Desktop.getDesktop().open(item.getFile().getParentFile());
} catch (Exception e) { } catch (Exception e) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
@ -190,6 +530,44 @@ public class SearchDialog extends JDialog {
} }
} }
} }
/**
* Open the selected file in the internal viewer (read-only)
*/
private void viewSelectedFile() {
int sel = resultsTable.getSelectedRow();
if (sel >= 0) {
FileItem item = tableModel.getResult(sel);
if (item != null && !item.isDirectory() && !"..".equals(item.getName())) {
try {
Frame owner = (Frame) SwingUtilities.getWindowAncestor(this);
FileEditor viewer = new FileEditor(owner, item.getFile(), config, true);
viewer.setVisible(true);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Chyba při otevírání souboru: " + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE);
}
}
}
}
/**
* Open the selected file in the internal editor (editable)
*/
private void editSelectedFile() {
int sel = resultsTable.getSelectedRow();
if (sel >= 0) {
FileItem item = tableModel.getResult(sel);
if (item != null && !item.isDirectory() && !"..".equals(item.getName())) {
try {
Frame owner = (Frame) SwingUtilities.getWindowAncestor(this);
FileEditor editor = new FileEditor(owner, item.getFile(), config, false);
editor.setVisible(true);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Chyba při otevírání souboru: " + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE);
}
}
}
}
/** /**
* Model tabulky pro výsledky hledání * Model tabulky pro výsledky hledání