search dialog redesign

This commit is contained in:
Radek Davidek 2026-02-10 17:13:39 +01:00
parent 461bb74503
commit 9b93d34988
3 changed files with 323 additions and 166 deletions

View File

@ -11,6 +11,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.zip.*;
import net.lingala.zip4j.ZipFile;
@ -656,35 +657,54 @@ public class FileOperations {
* Search files by filename pattern and/or content text.
* If both are provided, both must match.
*/
public static void search(File directory, String filenamePattern, String contentText, boolean recursive, boolean searchArchives, boolean wholeWord, boolean caseSensitive, SearchCallback callback) throws IOException {
public static void search(File directory, String filenamePattern, String contentText, int maxDepth, boolean searchArchives, boolean wholeWord, boolean caseSensitive, boolean filenameIsRegex, boolean contentIsRegex, SearchCallback callback) throws IOException {
Pattern filenameRegex = null;
String filenameLower = null;
if (filenamePattern != null && !filenamePattern.isEmpty()) {
filenameLower = filenamePattern.toLowerCase();
if (filenamePattern.contains("*") || filenamePattern.contains("?")) {
String regex = filenamePattern
.replace(".", "\\.")
.replace("*", ".*")
.replace("?", ".");
filenameRegex = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
if (filenameIsRegex) {
try {
filenameRegex = Pattern.compile(filenamePattern, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
} catch (Exception e) {
throw new IOException("Invalid filename RegEx: " + e.getMessage());
}
} else {
filenameLower = caseSensitive ? filenamePattern : filenamePattern.toLowerCase();
if (filenamePattern.contains("*") || filenamePattern.contains("?")) {
String regex = filenamePattern
.replace(".", "\\.")
.replace("*", ".*")
.replace("?", ".");
filenameRegex = Pattern.compile(regex, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
}
}
Pattern contentPattern = null;
if (contentText != null && !contentText.isEmpty()) {
String quote = Pattern.quote(contentText);
String regex = wholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
int flags = Pattern.UNICODE_CASE;
if (!caseSensitive) {
flags |= Pattern.CASE_INSENSITIVE;
}
contentPattern = Pattern.compile(regex, flags);
String regex;
if (contentIsRegex) {
regex = contentText;
} else {
String quote = Pattern.quote(contentText);
regex = wholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
}
try {
contentPattern = Pattern.compile(regex, flags);
} catch (Exception e) {
throw new IOException("Invalid content RegEx: " + e.getMessage());
}
}
searchRecursive(directory.toPath(), filenameLower, filenameRegex, contentPattern, recursive, searchArchives, callback);
searchRecursive(directory.toPath(), filenameLower, filenameRegex, contentPattern, maxDepth, 0, searchArchives, caseSensitive, callback);
}
private static void searchRecursive(Path directory, String patternLower, Pattern filenameRegex, Pattern contentPattern, boolean recursive, boolean searchArchives, SearchCallback callback) throws IOException {
private static void searchRecursive(Path directory, String patternLower, Pattern filenameRegex, Pattern contentPattern, int maxDepth, int currentDepth, boolean searchArchives, boolean caseSensitive, SearchCallback callback) throws IOException {
if (callback != null && callback.isCancelled()) return;
// Use absolute path for reliable prefix checking
@ -708,14 +728,14 @@ public class FileOperations {
}
if (Files.isDirectory(entry)) {
if (recursive) {
searchRecursive(entry, patternLower, filenameRegex, contentPattern, recursive, searchArchives, callback);
if (maxDepth == -1 || currentDepth < maxDepth) {
searchRecursive(entry, patternLower, filenameRegex, contentPattern, maxDepth, currentDepth + 1, searchArchives, caseSensitive, callback);
}
} else {
File file = entry.toFile();
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
nameMatched = matchName(file.getName(), patternLower, filenameRegex);
nameMatched = matchName(file.getName(), patternLower, filenameRegex, caseSensitive);
}
boolean contentMatched = true;
@ -729,7 +749,7 @@ public class FileOperations {
// SEARCH IN ARCHIVES
if (searchArchives && isArchiveFile(file)) {
searchInArchiveCombined(file, patternLower, filenameRegex, contentPattern, callback);
searchInArchiveCombined(file, patternLower, filenameRegex, contentPattern, caseSensitive, callback);
}
}
} catch (AccessDeniedException e) {
@ -741,13 +761,15 @@ public class FileOperations {
}
}
private static boolean matchName(String name, String patternLower, Pattern filenameRegex) {
String nameLower = name.toLowerCase();
if (nameLower.contains(patternLower)) return true;
private static boolean matchName(String name, String patternLower, Pattern filenameRegex, boolean caseSensitive) {
if (filenameRegex != null) {
return filenameRegex.matcher(name).matches();
}
return false;
if (caseSensitive) {
return name.contains(patternLower);
} else {
return name.toLowerCase().contains(patternLower.toLowerCase());
}
}
private static boolean fileMatchesContent(Path entry, Pattern contentPattern) {
@ -847,7 +869,7 @@ public class FileOperations {
return n.endsWith(".war") || n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".tar") || n.endsWith(".tar.gz") || n.endsWith(".tgz") || n.endsWith(".7z") || n.endsWith(".rar");
}
private static void searchInArchiveCombined(File archive, String patternLower, Pattern filenameRegex, Pattern contentPattern, SearchCallback callback) {
private static void searchInArchiveCombined(File archive, String patternLower, Pattern filenameRegex, Pattern contentPattern, boolean caseSensitive, SearchCallback callback) {
if (callback != null && callback.isCancelled()) return;
String name = archive.getName().toLowerCase();
try {
@ -859,7 +881,7 @@ public class FileOperations {
if (!entry.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
nameMatched = matchEntry(entry.getName(), patternLower, filenameRegex);
nameMatched = matchEntry(entry.getName(), patternLower, filenameRegex, caseSensitive);
}
boolean contentMatched = true;
if (nameMatched && contentPattern != null) {
@ -883,7 +905,7 @@ public class FileOperations {
if (!entry.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
nameMatched = matchEntry(entry.getName(), patternLower, filenameRegex);
nameMatched = matchEntry(entry.getName(), patternLower, filenameRegex, caseSensitive);
}
boolean contentMatched = true;
if (nameMatched && contentPattern != null) {
@ -903,7 +925,7 @@ public class FileOperations {
if (!entry.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
nameMatched = matchEntry(entry.getName(), patternLower, filenameRegex);
nameMatched = matchEntry(entry.getName(), patternLower, filenameRegex, caseSensitive);
}
boolean contentMatched = true;
if (nameMatched && contentPattern != null) {
@ -931,7 +953,7 @@ public class FileOperations {
if (!fh.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
nameMatched = matchEntry(fh.getFileName(), patternLower, filenameRegex);
nameMatched = matchEntry(fh.getFileName(), patternLower, filenameRegex, caseSensitive);
}
boolean contentMatched = true;
if (nameMatched && contentPattern != null) {
@ -952,16 +974,13 @@ public class FileOperations {
}
private static boolean matchEntry(String entryName, String patternLower, Pattern filenameRegex) {
private static boolean matchEntry(String entryName, String patternLower, Pattern filenameRegex, boolean caseSensitive) {
if (entryName == null) return false;
String nameShort = entryName;
int lastSlash = entryName.lastIndexOf('/');
if (lastSlash != -1) nameShort = entryName.substring(lastSlash + 1);
String nameLower = nameShort.toLowerCase();
if (nameLower.contains(patternLower)) return true;
if (filenameRegex != null && filenameRegex.matcher(nameShort).matches()) return true;
return false;
return matchName(nameShort, patternLower, filenameRegex, caseSensitive);
}
private static boolean searchInStream(InputStream is, Pattern contentPattern) {

View File

@ -53,19 +53,22 @@ public class FileEditor extends JFrame {
private JTextField searchField;
private JCheckBox wholeWordCheckBox;
private JCheckBox caseSensitiveCheckBox;
private JCheckBox regexCheckBox;
private JLabel searchStatusLabel;
private static String lastSearchValue = "";
private static boolean lastWholeWord = false;
private static boolean lastCaseSensitive = false;
private static boolean lastRegex = false;
public static void setLastSearchValue(String value) {
lastSearchValue = value;
}
public static void setLastSearchOptions(String value, boolean wholeWord, boolean caseSensitive) {
public static void setLastSearchOptions(String value, boolean wholeWord, boolean caseSensitive, boolean regex) {
lastSearchValue = value;
lastWholeWord = wholeWord;
lastCaseSensitive = caseSensitive;
lastRegex = regex;
}
public FileEditor(Window parent, File file, AppConfig config, boolean readOnly) {
@ -118,6 +121,7 @@ public class FileEditor extends JFrame {
wholeWordCheckBox = new JCheckBox("Whole word");
caseSensitiveCheckBox = new JCheckBox("Case sensitive");
regexCheckBox = new JCheckBox("RegEx");
JButton nextBtn = new JButton("Next");
nextBtn.addActionListener(e -> findNext());
@ -135,6 +139,7 @@ public class FileEditor extends JFrame {
searchPanel.add(searchField);
searchPanel.add(wholeWordCheckBox);
searchPanel.add(caseSensitiveCheckBox);
searchPanel.add(regexCheckBox);
searchPanel.add(nextBtn);
searchPanel.add(prevBtn);
searchPanel.add(closeBtn);
@ -147,6 +152,7 @@ public class FileEditor extends JFrame {
searchPanel.setVisible(true);
wholeWordCheckBox.setSelected(lastWholeWord);
caseSensitiveCheckBox.setSelected(lastCaseSensitive);
regexCheckBox.setSelected(lastRegex);
String selection = textArea.getSelectedText();
if (selection != null && !selection.isEmpty() && !selection.contains("\n")) {
searchField.setText(selection);
@ -194,6 +200,7 @@ public class FileEditor extends JFrame {
lastSearchValue = text;
lastWholeWord = wholeWordCheckBox.isSelected();
lastCaseSensitive = caseSensitiveCheckBox.isSelected();
lastRegex = regexCheckBox.isSelected();
updateSearchHistory(text);
if (searchField.getText().isEmpty()) searchField.setText(text);
@ -205,23 +212,38 @@ public class FileEditor extends JFrame {
if (textArea.getSelectionEnd() > textArea.getSelectionStart()) {
String selected = content.substring(textArea.getSelectionStart(), textArea.getSelectionEnd());
boolean match;
if (lastCaseSensitive) {
match = selected.equals(text);
if (lastRegex) {
try {
int flags = (!lastCaseSensitive) ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0;
if (Pattern.compile(text, flags).matcher(selected).matches()) {
start = textArea.getSelectionEnd();
}
} catch (Exception ignore) {}
} else {
match = selected.equalsIgnoreCase(text);
}
if (match) {
start = textArea.getSelectionEnd();
if (lastCaseSensitive) {
match = selected.equals(text);
} else {
match = selected.equalsIgnoreCase(text);
}
if (match) {
start = textArea.getSelectionEnd();
}
}
}
String quote = Pattern.quote(text);
String regex = lastWholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
String patternStr = lastRegex ? text : Pattern.quote(text);
String regex = (lastWholeWord && !lastRegex) ? "(?<!\\w)" + patternStr + "(?!\\w)" : patternStr;
int flags = 0;
if (!lastCaseSensitive) {
flags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
}
Pattern pattern = Pattern.compile(regex, flags);
Pattern pattern;
try {
pattern = Pattern.compile(regex, flags);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "Invalid Regular Expression: " + ex.getMessage());
return;
}
Matcher matcher = pattern.matcher(content);
if (matcher.find(start)) {
@ -263,6 +285,7 @@ public class FileEditor extends JFrame {
lastSearchValue = text;
lastWholeWord = wholeWordCheckBox.isSelected();
lastCaseSensitive = caseSensitiveCheckBox.isSelected();
lastRegex = regexCheckBox.isSelected();
updateSearchHistory(text);
if (searchField.getText().isEmpty()) searchField.setText(text);
@ -271,13 +294,19 @@ public class FileEditor extends JFrame {
int start = textArea.getSelectionStart();
if (start < 0) start = content.length();
String quote = Pattern.quote(text);
String regex = lastWholeWord ? "(?<!\\w)" + quote + "(?!\\w)" : quote;
String patternStr = lastRegex ? text : Pattern.quote(text);
String regex = (lastWholeWord && !lastRegex) ? "(?<!\\w)" + patternStr + "(?!\\w)" : patternStr;
int flags = 0;
if (!lastCaseSensitive) {
flags |= Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
}
Pattern pattern = Pattern.compile(regex, flags);
Pattern pattern;
try {
pattern = Pattern.compile(regex, flags);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "Invalid Regular Expression: " + ex.getMessage());
return;
}
Matcher matcher = pattern.matcher(content);
int lastMatchStart = -1;

View File

@ -8,6 +8,7 @@ import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.ActionListener;
import java.io.File;
import java.awt.Desktop;
import java.util.ArrayList;
@ -19,12 +20,17 @@ import java.util.List;
public class SearchDialog extends JDialog {
private JComboBox<String> patternCombo;
private JComboBox<String> searchInCombo;
private JComboBox<String> contentPatternCombo;
private JComboBox<String> subDirCombo;
private JCheckBox recursiveCheckBox;
private JCheckBox contentSearchCheckBox;
private JCheckBox archiveSearchCheckBox;
private JCheckBox wholeWordCheckBox;
private JCheckBox caseSensitiveCheckBox;
private JCheckBox regexCheckBox;
private JCheckBox regexContentCheckBox;
private JCheckBox everythingCheckBox;
private JTable resultsTable;
private ResultsTableModel tableModel;
private JButton searchButton;
@ -69,19 +75,28 @@ public class SearchDialog extends JDialog {
Color bg = config.getBackgroundColor();
if (bg == null) return;
updateComponentBackground(getContentPane(), bg);
// Ensure some panels are transparent to let the main background show through
if (resultsTable.getTableHeader() != null) {
resultsTable.getTableHeader().setBackground(bg);
resultsTable.getTableHeader().setForeground(isDark(bg) ? Color.WHITE : Color.BLACK);
}
}
private void updateComponentBackground(Container container, Color bg) {
if (container == null) return;
container.setBackground(bg);
if (!(container instanceof JViewport)) {
container.setBackground(bg);
}
boolean dark = isDark(bg);
Color selColor = config != null ? config.getSelectionColor() : null;
for (Component c : container.getComponents()) {
if (c instanceof JPanel || c instanceof JToolBar || c instanceof JScrollPane || c instanceof JViewport ||
if (c instanceof JPanel || c instanceof JToolBar || c instanceof JScrollPane ||
c instanceof JTabbedPane || c instanceof JButton || c instanceof JSplitPane ||
c instanceof JList || c instanceof JComboBox || c instanceof JTable) {
c.setBackground(bg);
if (!(c instanceof JViewport)) {
c.setBackground(bg);
}
if (c instanceof JTable t) {
if (t.getTableHeader() != null) {
t.getTableHeader().setBackground(bg);
@ -128,98 +143,145 @@ public class SearchDialog extends JDialog {
setLayout(new BorderLayout(10, 10));
((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Panel pro zadání kritérií
JPanel searchPanel = new JPanel(new GridBagLayout());
// Panel pro zadání kritérií (NORTH panel)
JPanel northPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.insets = new Insets(2, 5, 2, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.anchor = GridBagConstraints.WEST;
// Row 0: Search for: [patternCombo]
gbc.gridx = 0;
gbc.gridy = 0;
searchPanel.add(new JLabel("Search for:"), gbc);
gbc.weightx = 0;
northPanel.add(new JLabel("Search for:"), gbc);
gbc.gridx = 1;
gbc.gridwidth = 2;
gbc.weightx = 1.0;
java.util.List<String> history = config != null ? config.getSearchHistory() : java.util.Collections.emptyList();
patternCombo = new JComboBox<>(new DefaultComboBoxModel<>(history.toArray(new String[0])));
patternCombo.setEditable(true);
try { patternCombo.setSelectedItem(""); patternCombo.getEditor().setItem(""); } catch (Exception ignore) {}
northPanel.add(patternCombo, gbc);
// Row 1: Search in: [searchInCombo] [>>] [Drives]
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 1;
gbc.weightx = 0;
northPanel.add(new JLabel("Search in:"), gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
// Pattern input is an editable combo box populated from history
java.util.List<String> history = config != null ? config.getSearchHistory() : java.util.Collections.emptyList();
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.gridwidth = 2; // Span across to the end since we removed side buttons
searchInCombo = new JComboBox<>(new String[]{searchDirectory.getAbsolutePath()});
searchInCombo.setEditable(true);
northPanel.add(searchInCombo, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
recursiveCheckBox = new JCheckBox("Include subdirectories", true);
searchPanel.add(recursiveCheckBox, gbc);
// Row for content text pattern
// Row 2: [ ] RegEx [ ] Everything
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 1;
searchPanel.add(new JLabel("Text:"), gbc);
gbc.weightx = 0;
regexCheckBox = new JCheckBox("RegEx");
northPanel.add(regexCheckBox, 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.gridwidth = 2;
everythingCheckBox = new JCheckBox("'Everything'");
northPanel.add(everythingCheckBox, gbc);
gbc.gridx = 0;
// Row 3: [ ] Search archives ...
gbc.gridx = 1;
gbc.gridy = 3;
gbc.gridwidth = 2;
contentSearchCheckBox = new JCheckBox("Search inside file contents", false);
searchPanel.add(contentSearchCheckBox, gbc);
archiveSearchCheckBox = new JCheckBox("Search archives (all except for UC2)", false);
northPanel.add(archiveSearchCheckBox, gbc);
// Row 4: Search in subdirectories: [ComboBox]
gbc.gridx = 0;
gbc.gridy = 4;
gbc.gridwidth = 1;
wholeWordCheckBox = new JCheckBox("Whole word only", false);
searchPanel.add(wholeWordCheckBox, gbc);
northPanel.add(new JLabel("Search in subdirectories:"), gbc);
gbc.gridx = 1;
caseSensitiveCheckBox = new JCheckBox("Case sensitive", false);
searchPanel.add(caseSensitiveCheckBox, gbc);
gbc.gridwidth = 2;
String[] subDirOptions = {"all (unlimited depth)", "current directory only", "1 level", "2 levels", "3 levels"};
subDirCombo = new JComboBox<>(subDirOptions);
recursiveCheckBox = new JCheckBox("", true); // keep for logic, but hidden/synced
subDirCombo.addActionListener(e -> {
recursiveCheckBox.setSelected(subDirCombo.getSelectedIndex() == 0);
});
northPanel.add(subDirCombo, gbc);
// Separator
gbc.gridx = 0;
gbc.gridy = 5;
gbc.gridwidth = 2;
archiveSearchCheckBox = new JCheckBox("Search inside archives", false);
archiveSearchCheckBox.setMnemonic(KeyEvent.VK_R);
searchPanel.add(archiveSearchCheckBox, gbc);
gbc.gridwidth = 3;
northPanel.add(new JSeparator(JSeparator.HORIZONTAL), gbc);
// Row 6: [x] Find text: [contentPatternCombo]
gbc.gridx = 0;
gbc.gridy = 6;
JLabel pathLabel = new JLabel("Directory: " + searchDirectory.getAbsolutePath());
pathLabel.setFont(pathLabel.getFont().deriveFont(Font.ITALIC));
searchPanel.add(pathLabel, gbc);
gbc.gridwidth = 1;
contentSearchCheckBox = new JCheckBox("Find text:");
northPanel.add(contentSearchCheckBox, gbc);
add(searchPanel, BorderLayout.NORTH);
gbc.gridx = 1;
gbc.gridwidth = 2;
gbc.weightx = 1.0;
java.util.List<String> contentHistory = config != null ? config.getContentSearchHistory() : java.util.Collections.emptyList();
contentPatternCombo = new JComboBox<>(new DefaultComboBoxModel<>(contentHistory.toArray(new String[0])));
contentPatternCombo.setEditable(true);
try { contentPatternCombo.setSelectedItem(""); contentPatternCombo.getEditor().setItem(""); } catch (Exception ignore) {}
// Enable/disable based on checkbox
contentPatternCombo.setEnabled(contentSearchCheckBox.isSelected());
contentSearchCheckBox.addActionListener(e -> contentPatternCombo.setEnabled(contentSearchCheckBox.isSelected()));
northPanel.add(contentPatternCombo, gbc);
// Tabulka s výsledky
// Row 7 & 8: Options
JPanel optionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
JPanel leftOptions = new JPanel(new GridLayout(0, 1));
wholeWordCheckBox = new JCheckBox("Whole words only");
caseSensitiveCheckBox = new JCheckBox("Case sensitive");
regexContentCheckBox = new JCheckBox("RegEx (2)");
leftOptions.add(wholeWordCheckBox);
leftOptions.add(caseSensitiveCheckBox);
leftOptions.add(regexContentCheckBox);
// Sync enabled state
ActionListener contentSync = e -> {
boolean sel = contentSearchCheckBox.isSelected();
contentPatternCombo.setEnabled(sel);
wholeWordCheckBox.setEnabled(sel);
caseSensitiveCheckBox.setEnabled(sel);
regexContentCheckBox.setEnabled(sel);
};
contentSearchCheckBox.addActionListener(contentSync);
contentSync.actionPerformed(null);
optionsPanel.add(leftOptions);
gbc.gridx = 1;
gbc.gridy = 7;
gbc.gridwidth = 2;
northPanel.add(optionsPanel, gbc);
add(northPanel, BorderLayout.NORTH);
// Tabulka s výsledky (CENTER panel)
tableModel = new ResultsTableModel();
resultsTable = new JTable(tableModel);
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
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(1).setPreferredWidth(100);
resultsTable.getColumnModel().getColumn(2).setPreferredWidth(150);
// Double-click pro otevření umístění
resultsTable.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent e) {
@ -229,7 +291,6 @@ public class SearchDialog extends JDialog {
}
});
// Enter pro otevření umístění
resultsTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "openLocation");
resultsTable.getActionMap().put("openLocation", new AbstractAction() {
@Override
@ -238,7 +299,6 @@ public class SearchDialog extends JDialog {
}
});
// Enable/disable view/edit buttons depending on selection
resultsTable.getSelectionModel().addListSelectionListener(e -> {
int sel = resultsTable.getSelectedRow();
boolean ok = false;
@ -255,67 +315,77 @@ public class SearchDialog extends JDialog {
editButton.setEnabled(canEdit);
});
JScrollPane scrollPane = new JScrollPane(resultsTable);
scrollPane.setBorder(BorderFactory.createTitledBorder("Results"));
add(scrollPane, BorderLayout.CENTER);
JScrollPane scrollPane = new JScrollPane(resultsTable);
scrollPane.setBorder(BorderFactory.createTitledBorder("Results"));
add(scrollPane, BorderLayout.CENTER);
// Status bar with progress and message
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 with buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
// Side Buttons (EAST panel)
JPanel eastPanel = new JPanel();
eastPanel.setLayout(new BoxLayout(eastPanel, BoxLayout.Y_AXIS));
eastPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
searchButton = new JButton("Search");
searchButton.addActionListener(e -> performSearch());
stopButton = new JButton("Cancel");
stopButton.setEnabled(false);
stopButton.addActionListener(e -> {
searching = false;
searchButton = new JButton("Start search");
searchButton.setMaximumSize(new Dimension(120, 30));
searchButton.addActionListener(e -> performSearch());
stopButton = new JButton("Cancel");
stopButton.setMaximumSize(new Dimension(120, 30));
stopButton.setEnabled(false);
statusLabel.setText("Cancelling...");
});
viewButton = new JButton("View");
viewButton.setEnabled(false);
viewButton.addActionListener(e -> viewSelectedFile());
editButton = new JButton("Edit");
editButton.setEnabled(false);
editButton.addActionListener(e -> editSelectedFile());
cancelButton = new JButton("Close");
cancelButton.addActionListener(e -> {
searching = false;
dispose();
});
JButton openButton = new JButton("Open location");
openButton.addActionListener(e -> openSelectedFile());
buttonPanel.add(searchButton);
buttonPanel.add(stopButton);
buttonPanel.add(viewButton);
buttonPanel.add(editButton);
buttonPanel.add(openButton);
buttonPanel.add(cancelButton);
stopButton.addActionListener(e -> {
searching = false;
stopButton.setEnabled(false);
statusLabel.setText("Cancelling...");
});
// Compose bottom area: status bar above buttons
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(statusPanel, BorderLayout.NORTH);
bottomPanel.add(buttonPanel, BorderLayout.SOUTH);
add(bottomPanel, BorderLayout.SOUTH);
// Set search button as default so Enter starts search
getRootPane().setDefaultButton(searchButton);
eastPanel.add(searchButton);
eastPanel.add(Box.createVerticalStrut(5));
eastPanel.add(stopButton);
add(eastPanel, BorderLayout.EAST);
// Status bar with progress and message
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);
// Bottom buttons panel
JPanel bottomButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
viewButton = new JButton("View");
viewButton.setEnabled(false);
viewButton.addActionListener(e -> viewSelectedFile());
editButton = new JButton("Edit");
editButton.setEnabled(false);
editButton.addActionListener(e -> editSelectedFile());
cancelButton = new JButton("Close");
cancelButton.addActionListener(e -> {
searching = false;
dispose();
});
JButton openButton = new JButton("Open location");
openButton.addActionListener(e -> openSelectedFile());
bottomButtonPanel.add(viewButton);
bottomButtonPanel.add(editButton);
bottomButtonPanel.add(openButton);
bottomButtonPanel.add(cancelButton);
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(statusPanel, BorderLayout.NORTH);
bottomPanel.add(bottomButtonPanel, BorderLayout.SOUTH);
add(bottomPanel, BorderLayout.SOUTH);
// Set search button as default so Enter starts search
getRootPane().setDefaultButton(searchButton);
// Require explicit Enter to start search when choosing from history.
// Bind Enter on the combo editor component so selecting an item from the
// popup does not automatically trigger search until user confirms.
@ -490,6 +560,12 @@ public class SearchDialog extends JDialog {
namePat = it != null ? it.toString().trim() : "";
} catch (Exception ex) { namePat = ""; }
String searchInDir = "";
try {
Object sit = searchInCombo.getEditor().getItem();
searchInDir = sit != null ? sit.toString().trim() : "";
} catch (Exception ex) { searchInDir = searchDirectory.getAbsolutePath(); }
String contentPat = "";
try {
Object cit = contentPatternCombo.getEditor().getItem();
@ -505,7 +581,17 @@ public class SearchDialog extends JDialog {
JOptionPane.WARNING_MESSAGE);
return;
}
File currentSearchDir = new File(searchInDir);
boolean searchEverything = everythingCheckBox != null && everythingCheckBox.isSelected();
if (!searchEverything && (!currentSearchDir.exists() || !currentSearchDir.isDirectory())) {
JOptionPane.showMessageDialog(this,
"Zadaný adresář neexistuje",
"Chyba",
JOptionPane.WARNING_MESSAGE);
return;
}
if (isContentSearch && contentPat.isEmpty()) {
JOptionPane.showMessageDialog(this,
"Zadejte hledaný text pro vyhledávání v obsahu",
@ -560,6 +646,18 @@ public class SearchDialog extends JDialog {
final boolean searchArchives = archiveSearchCheckBox != null && archiveSearchCheckBox.isSelected();
final boolean wholeWord = wholeWordCheckBox != null && wholeWordCheckBox.isSelected();
final boolean caseSensitive = caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected();
final boolean filenameIsRegex = regexCheckBox != null && regexCheckBox.isSelected();
final boolean contentIsRegex = regexContentCheckBox != null && regexContentCheckBox.isSelected();
int mDepth = -1;
if (subDirCombo != null) {
int idx = subDirCombo.getSelectedIndex();
if (idx == 1) mDepth = 0;
else if (idx == 2) mDepth = 1;
else if (idx == 3) mDepth = 2;
else if (idx == 4) mDepth = 3;
}
final int maxDepth = mDepth;
// Reset and show status
foundCount = 0;
@ -573,7 +671,7 @@ public class SearchDialog extends JDialog {
@Override
protected Void doInBackground() throws Exception {
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, wholeWord, caseSensitive, new FileOperations.SearchCallback() {
FileOperations.SearchCallback sc = new FileOperations.SearchCallback() {
@Override
public void onFileFound(File file, String virtualPath) {
publish(new Object[]{"file", file, virtualPath});
@ -588,7 +686,16 @@ public class SearchDialog extends JDialog {
public void onProgress(String status) {
publish(new Object[]{"progress", status});
}
});
};
if (searchEverything) {
for (File root : File.listRoots()) {
if (!searching) break;
FileOperations.search(root, finalNamePat, finalContentPat, maxDepth, searchArchives, wholeWord, caseSensitive, filenameIsRegex, contentIsRegex, sc);
}
} else {
FileOperations.search(currentSearchDir, finalNamePat, finalContentPat, maxDepth, searchArchives, wholeWord, caseSensitive, filenameIsRegex, contentIsRegex, sc);
}
return null;
}
@ -720,7 +827,8 @@ public class SearchDialog extends JDialog {
if (!contentPat.isEmpty()) {
FileEditor.setLastSearchOptions(contentPat,
wholeWordCheckBox != null && wholeWordCheckBox.isSelected(),
caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected());
caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected(),
regexContentCheckBox != null && regexContentCheckBox.isSelected());
}
FileEditor viewer = new FileEditor(owner, item.getFile(), vPath, config, true);
@ -766,7 +874,8 @@ public class SearchDialog extends JDialog {
if (!contentPat.isEmpty()) {
FileEditor.setLastSearchOptions(contentPat,
wholeWordCheckBox != null && wholeWordCheckBox.isSelected(),
caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected());
caseSensitiveCheckBox != null && caseSensitiveCheckBox.isSelected(),
regexContentCheckBox != null && regexContentCheckBox.isSelected());
}
FileEditor editor = new FileEditor(owner, item.getFile(), config, false);