Skip to content

Commit 5668e8f

Browse files
committed
Implement Rename by Pattern command
1 parent 79e51b2 commit 5668e8f

5 files changed

Lines changed: 101 additions & 70 deletions

File tree

‎src/main/java/sc/fiji/snt/PathManagerUI.java‎

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import java.util.concurrent.ExecutorService;
6565
import java.util.concurrent.Executors;
6666
import java.util.concurrent.Future;
67+
import java.util.regex.Matcher;
6768
import java.util.regex.Pattern;
6869
import java.util.stream.Collectors;
6970

@@ -155,9 +156,12 @@ public PathManagerUI(final SNT plugin) {
155156
navToolbar = new NavigationToolBar(tree, scrollPane);
156157
add(scrollPane, BorderLayout.CENTER);
157158

159+
// Search Bar TreeSearchable
160+
searchableBar = new PathManagerUISearchableBar(this);
161+
158162
// Create all the menu items:
159-
final SinglePathActionListener singlePathListener = new SinglePathActionListener();
160163
final MultiPathActionListener multiPathListener = new MultiPathActionListener();
164+
final SinglePathActionListener singlePathListener = new SinglePathActionListener(multiPathListener);
161165

162166
GuiUtils.removeIcon(this);
163167
menuBar = new JMenuBar();
@@ -168,9 +172,13 @@ public PathManagerUI(final SNT plugin) {
168172
editMenu.add(getDeleteMenuItem(multiPathListener));
169173
editMenu.add(getDuplicateMenuItem(singlePathListener));
170174
editMenu.add(getRenameMenuItem(singlePathListener));
175+
JMenuItem jmi = new JMenuItem(MultiPathActionListener.RENAME_BY_PATTERN_CMD, IconFactory.menuIcon('\ue521', true));
176+
editMenu.add(jmi);
177+
jmi.setToolTipText("Renames multiple paths using Find/Replace matching");
178+
jmi.addActionListener(multiPathListener);
171179
editMenu.addSeparator();
172180

173-
JMenuItem jmi = new JMenuItem(MultiPathActionListener.AUTO_CONNECT_CMD, IconFactory.menuIcon(IconFactory.GLYPH.LINK));
181+
jmi = new JMenuItem(MultiPathActionListener.AUTO_CONNECT_CMD, IconFactory.menuIcon(IconFactory.GLYPH.LINK));
174182
jmi.setToolTipText("<HTML>Connects 2 paths (parent-child) at an inferred fork point.<br>" +
175183
"Suggests parent based on path properties.");
176184
jmi.addActionListener(multiPathListener);
@@ -452,8 +460,7 @@ public PathManagerUI(final SNT plugin) {
452460
jmi.addActionListener(multiPathListener);
453461
advanced.add(jmi);
454462

455-
// Search Bar TreeSearchable
456-
searchableBar = new PathManagerUISearchableBar(this);
463+
457464
final JPopupMenu popup = new JPopupMenu();
458465
popup.putClientProperty("owner", this); // see SNTCommandFinder#revealMenuItem()
459466
popup.add(getDeleteMenuItem(multiPathListener));
@@ -2573,17 +2580,28 @@ private class SinglePathActionListener implements ActionListener {
25732580
private static final String EXPLORE_FIT_CMD = "Explore/Preview Fit";
25742581
private static final String STRAIGHTEN = "Straighten...";
25752582
private static final String NODE_PROFILER = "Node Profiler...";
2583+
private final MultiPathActionListener multiPathActionListener;
2584+
2585+
SinglePathActionListener(final MultiPathActionListener fallbackMultiPathActionListener) {
2586+
this.multiPathActionListener = fallbackMultiPathActionListener;
2587+
}
25762588

25772589
@Override
25782590
public void actionPerformed(final ActionEvent e) {
25792591

25802592
// Process nothing without a single path selection
2581-
final Collection<Path> selectedPaths = getSelectedPaths(false);
2582-
if (selectedPaths.size() != 1) {
2593+
final List<Path> selectedPaths = getSelectedPaths(false);
2594+
if (RENAME_CMD.equals(e.getActionCommand()) && selectedPaths.size() > 1) {
2595+
if (guiUtils.getConfirmation("This command accepts only one path. Run "
2596+
+ MultiPathActionListener.RENAME_BY_PATTERN_CMD + " instead?", "Single Path Command"))
2597+
multiPathActionListener.commands.get(MultiPathActionListener.RENAME_BY_PATTERN_CMD)
2598+
.execute(selectedPaths, MultiPathActionListener.RENAME_BY_PATTERN_CMD);
2599+
return;
2600+
} else if (selectedPaths.size() != 1) {
25832601
guiUtils.error("This command accepts only a single path. Please re-run after having only one path selected.");
25842602
return;
25852603
}
2586-
final Path p = selectedPaths.iterator().next();
2604+
final Path p = selectedPaths.getFirst();
25872605
switch (e.getActionCommand()) {
25882606
case RENAME_CMD -> {
25892607
final String s = guiUtils.getString(
@@ -2904,6 +2922,7 @@ private class MultiPathActionListener implements ActionListener {
29042922
private static final String DOWNSAMPLE_CMD = "Downsample...";
29052923
private static final String Z_CORRECTION_CMD = "Correct Z-Shrinkage...";
29062924
private static final String CUSTOM_TAG_CMD = "Other...";
2925+
private static final String RENAME_BY_PATTERN_CMD = "Rename by Pattern...";
29072926
private static final String REPLACE_TAG_CMD = "Replace...";
29082927
private static final String LENGTH_TAG_CMD = "Length";
29092928
private static final String MEAN_RADIUS_TAG_CMD = "Mean Radius";
@@ -3013,6 +3032,7 @@ private void initializeCommands() {
30133032
commands.put(DETECT_LABEL_PROXIMITY_CMD, new DetectLabelProximityCommand());
30143033

30153034
// Modification commands
3035+
commands.put(RENAME_BY_PATTERN_CMD, new RenameByPatternCommand());
30163036
commands.put(REVERSE_CMD, new ReverseCommand());
30173037
commands.put(CONCATENATE_CMD, new ConcatenateCommand());
30183038
commands.put(COMBINE_CMD, new CombineCommand());
@@ -3940,6 +3960,58 @@ public boolean canExecute(List<Path> selectedPaths) {
39403960
}
39413961
}
39423962

3963+
private class RenameByPatternCommand implements PathCommand {
3964+
@Override
3965+
public void execute(List<Path> selectedPaths, String cmd) {
3966+
final String[] labels = {"<HTML>Find", "<HTML>Replace&nbsp;"};
3967+
if (getSearchable().isCaseSensitive()) labels[0] += " <i>[Cc]</i> ";
3968+
if (getSearchable().isWildcardEnabled()) labels[0] += " <i>[.*]</i> ";
3969+
labels[0] += "&nbsp;";
3970+
final String[] defaults = {PathManagerUI.this.getSearchable().getSearchingText(), ""};
3971+
final String[] findReplace = guiUtils.getStrings(
3972+
String.format("Renaming %d Path(s) by Pattern", selectedPaths.size()),
3973+
labels, defaults,
3974+
"<HTML>Use <i>[Cc]</i> and <i>[.*]</i> in the search bar to refine matching");
3975+
if (findReplace == null || findReplace[0] == null || findReplace[0].isEmpty())
3976+
return; // user pressed cancel or chose no inputs
3977+
3978+
if (findReplace[1] == null || findReplace[1].isEmpty())
3979+
return; // nothing to replace
3980+
if (getSearchable().isWildcardEnabled()) {
3981+
findReplace[0] = findReplace[0].replaceAll("\\?", ".?");
3982+
findReplace[0] = findReplace[0].replaceAll("\\*", ".*");
3983+
}
3984+
if (!getSearchable().isCaseSensitive()) {
3985+
findReplace[0] = "(?i)" + findReplace[0];
3986+
}
3987+
int replacements = 0;
3988+
try {
3989+
final Pattern pattern = Pattern.compile(findReplace[0]);
3990+
for (final Path p : selectedPaths) {
3991+
final Matcher matcher = pattern.matcher(p.getName());
3992+
if (matcher.find()) {
3993+
replacements++;
3994+
p.setName(matcher.replaceAll(findReplace[1]));
3995+
}
3996+
}
3997+
if (replacements > 0) {
3998+
refreshManager(false, false, selectedPaths);
3999+
plugin.setUnsavedChanges(true);
4000+
guiUtils.floatingMsg(String.format("%d/%d path(s) renamed.", replacements, selectedPaths.size()), true);
4001+
} else {
4002+
guiUtils.error(String.format("None of the %d paths matched the specified pattern.", selectedPaths.size()));
4003+
}
4004+
} catch (final IllegalArgumentException ex) {
4005+
guiUtils.error("Replacement pattern invalid: " + ex.getMessage());
4006+
}
4007+
}
4008+
4009+
@Override
4010+
public boolean canExecute(List<Path> selectedPaths) {
4011+
return !selectedPaths.isEmpty();
4012+
}
4013+
}
4014+
39434015
private class SliceLabelTagCommand implements PathCommand {
39444016
@Override
39454017
public void execute(List<Path> selectedPaths, String cmd) {
@@ -5198,7 +5270,7 @@ private void removeTagsThroughPrompt(final List<Path> selectedPaths) {
51985270
private void replaceTags(final List<Path> selectedPaths) {
51995271
final String[] labels = { "Find (case-sensitive) ", "Replace with " };
52005272
final String[] defaults = { GuiUtils.toString(extractTagsFromPaths(selectedPaths)), "" };
5201-
final String[] findReplace = guiUtils.getStrings("Replace Tag(s)...", labels, defaults);
5273+
final String[] findReplace = guiUtils.getStrings("Replace Tag(s)...", labels, defaults, null);
52025274
if (findReplace == null || findReplace[0] == null || findReplace[0].isEmpty() || findReplace[1] == null)
52035275
return; // nothing to replace
52045276
findReplace[1] = findReplace[1].replace("[", "(").replace("]", ")")

‎src/main/java/sc/fiji/snt/gui/GuiUtils.java‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,8 @@ public String getString(final String promptMsg, final String promptTitle,
821821
return (String) getObj(promptMsg, promptTitle, defaultValue);
822822
}
823823

824-
public String[] getStrings(final String promptTitle, final String[] labels, final String[] defaultValues) {
824+
public String[] getStrings(final String promptTitle, final String[] labels, final String[] defaultValues,
825+
final String optionalMsg) {
825826
final JTextField[] fields = new JTextField[labels.length];
826827
final JPanel panel = new JPanel(new GridBagLayout());
827828
final GridBagConstraints c = new GridBagConstraints();
@@ -839,6 +840,13 @@ public String[] getStrings(final String promptTitle, final String[] labels, fina
839840
fields[i].setText(defaultValues[i]);
840841
panel.add(fields[i], c);
841842
}
843+
if (optionalMsg != null) {
844+
c.gridx = 0;
845+
c.gridy++;
846+
c.gridwidth = 2;
847+
c.insets.top += 10;
848+
panel.add(new JLabel(optionalMsg), c);
849+
}
842850
final int result = JOptionPane.showConfirmDialog(parent, panel, promptTitle, JOptionPane.OK_CANCEL_OPTION,
843851
JOptionPane.PLAIN_MESSAGE);
844852
if (result == JOptionPane.OK_OPTION) {

‎src/main/java/sc/fiji/snt/gui/PathManagerUISearchableBar.java‎

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import java.util.*;
3939
import java.util.List;
4040
import java.util.concurrent.ExecutionException;
41-
import java.util.regex.Pattern;
4241
import java.util.stream.Collectors;
4342
import java.util.stream.IntStream;
4443

@@ -66,7 +65,6 @@ public PathManagerUISearchableBar(final PathManagerUI pmui) {
6665
guiUtils = new GuiUtils(pmui);
6766
setGuiUtils(guiUtils);
6867
setSearchableObjectDescription("Paths");
69-
appendToOptionsMenu(List.of(createFindAndReplaceMenuItem()));
7068
_extraButtons = new ArrayList<>();
7169
_extraButtons.add(createSWCTypeFilteringButton());
7270
_extraButtons.add(createColorFilteringButton());
@@ -95,53 +93,6 @@ private void logSelectionCount(final int count) {
9593
_statusLabel.setText(count + " Match(es)");
9694
}
9795

98-
private JMenuItem createFindAndReplaceMenuItem() {
99-
final JMenuItem mi = new JMenuItem("Find and Replace...");
100-
mi.addActionListener(e -> {
101-
final String[] labels = { "<HTML>Find", "<HTML>Replace&nbsp;" };
102-
if (getSearchable().isCaseSensitive())
103-
labels[0] += " <i>[Cc]</i> ";
104-
if (getSearchable().isWildcardEnabled())
105-
labels[0] += " <i>[.*]</i> ";
106-
labels[0] += "&nbsp;";
107-
final String[] defaults = { getSearchingText(), "" };
108-
final String[] findReplace = guiUtils.getStrings("Replace by Pattern...", labels, defaults);
109-
if (findReplace == null || findReplace[0] == null || findReplace[0].isEmpty())
110-
return; // user pressed cancel or chose no inputs
111-
setSearchingText(findReplace[0]);
112-
final boolean clickOnHighlightAllNeeded = !isHighlightAll();
113-
if (clickOnHighlightAllNeeded)
114-
_highlightsButton.doClick();
115-
final Collection<Path> selectedPath = pmui.getSelectedPaths(false);
116-
if (selectedPath.isEmpty()) {
117-
guiUtils.error("No Paths matching '" + findReplace[0] + "'.", "No Paths Selected");
118-
return;
119-
}
120-
if (findReplace[1] == null || findReplace[1].isEmpty())
121-
return; // nothing to replace
122-
if (getSearchable().isWildcardEnabled()) {
123-
findReplace[0] = findReplace[0].replaceAll("\\?", ".?");
124-
findReplace[0] = findReplace[0].replaceAll("\\*", ".*");
125-
}
126-
if (!getSearchable().isCaseSensitive()) {
127-
findReplace[0] = "(?i)" + findReplace[0];
128-
}
129-
try {
130-
final Pattern pattern = Pattern.compile(findReplace[0]);
131-
for (final Path p : selectedPath) {
132-
p.setName(pattern.matcher(p.getName()).replaceAll(findReplace[1]));
133-
}
134-
pmui.update();
135-
} catch (final IllegalArgumentException ex) { // PatternSyntaxException etc.
136-
guiUtils.error("Replacement pattern not valid: " + ex.getMessage());
137-
} finally {
138-
if (clickOnHighlightAllNeeded)
139-
_highlightsButton.doClick(); // restore status
140-
}
141-
});
142-
return mi;
143-
}
144-
14596
private JMenu getImageFilterMenu() {
14697
final JMenu imgFilteringMenu = new JMenu("Select by Image Property");
14798
imgFilteringMenu.setIcon(IconFactory.menuIcon(

‎src/main/java/sc/fiji/snt/gui/SNTSearchableBar.java‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -296,23 +296,23 @@ private JPopupMenu createOptionsMenu() {
296296
historyMenu.setToolTipText("Press enter on a typed term to store it in this menu");
297297
popup.add(historyMenu);
298298
popup.addSeparator();
299+
final JMenuItem jcbmi4 = new JCheckBoxMenuItem("Live Search", getSearchable().getSearchingDelay()!=-1);
300+
jcbmi4.addItemListener(e -> getSearchable().setSearchingDelay((jcbmi4.isSelected())?DELAY_MS:-1));
301+
jcbmi4.setToolTipText("Search while typing without having to press Enter. May slow down searches");
302+
popup.add(jcbmi4);
299303
if ((getVisibleButtons() & SHOW_STATUS) != 0) {
300-
final JMenuItem jcbmi = new JCheckBoxMenuItem("Display No. of Matches", getSearchable().isCountMatch());
301-
jcbmi.setToolTipText("May adversely affect performance if selected");
304+
final JMenuItem jcbmi = new JCheckBoxMenuItem("Show Match Count", getSearchable().isCountMatch());
305+
jcbmi.setToolTipText("Display no. of matches. May slow down searches");
302306
jcbmi.addItemListener(e -> {
303307
setShowMatchCount(jcbmi.isSelected());
304308
updateSearch();
305309
});
306310
popup.add(jcbmi);
307311
}
308-
final JMenuItem jcbmi3 = new JCheckBoxMenuItem("Loop After First/Last Hit", getSearchable().isRepeats());
312+
final JMenuItem jcbmi3 = new JCheckBoxMenuItem("Wrap Around", getSearchable().isRepeats());
309313
jcbmi3.addItemListener(e -> getSearchable().setRepeats(jcbmi3.isSelected()));
310314
jcbmi3.setToolTipText("Return to first hit after last is selected?");
311315
popup.add(jcbmi3);
312-
final JMenuItem jcbmi4 = new JCheckBoxMenuItem("Non-interactive Search", getSearchable().getSearchingDelay()==-1);
313-
jcbmi4.addItemListener(e -> getSearchable().setSearchingDelay((jcbmi4.isSelected())?-1:DELAY_MS));
314-
jcbmi4.setToolTipText("Search only after Enter is pressed");
315-
popup.add(jcbmi4);
316316
popup.addSeparator();
317317
popup.add(getTipsAndShortcutsMenuItem());
318318
return popup;
@@ -352,10 +352,10 @@ private JMenuItem getTipsAndShortcutsMenuItem() {
352352
+ objectDescription + " filtered by the search term</li>";
353353
}
354354
msg += "<li>To improve search performance:</li><ul>" +
355-
"<li>Disable <i>Display No. of Matches</i></li>" +
356-
"<li>Enable <i>Non-interactive Search</i> (search starts only after pressing Enter)</li></ul>" +
355+
"<li>Disable <i>Live Search</i> (search starts only after pressing Enter)</li>" +
356+
"<li>Disable <i>Show Match Count</i></li></ul>" +
357357
"</ul></div></html>";
358-
getGuiUtils().centeredMsg(msg, "Text-based Filtering");
358+
getGuiUtils().centeredMsg(msg, "Text-Based Selections");
359359
});
360360
return mi2;
361361
}

‎src/main/java/sc/fiji/snt/viewer/Viewer3D.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4239,7 +4239,7 @@ boolean validPrompt(final String title, final String[] firstCcLabel, final Strin
42394239
values.add(prefs.getGuiPref("aColor", "red")); // color
42404240
values.add(prefs.getGuiPref("aTransparency", "50")); // transparency
42414241
final String[] result = guiUtils().getStrings(title, labels.toArray(new String[0]),
4242-
values.toArray(new String[0]));
4242+
values.toArray(new String[0]), null);
42434243
if (result == null)
42444244
return false;
42454245
try {
@@ -4908,7 +4908,7 @@ private JMenu axesMenu() {
49084908
final String[] defaults = { view.getAxisLayout().getXAxisLabel(), view.getAxisLayout().getYAxisLabel(),
49094909
view.getAxisLayout().getZAxisLabel() };
49104910
final String[] labels = mgrGuiUtils.getStrings("Axes Labels...",
4911-
new String[] { "X axis ", "Y axis ", "Z axis " }, defaults);
4911+
new String[] { "X axis ", "Y axis ", "Z axis " }, defaults, null);
49124912
if (labels != null)
49134913
setAxesLabels(labels);
49144914
});

0 commit comments

Comments
 (0)