Skip to content

Commit 331c64e

Browse files
committed
Ensure text-modifying actions are not enable when editing is locked
While at it re-organize menu commands, inlcuding popup menu
1 parent a621ea8 commit 331c64e

File tree

5 files changed

+131
-61
lines changed

5 files changed

+131
-61
lines changed

src/main/java/org/scijava/ui/swing/script/EditorPane.java

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.awt.Dimension;
3636
import java.awt.Font;
3737
import java.awt.event.ActionEvent;
38+
import java.awt.event.KeyEvent;
3839
import java.awt.event.MouseAdapter;
3940
import java.awt.event.MouseEvent;
4041
import java.io.BufferedReader;
@@ -61,6 +62,7 @@
6162
import javax.swing.JRadioButtonMenuItem;
6263
import javax.swing.JScrollPane;
6364
import javax.swing.JViewport;
65+
import javax.swing.KeyStroke;
6466
import javax.swing.SwingUtilities;
6567
import javax.swing.ToolTipManager;
6668
import javax.swing.UIManager;
@@ -78,9 +80,12 @@
7880
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
7981
import org.fife.ui.rsyntaxtextarea.Theme;
8082
import org.fife.ui.rsyntaxtextarea.parser.TaskTagParser;
83+
import org.fife.ui.rtextarea.ClipboardHistory;
8184
import org.fife.ui.rtextarea.Gutter;
8285
import org.fife.ui.rtextarea.GutterIconInfo;
8386
import org.fife.ui.rtextarea.RTextArea;
87+
import org.fife.ui.rtextarea.RTextAreaEditorKit;
88+
import org.fife.ui.rtextarea.RTextAreaEditorKit.CutAction;
8489
import org.fife.ui.rtextarea.RTextScrollPane;
8590
import org.fife.ui.rtextarea.RecordableTextAction;
8691
import org.fife.ui.rtextarea.SearchContext;
@@ -226,47 +231,69 @@ public void mousePressed(final MouseEvent me) {
226231
});
227232
}
228233

234+
protected boolean isLocked() {
235+
return !(isEditable() && isEnabled());
236+
}
237+
238+
@Override
239+
protected JPopupMenu createPopupMenu() {
240+
super.createPopupMenu();
241+
JPopupMenu popup = new JPopupMenu();
242+
TextEditor.addPopupMenuSeparator(popup, "Undo/Redo:");
243+
popup.add(getMenuItem("Undo", EditorPaneActions.rtaUndoAction, true));
244+
popup.add(getMenuItem("Redo", EditorPaneActions.rtaRedoAction, true));
245+
TextEditor.addPopupMenuSeparator(popup, "Code Editing:");
246+
popup.add(getMenuItem("Copy", EditorPaneActions.copyAction, false));
247+
popup.add(getMenuItem("Cut", EditorPaneActions.cutAction, true));
248+
popup.add(getMenuItem("Paste", EditorPaneActions.pasteAction, true));
249+
popup.add(getMenuItem("Delete Line", EditorPaneActions.rtaDeleteLineAction, true));
250+
popup.add(getMenuItem("Delete Rest of Line", EditorPaneActions.rtaDeleteRestOfLineAction, true));
251+
TextEditor.addPopupMenuSeparator(popup, "Code Selection:");
252+
popup.add(getMenuItem("Select All", EditorPaneActions.selectAllAction, false));
253+
popup.add(getMenuItem("Select Line", EditorPaneActions.selectLineAction, false));
254+
popup.add(getMenuItem("Select Paragraph", EditorPaneActions.selectParagraphAction, false));
255+
return popup;
256+
}
257+
229258
@Override
230259
protected void appendFoldingMenu(JPopupMenu popup) {
231260
// We are overriding the entire foldingMenu completely so that we can include
232261
// our shortcuts in the menu items. These commands are not listed on the
233262
// menubar, so this is the only access point in the GUI for these
234263
// popup.addSeparator();
235-
popup.add(getMenuItem("Select Line", EditorPaneActions.selectLineAction));
236-
popup.add(getMenuItem("Select Paragraph", EditorPaneActions.selectParagraphAction));
237264
TextEditor.addPopupMenuSeparator(popup, "Code Folding:");
238-
popup.add(getMenuItem("Collapse Fold", EditorPaneActions.rstaCollapseFoldAction));
239-
popup.add(getMenuItem("Expand Fold", EditorPaneActions.rstaExpandFoldAction));
240-
popup.add(getMenuItem("Toggle Current Fold", EditorPaneActions.rstaToggleCurrentFoldAction));
265+
popup.add(getMenuItem("Collapse Fold", EditorPaneActions.rstaCollapseFoldAction, false));
266+
popup.add(getMenuItem("Expand Fold", EditorPaneActions.rstaExpandFoldAction, false));
267+
popup.add(getMenuItem("Toggle Current Fold", EditorPaneActions.rstaToggleCurrentFoldAction, false));
241268
// popup.addSeparator();
242-
popup.add(getMenuItem("Collapse All Folds", EditorPaneActions.rstaCollapseAllFoldsAction));
243-
popup.add(getMenuItem("Expand All Folds", EditorPaneActions.rstaExpandAllFoldsAction));
269+
popup.add(getMenuItem("Collapse All Folds", EditorPaneActions.rstaCollapseAllFoldsAction, false));
270+
popup.add(getMenuItem("Expand All Folds", EditorPaneActions.rstaExpandAllFoldsAction, false));
244271
// popup.addSeparator();
245-
popup.add(getMenuItem("Collapse All Comments", EditorPaneActions.rstaCollapseAllCommentFoldsAction));
272+
popup.add(getMenuItem("Collapse All Comments", EditorPaneActions.rstaCollapseAllCommentFoldsAction, false));
246273
}
247274

248275
private void adjustPopupMenu() {
249276
final JPopupMenu popup = super.getPopupMenu();
250277
// See #appendFoldingMenu()
251278
TextEditor.addPopupMenuSeparator(popup, "Code Formatting:");
252-
popup.add(getMenuItem("Indent Right", EditorPaneActions.epaIncreaseIndentAction));
253-
popup.add(getMenuItem("Indent Left", EditorPaneActions.rstaDecreaseIndentAction));
279+
popup.add(getMenuItem("Indent Right", EditorPaneActions.epaIncreaseIndentAction, true));
280+
popup.add(getMenuItem("Indent Left", EditorPaneActions.rstaDecreaseIndentAction, true));
254281
//popup.addSeparator();
255-
popup.add(getMenuItem("Move Up", EditorPaneActions.rtaLineUpAction));
256-
popup.add(getMenuItem("Move Down", EditorPaneActions.rtaLineDownAction));
257-
popup.add(getMenuItem("Join Lines", EditorPaneActions.rtaJoinLinesAction));
282+
popup.add(getMenuItem("Move Up", EditorPaneActions.rtaLineUpAction, true));
283+
popup.add(getMenuItem("Move Down", EditorPaneActions.rtaLineDownAction, true));
284+
popup.add(getMenuItem("Join Lines", EditorPaneActions.rtaJoinLinesAction, true));
258285
JMenu menu = new JMenu("Transform Case");
259286
popup.add(menu);
260-
menu.add(getMenuItem("Invert Case", EditorPaneActions.rtaInvertSelectionCaseAction));
287+
menu.add(getMenuItem("Invert Case", EditorPaneActions.rtaInvertSelectionCaseAction, true));
261288
menu.addSeparator();
262-
menu.add(getMenuItem("Camel Case", EditorPaneActions.epaCamelCaseAction));
263-
menu.add(getMenuItem("Lower Case", EditorPaneActions.rtaLowerSelectionCaseAction));
264-
menu.add(getMenuItem("Lower Case ('_' Sep.)", EditorPaneActions.epaLowerCaseUndAction));
265-
menu.add(getMenuItem("Title Case", EditorPaneActions.epaTitleCaseAction));
266-
menu.add(getMenuItem("Upper Case", EditorPaneActions.rtaUpperSelectionCaseAction));
289+
menu.add(getMenuItem("Camel Case", EditorPaneActions.epaCamelCaseAction, true));
290+
menu.add(getMenuItem("Lower Case", EditorPaneActions.rtaLowerSelectionCaseAction, true));
291+
menu.add(getMenuItem("Lower Case ('_' Sep.)", EditorPaneActions.epaLowerCaseUndAction, true));
292+
menu.add(getMenuItem("Title Case", EditorPaneActions.epaTitleCaseAction, true));
293+
menu.add(getMenuItem("Upper Case", EditorPaneActions.rtaUpperSelectionCaseAction, true));
267294
TextEditor.addPopupMenuSeparator(popup, "Ocurrences:");
268-
popup.add(getMenuItem("Next Occurrence", EditorPaneActions.rtaNextOccurrenceAction));
269-
popup.add(getMenuItem("Previous Occurrence", EditorPaneActions.rtaPrevOccurrenceAction));
295+
popup.add(getMenuItem("Next Occurrence", EditorPaneActions.rtaNextOccurrenceAction, false));
296+
popup.add(getMenuItem("Previous Occurrence", EditorPaneActions.rtaPrevOccurrenceAction, false));
270297
TextEditor.addPopupMenuSeparator(popup, "Utilities:");
271298
popup.add(new OpenLinkUnderCursor().getMenuItem());
272299
popup.add(new SearchWebOnSelectedText().getMenuItem());
@@ -275,10 +302,19 @@ private void adjustPopupMenu() {
275302

276303
}
277304

278-
private JMenuItem getMenuItem(final String label, final String actionID) {
305+
private JMenuItem getMenuItem(final String label, final String actionID, final boolean editingAction) {
279306
final Action action = getActionMap().get(actionID);
280307
final JMenuItem jmi = new JMenuItem(action);
281308
jmi.setAccelerator(getPaneActions().getAccelerator(actionID));
309+
jmi.addActionListener(e -> {
310+
if (editingAction && isLocked()) {
311+
UIManager.getLookAndFeel().provideErrorFeedback(this);
312+
} else try {
313+
action.actionPerformed(e);
314+
} catch (final Exception | Error ignored) {
315+
UIManager.getLookAndFeel().provideErrorFeedback(this);
316+
}
317+
});
282318
jmi.setText(label);
283319
return jmi;
284320
}

src/main/java/org/scijava/ui/swing/script/EditorPaneActions.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ public EditorPaneActions(final EditorPane editorPane) {
6969

7070
final int defaultMod = RTextArea.getDefaultModifier();
7171
final int shift = InputEvent.SHIFT_DOWN_MASK;
72-
final int alt = InputEvent.ALT_DOWN_MASK;
7372
final InputMap map = editorPane.getInputMap();
7473

7574
/*
@@ -96,7 +95,6 @@ public EditorPaneActions(final EditorPane editorPane) {
9695
* Note that some of those bindings must be overridden in map.getParent()
9796
*/
9897

99-
10098
installCustomActions();
10199
}
102100

@@ -126,8 +124,7 @@ private void installCustomActions() {
126124

127125
// actions that are not registered by default
128126
map.put(rtaTimeDateAction, new TimeDateAction());
129-
if (map.get(clipboardHistoryAction) != null)
130-
map.put(clipboardHistoryAction, new ClipboardHistoryAction());
127+
map.put(clipboardHistoryAction, new ClipboardHistoryActionImpl());
131128
if (map.get(rstaCopyAsStyledTextAction) != null)
132129
map.put(rstaCopyAsStyledTextAction, new CopyAsStyledTextAction());
133130
if (map.get(rstaGoToMatchingBracketAction) != null)
@@ -138,7 +135,14 @@ private void installCustomActions() {
138135
public KeyStroke getAccelerator(final String actionID) {
139136
final Action action = editorPane.getActionMap().get(actionID);
140137
if (action == null) return null;
141-
for (KeyStroke key: editorPane.getInputMap().allKeys()) {
138+
// Pass 1: Current map, this should take precedence
139+
for (final KeyStroke key: editorPane.getInputMap().keys()) {
140+
if (actionID.equals(editorPane.getInputMap().get(key))) {
141+
return key;
142+
}
143+
}
144+
// Pass 2: All mappings, including parent map
145+
for (final KeyStroke key: editorPane.getInputMap().allKeys()) {
142146
if (actionID.equals(editorPane.getInputMap().get(key))) {
143147
return key;
144148
}
@@ -165,6 +169,23 @@ static class ToggleCommentAltAction extends ToggleCommentAction {
165169
}
166170
}
167171

172+
173+
/* Variant of original action that does not allow pasting if textArea is locked */
174+
static class ClipboardHistoryActionImpl extends ClipboardHistoryAction {
175+
176+
private static final long serialVersionUID = 1L;
177+
178+
@Override
179+
public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
180+
final boolean editingPossible = textArea.isEditable() && textArea.isEnabled();
181+
if (!editingPossible) {
182+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
183+
return;
184+
}
185+
super.actionPerformedImpl(e, textArea);
186+
}
187+
}
188+
168189
static class CamelCaseAction extends RecordableTextAction {
169190
private static final long serialVersionUID = 1L;
170191

src/main/java/org/scijava/ui/swing/script/FindAndReplaceDialog.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,14 @@ private JTextArea getSearchArea() {
143143
return getTextArea();
144144
}
145145

146-
final public RSyntaxTextArea getTextArea() {
146+
final private EditorPane getTextAreaAsEditorPane() {
147147
return textEditor.getEditorPane();
148148
}
149149

150+
final public RSyntaxTextArea getTextArea() {
151+
return getTextAreaAsEditorPane();
152+
}
153+
150154
@Override
151155
public void show(final boolean replace) {
152156
if (replace && restrictToConsole)
@@ -213,6 +217,10 @@ public void actionPerformed(final ActionEvent e) {
213217
if (source == findNext) searchOrReplace(false);
214218
else if (source == replace) searchOrReplace(true);
215219
else if (source == replaceAll) {
220+
if (getTextAreaAsEditorPane().isLocked()) {
221+
JOptionPane.showMessageDialog(this, "File is currently locked.");
222+
return;
223+
}
216224
final int replace =
217225
SearchEngine.replaceAll(getTextArea(), getSearchContext(true))
218226
.getCount();

src/main/java/org/scijava/ui/swing/script/TextEditor.java

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -351,24 +351,15 @@ public TextEditor(final Context context) {
351351
saveas = addToMenu(file, "Save As...", KeyEvent.VK_S, ctrl + shift);
352352
saveas.setMnemonic(KeyEvent.VK_A);
353353
file.addSeparator();
354-
makeJar = addToMenu(file, "Export as JAR", 0, 0);
354+
makeJar = addToMenu(file, "Export as JAR...", 0, 0);
355355
makeJar.setMnemonic(KeyEvent.VK_E);
356-
makeJarWithSource = addToMenu(file, "Export as JAR (With Source)", 0, 0);
356+
makeJarWithSource = addToMenu(file, "Export as JAR (With Source)...", 0, 0);
357357
makeJarWithSource.setMnemonic(KeyEvent.VK_X);
358358
file.addSeparator();
359-
final JCheckBoxMenuItem lock = new JCheckBoxMenuItem("Lock (Make Read Only)");
360-
file.add(lock);
361-
lock.addActionListener( e -> {
362-
if (lock.isSelected()) {
363-
new SetReadOnlyAction().actionPerformedImpl(e, getTextArea());
364-
} else {
365-
new SetWritableAction().actionPerformedImpl(e, getTextArea());
366-
}
367-
});
368-
JMenuItem jmi = new JMenuItem("Revert");
359+
JMenuItem jmi = new JMenuItem("Revert...");
369360
jmi.addActionListener(e -> {
370-
if (lock.isSelected()) {
371-
error("File is locked (read only).");
361+
if (getEditorPane().isLocked()) {
362+
error("File is currently locked.");
372363
return;
373364
}
374365
final File f = getEditorPane().getFile();
@@ -406,8 +397,7 @@ public TextEditor(final Context context) {
406397

407398
// -- Language menu --
408399

409-
languageMenuItems =
410-
new LinkedHashMap<>();
400+
languageMenuItems = new LinkedHashMap<>();
411401
final Set<Integer> usedShortcuts = new HashSet<>();
412402
final JMenu languages = new JMenu("Language");
413403
languages.setMnemonic(KeyEvent.VK_L);
@@ -877,9 +867,9 @@ private void assembleEditMenu() {
877867
selectAll = addToMenu(editMenu, "Select All", KeyEvent.VK_A, ctrl);
878868
cut = addToMenu(editMenu, "Cut", KeyEvent.VK_X, ctrl);
879869
copy = addToMenu(editMenu, "Copy", KeyEvent.VK_C, ctrl);
880-
addMappedActionToMenu(editMenu, "Copy as Styled Text", EditorPaneActions.rstaCopyAsStyledTextAction);
870+
addMappedActionToMenu(editMenu, "Copy as Styled Text", EditorPaneActions.rstaCopyAsStyledTextAction, false);
881871
paste = addToMenu(editMenu, "Paste", KeyEvent.VK_V, ctrl);
882-
addMappedActionToMenu(editMenu, "Paste from History...", EditorPaneActions.clipboardHistoryAction);
872+
addMappedActionToMenu(editMenu, "Paste from History...", EditorPaneActions.clipboardHistoryAction, true);
883873
addMenubarSeparator(editMenu, "Find:");
884874
find = addToMenu(editMenu, "Find/Replace...", KeyEvent.VK_F, ctrl);
885875
find.setMnemonic(KeyEvent.VK_F);
@@ -891,7 +881,7 @@ private void assembleEditMenu() {
891881
addMenubarSeparator(editMenu, "Goto:");
892882
gotoLine = addToMenu(editMenu, "Goto Line...", KeyEvent.VK_G, ctrl);
893883
gotoLine.setMnemonic(KeyEvent.VK_G);
894-
addMappedActionToMenu(editMenu, "Goto Matching Bracket", EditorPaneActions.rstaGoToMatchingBracketAction);
884+
addMappedActionToMenu(editMenu, "Goto Matching Bracket", EditorPaneActions.rstaGoToMatchingBracketAction, false);
895885

896886
final JMenuItem gotoType = new JMenuItem("Goto Type...");
897887
gotoType.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, ctrl + shift)); // default is Ctrl+Shift+O
@@ -907,10 +897,10 @@ private void assembleEditMenu() {
907897
editMenu.add(gotoType);
908898

909899
addMenubarSeparator(editMenu, "Bookmarks:");
910-
addMappedActionToMenu(editMenu, "Next Bookmark", EditorPaneActions.rtaNextBookmarkAction);
911-
addMappedActionToMenu(editMenu, "Previous Bookmark", EditorPaneActions.rtaPrevBookmarkAction);
900+
addMappedActionToMenu(editMenu, "Next Bookmark", EditorPaneActions.rtaNextBookmarkAction, false);
901+
addMappedActionToMenu(editMenu, "Previous Bookmark", EditorPaneActions.rtaPrevBookmarkAction, false);
912902
final JMenuItem toggB = addMappedActionToMenu(editMenu, "Toggle Bookmark",
913-
EditorPaneActions.rtaToggleBookmarkAction);
903+
EditorPaneActions.rtaToggleBookmarkAction, false);
914904
toggB.setToolTipText("Alternatively, click on left bookmark gutter near the line number");
915905
final JMenuItem listBookmarks = addToMenu(editMenu, "List Bookmarks...", 0, 0);
916906
listBookmarks.setMnemonic(KeyEvent.VK_L);
@@ -920,11 +910,22 @@ private void assembleEditMenu() {
920910

921911
addMenubarSeparator(editMenu, "Utilities:");
922912
final JMenuItem commentJMI = addMappedActionToMenu(editMenu, "Toggle Comment",
923-
EditorPaneActions.rstaToggleCommentAction);
913+
EditorPaneActions.rstaToggleCommentAction, true);
924914
commentJMI.setToolTipText("Alternative shortcut: "
925915
+ getEditorPane().getPaneActions().getAcceleratorLabel(EditorPaneActions.epaToggleCommentAltAction));
926-
addMappedActionToMenu(editMenu, "Insert Time Stamp", EditorPaneActions.rtaTimeDateAction);
916+
addMappedActionToMenu(editMenu, "Insert Time Stamp", EditorPaneActions.rtaTimeDateAction, true);
927917
removeTrailingWhitespace = addToMenu(editMenu, "Remove Trailing Whitespace", 0, 0);
918+
final JCheckBoxMenuItem lock = new JCheckBoxMenuItem("Lock Editing");
919+
editMenu.add(lock);
920+
lock.addActionListener(e -> {
921+
for (int i = 0; i < tabbed.getTabCount(); i++) {
922+
if (lock.isSelected()) {
923+
new SetReadOnlyAction().actionPerformedImpl(e, getEditorPane(i));
924+
} else {
925+
new SetWritableAction().actionPerformedImpl(e, getEditorPane(i));
926+
}
927+
}
928+
});
928929
zapGremlins = addToMenu(editMenu, "Zap Gremlins", 0, 0);
929930
zapGremlins.setToolTipText("Removes invalid (non-printable) ASCII characters");
930931
}
@@ -1385,15 +1386,19 @@ public JMenuItem addToMenu(final JMenu menu, final String menuEntry,
13851386
return item;
13861387
}
13871388

1388-
private JMenuItem addMappedActionToMenu(final JMenu menu, String label, String actionID) {
1389+
private JMenuItem addMappedActionToMenu(final JMenu menu, String label, String actionID, final boolean editingAction) {
13891390
final JMenuItem jmi = new JMenuItem(label);
13901391
jmi.addActionListener(e -> {
1391-
if (RTextAreaEditorKit.clipboardHistoryAction.equals(actionID)
1392-
&& ClipboardHistory.get().getHistory().isEmpty()) {
1393-
warn("The internal clipboard manager is empty.");
1394-
return;
1395-
}
13961392
try {
1393+
if (editingAction && getEditorPane().isLocked()) {
1394+
warn("File is currently locked.");
1395+
return;
1396+
}
1397+
if (RTextAreaEditorKit.clipboardHistoryAction.equals(actionID)
1398+
&& ClipboardHistory.get().getHistory().isEmpty()) {
1399+
warn("The internal clipboard manager is empty.");
1400+
return;
1401+
}
13971402
getTextArea().getActionMap().get(actionID).actionPerformed(e);
13981403
} catch (final Exception | Error ignored) {
13991404
error("\"" + label + "\" not availabe for current scripting language.");
@@ -1973,10 +1978,10 @@ public void findOrReplace(final boolean doReplace) {
19731978

19741979
// override search pattern only if
19751980
// there is sth. selected
1976-
final String selection = getTextArea().getSelectedText();
1981+
final String selection = getEditorPane().getSelectedText();
19771982
if (selection != null) findDialog.setSearchPattern(selection);
19781983

1979-
findDialog.show(doReplace);
1984+
findDialog.show(doReplace && !getEditorPane().isLocked());
19801985
}
19811986

19821987
public void gotoLine() {

src/main/java/org/scijava/ui/swing/script/TextEditorTab.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public void dragEnter(final DropTargetDragEvent e) {
155155
bc.weightx = 0;
156156
bc.weighty = 0;
157157
bc.anchor = GridBagConstraints.NORTHWEST;
158-
bc.fill = GridBagConstraints.NONE;
158+
bc.fill = GridBagConstraints.HORIZONTAL;
159159
runit = new JButton("Run");
160160
runit.setToolTipText("Control+R, F5, or F11");
161161
runit.addActionListener(ae -> textEditor.runText());

0 commit comments

Comments
 (0)