Skip to content

Commit bdf65bf

Browse files
committed
Improve file locking and Javadocs
Fix: Some editable actions remained available with locked panes Fix: File lock not being set on a pane-by-pane basis Other minor improvements: Javadocs: see https://forum.image.sc/t/shiny-new-script-editor/64160/23 Minor code cleanup
1 parent ea599ab commit bdf65bf

File tree

2 files changed

+73
-82
lines changed

2 files changed

+73
-82
lines changed

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

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import java.awt.Dimension;
3636
import java.awt.Font;
3737
import java.awt.event.ActionEvent;
38-
import java.awt.event.KeyEvent;
3938
import java.awt.event.MouseAdapter;
4039
import java.awt.event.MouseEvent;
4140
import java.io.BufferedReader;
@@ -62,8 +61,6 @@
6261
import javax.swing.JRadioButtonMenuItem;
6362
import javax.swing.JScrollPane;
6463
import javax.swing.JViewport;
65-
import javax.swing.KeyStroke;
66-
import javax.swing.SwingUtilities;
6764
import javax.swing.ToolTipManager;
6865
import javax.swing.UIManager;
6966
import javax.swing.event.DocumentEvent;
@@ -80,12 +77,9 @@
8077
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
8178
import org.fife.ui.rsyntaxtextarea.Theme;
8279
import org.fife.ui.rsyntaxtextarea.parser.TaskTagParser;
83-
import org.fife.ui.rtextarea.ClipboardHistory;
8480
import org.fife.ui.rtextarea.Gutter;
8581
import org.fife.ui.rtextarea.GutterIconInfo;
8682
import org.fife.ui.rtextarea.RTextArea;
87-
import org.fife.ui.rtextarea.RTextAreaEditorKit;
88-
import org.fife.ui.rtextarea.RTextAreaEditorKit.CutAction;
8983
import org.fife.ui.rtextarea.RTextScrollPane;
9084
import org.fife.ui.rtextarea.RecordableTextAction;
9185
import org.fife.ui.rtextarea.SearchContext;
@@ -106,6 +100,7 @@
106100
*
107101
* @author Johannes Schindelin
108102
* @author Jonathan Hale
103+
* @author Tiago Ferreira
109104
*/
110105
public class EditorPane extends RSyntaxTextArea implements DocumentListener {
111106

@@ -189,7 +184,7 @@ public void hyperlinkUpdate(final HyperlinkEvent hle) {
189184
wordMovement("Prev-Word-Select-Action", -1, true));
190185

191186
noneLangSyntaxMenu = geSyntaxForNoneLang();
192-
adjustPopupMenu();
187+
installCostumPopupMenu();
193188

194189
ToolTipManager.sharedInstance().registerComponent(this);
195190
addMouseListener(new MouseAdapter() {
@@ -241,13 +236,12 @@ public void addNotify() {
241236
if (isVisible()) super.addNotify();
242237
}
243238

244-
@Override
245-
protected JPopupMenu createPopupMenu() {
246-
super.createPopupMenu();
247-
JPopupMenu popup = new JPopupMenu();
248-
TextEditor.addPopupMenuSeparator(popup, "Undo/Redo:");
249-
popup.add(getMenuItem("Undo", EditorPaneActions.rtaUndoAction, true));
250-
popup.add(getMenuItem("Redo", EditorPaneActions.rtaRedoAction, true));
239+
private void installCostumPopupMenu() {
240+
// We are overriding the entire menu so that we can include our shortcuts
241+
// in the menu items. These commands are not listed on the menubar, so
242+
// this is the only access point in the GUI for these
243+
// see #createPopupMenu(); #appendFoldingMenu();
244+
final JPopupMenu popup = new JPopupMenu();
251245
TextEditor.addPopupMenuSeparator(popup, "Code Editing:");
252246
popup.add(getMenuItem("Copy", EditorPaneActions.copyAction, false));
253247
popup.add(getMenuItem("Cut", EditorPaneActions.cutAction, true));
@@ -258,33 +252,16 @@ protected JPopupMenu createPopupMenu() {
258252
popup.add(getMenuItem("Select All", EditorPaneActions.selectAllAction, false));
259253
popup.add(getMenuItem("Select Line", EditorPaneActions.selectLineAction, false));
260254
popup.add(getMenuItem("Select Paragraph", EditorPaneActions.selectParagraphAction, false));
261-
return popup;
262-
}
263-
264-
@Override
265-
protected void appendFoldingMenu(JPopupMenu popup) {
266-
// We are overriding the entire foldingMenu completely so that we can include
267-
// our shortcuts in the menu items. These commands are not listed on the
268-
// menubar, so this is the only access point in the GUI for these
269-
// popup.addSeparator();
270255
TextEditor.addPopupMenuSeparator(popup, "Code Folding:");
271256
popup.add(getMenuItem("Collapse Fold", EditorPaneActions.rstaCollapseFoldAction, false));
272257
popup.add(getMenuItem("Expand Fold", EditorPaneActions.rstaExpandFoldAction, false));
273258
popup.add(getMenuItem("Toggle Current Fold", EditorPaneActions.rstaToggleCurrentFoldAction, false));
274-
// popup.addSeparator();
275259
popup.add(getMenuItem("Collapse All Folds", EditorPaneActions.rstaCollapseAllFoldsAction, false));
276260
popup.add(getMenuItem("Expand All Folds", EditorPaneActions.rstaExpandAllFoldsAction, false));
277-
// popup.addSeparator();
278261
popup.add(getMenuItem("Collapse All Comments", EditorPaneActions.rstaCollapseAllCommentFoldsAction, false));
279-
}
280-
281-
private void adjustPopupMenu() {
282-
final JPopupMenu popup = super.getPopupMenu();
283-
// See #appendFoldingMenu()
284262
TextEditor.addPopupMenuSeparator(popup, "Code Formatting:");
285263
popup.add(getMenuItem("Indent Right", EditorPaneActions.epaIncreaseIndentAction, true));
286264
popup.add(getMenuItem("Indent Left", EditorPaneActions.rstaDecreaseIndentAction, true));
287-
//popup.addSeparator();
288265
popup.add(getMenuItem("Move Up", EditorPaneActions.rtaLineUpAction, true));
289266
popup.add(getMenuItem("Move Down", EditorPaneActions.rtaLineDownAction, true));
290267
popup.add(getMenuItem("Join Lines", EditorPaneActions.rtaJoinLinesAction, true));
@@ -303,9 +280,8 @@ private void adjustPopupMenu() {
303280
TextEditor.addPopupMenuSeparator(popup, "Utilities:");
304281
popup.add(new OpenLinkUnderCursor().getMenuItem());
305282
popup.add(new SearchWebOnSelectedText().getMenuItem());
306-
//popup.addSeparator();
307283
popup.add(noneLangSyntaxMenu);
308-
284+
super.setPopupMenu(popup);
309285
}
310286

311287
private JMenuItem getMenuItem(final String label, final String actionID, final boolean editingAction) {
@@ -317,8 +293,8 @@ private JMenuItem getMenuItem(final String label, final String actionID, final b
317293
UIManager.getLookAndFeel().provideErrorFeedback(this);
318294
} else try {
319295
action.actionPerformed(e);
320-
} catch (final Exception | Error ignored) {
321-
UIManager.getLookAndFeel().provideErrorFeedback(this);
296+
} catch (final Exception | Error ex) {
297+
log.debug(ex);
322298
}
323299
});
324300
jmi.setText(label);
@@ -1158,7 +1134,7 @@ String getSupportStatus() {
11581134
return supportStatus;
11591135
}
11601136

1161-
void openLinkInBrowser(String link) {
1137+
void openLinkInBrowser(String link) {
11621138
try {
11631139
if (!link.startsWith("http"))
11641140
link = "https://" + link; // or it won't work
@@ -1186,10 +1162,6 @@ class SearchWebOnSelectedText extends RecordableTextAction {
11861162

11871163
@Override
11881164
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1189-
if (!textArea.isEditable() || !textArea.isEnabled()) {
1190-
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1191-
return;
1192-
}
11931165
final String selection = textArea.getSelectedText();
11941166
if (selection == null) {
11951167
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
@@ -1223,10 +1195,6 @@ class OpenLinkUnderCursor extends RecordableTextAction {
12231195

12241196
@Override
12251197
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1226-
if (!textArea.isEditable() || !textArea.isEnabled()) {
1227-
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1228-
return;
1229-
}
12301198
String link = new CursorUtils(textArea).getLinkAtCursor();
12311199
if (link == null) {
12321200
UIManager.getLookAndFeel().provideErrorFeedback(textArea);

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

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,17 @@ public TextEditor(final Context context) {
356356
makeJarWithSource = addToMenu(file, "Export as JAR (With Source)...", 0, 0);
357357
makeJarWithSource.setMnemonic(KeyEvent.VK_X);
358358
file.addSeparator();
359+
final JCheckBoxMenuItem lockPane = new JCheckBoxMenuItem("Lock (Make Read Only)");
360+
lockPane.setToolTipText("Protects file from accidental editing");
361+
file.add(lockPane);
362+
lockPane.addActionListener(e -> {
363+
if (lockPane.isSelected()) {
364+
new SetReadOnlyAction().actionPerformedImpl(e, getEditorPane());
365+
} else {
366+
new SetWritableAction().actionPerformedImpl(e, getEditorPane());
367+
}
368+
});
369+
tabbed.addChangeListener(e -> lockPane.setSelected(getTab(tabbed.getSelectedIndex()).editorPane.isLocked()));
359370
JMenuItem jmi = new JMenuItem("Revert...");
360371
jmi.addActionListener(e -> {
361372
if (getEditorPane().isLocked()) {
@@ -915,17 +926,6 @@ private void assembleEditMenu() {
915926
+ getEditorPane().getPaneActions().getAcceleratorLabel(EditorPaneActions.epaToggleCommentAltAction));
916927
addMappedActionToMenu(editMenu, "Insert Time Stamp", EditorPaneActions.rtaTimeDateAction, true);
917928
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-
});
929929
zapGremlins = addToMenu(editMenu, "Zap Gremlins", 0, 0);
930930
zapGremlins.setToolTipText("Removes invalid (non-printable) ASCII characters");
931931
}
@@ -934,6 +934,10 @@ private void addScritpEditorMacroCommands(final JMenu menu) {
934934
addMenubarSeparator(menu, "Script Editor Macros:");
935935
final JMenuItem startMacro = new JMenuItem("Start/Resume Macro Recording");
936936
startMacro.addActionListener(e -> {
937+
if (getEditorPane().isLocked()) {
938+
error("File is currently locked.");
939+
return;
940+
}
937941
final String state = (RTextArea.getCurrentMacro() == null) ? "on" : "resumed";
938942
write("Script Editor: Macro recording " + state);
939943
RTextArea.beginRecordingMacro();
@@ -984,6 +988,10 @@ private void addScritpEditorMacroCommands(final JMenu menu) {
984988
final JMenuItem playMacro = new JMenuItem("Run Recorded Macro");
985989
playMacro.setToolTipText("Runs current recordings. Prompts for\nrecordings file if no recordings exist");
986990
playMacro.addActionListener(e -> {
991+
if (getEditorPane().isLocked()) {
992+
error("File is currently locked.");
993+
return;
994+
}
987995
if (null == RTextArea.getCurrentMacro()) {
988996
final File fileToOpen = getMacroFile(true);
989997
if (fileToOpen != null) {
@@ -1683,11 +1691,7 @@ else if (source == close) if (tabbed.getTabCount() < 2) processWindowEvent(new W
16831691
if (index > 0) index--;
16841692
switchTo(index);
16851693
}
1686-
else if (source == cut) getTextArea().cut();
16871694
else if (source == copy) getTextArea().copy();
1688-
else if (source == paste) getTextArea().paste();
1689-
else if (source == undo) getTextArea().undoLastAction();
1690-
else if (source == redo) getTextArea().redoLastAction();
16911695
else if (source == find) findOrReplace(true);
16921696
else if (source == findNext) {
16931697
findDialog.setRestrictToConsole(false);
@@ -1708,21 +1712,6 @@ else if (source == chooseFontSize) {
17081712
else if (source == chooseTabSize) {
17091713
commandService.run(ChooseTabSize.class, true, "editor", this);
17101714
}
1711-
else if (source == addImport) {
1712-
addImport(getSelectedClassNameOrAsk("Add import (complete qualified name of class/package)",
1713-
"Which Class to Import?"));
1714-
}
1715-
else if (source == removeUnusedImports) new TokenFunctions(getTextArea())
1716-
.removeUnusedImports();
1717-
else if (source == sortImports) new TokenFunctions(getTextArea())
1718-
.sortImports();
1719-
else if (source == removeTrailingWhitespace) new TokenFunctions(
1720-
getTextArea()).removeTrailingWhitespace();
1721-
else if (source == replaceTabsWithSpaces) getTextArea()
1722-
.convertTabsToSpaces();
1723-
else if (source == replaceSpacesWithTabs) getTextArea()
1724-
.convertSpacesToTabs();
1725-
else if (source == zapGremlins) zapGremlins();
17261715
else if (source == openClassOrPackageHelp) openClassOrPackageHelp(null);
17271716
else if (source == extractSourceJar) extractSourceJar();
17281717
else if (source == openSourceForClass) {
@@ -1771,6 +1760,36 @@ else if (source == increaseFontSize || source == decreaseFontSize) {
17711760
else if (source == nextTab) switchTabRelative(1);
17721761
else if (source == previousTab) switchTabRelative(-1);
17731762
else if (handleTabsMenu(source)) return;
1763+
else {
1764+
// commands that should not run when files are locked!
1765+
if (getEditorPane().isLocked()) {
1766+
error("File is currently locked.");
1767+
return;
1768+
}
1769+
if (source == cut)
1770+
getTextArea().cut();
1771+
else if (source == paste)
1772+
getTextArea().paste();
1773+
else if (source == undo)
1774+
getTextArea().undoLastAction();
1775+
else if (source == redo)
1776+
getTextArea().redoLastAction();
1777+
else if (source == addImport) {
1778+
addImport(getSelectedClassNameOrAsk("Add import (complete qualified name of class/package)",
1779+
"Which Class to Import?"));
1780+
} else if (source == removeUnusedImports)
1781+
new TokenFunctions(getTextArea()).removeUnusedImports();
1782+
else if (source == sortImports)
1783+
new TokenFunctions(getTextArea()).sortImports();
1784+
else if (source == removeTrailingWhitespace)
1785+
new TokenFunctions(getTextArea()).removeTrailingWhitespace();
1786+
else if (source == replaceTabsWithSpaces)
1787+
getTextArea().convertTabsToSpaces();
1788+
else if (source == replaceSpacesWithTabs)
1789+
getTextArea().convertSpacesToTabs();
1790+
else if (source == zapGremlins)
1791+
zapGremlins();
1792+
}
17741793
}
17751794

17761795
private void setAutoCompletionEnabled(final boolean enabled) {
@@ -2098,20 +2117,24 @@ public static boolean isBinary(final File file) {
20982117
}
20992118

21002119
/**
2101-
* Open a new tab with some content; the languageExtension is like ".java",
2102-
* ".py", etc.
2120+
* Opens a new tab with some content.
2121+
*
2122+
* @param content the script content
2123+
* @param languageExtension the file extension associated with the content's
2124+
* language (e.g., ".java", ".py", etc.
2125+
* @return the text editor tab
21032126
*/
2104-
public TextEditorTab newTab(final String content, final String language) {
2105-
String lang = language;
2127+
public TextEditorTab newTab(final String content, final String languageExtension) {
2128+
String lang = languageExtension;
21062129
final TextEditorTab tab = open(null);
21072130
if (null != lang && lang.length() > 0) {
21082131
lang = lang.trim().toLowerCase();
21092132

21102133
if ('.' != lang.charAt(0)) {
2111-
lang = "." + language;
2134+
lang = "." + languageExtension;
21122135
}
21132136

2114-
tab.editorPane.setLanguage(scriptService.getLanguageByName(language));
2137+
tab.editorPane.setLanguage(scriptService.getLanguageByName(languageExtension));
21152138
} else {
21162139
final String lastLanguageName = prefService.get(getClass(), LAST_LANGUAGE);
21172140
if (null != lastLanguageName && "none" != lastLanguageName)
@@ -2189,11 +2212,11 @@ public TextEditorTab open(final File file) {
21892212
}
21902213
catch (final FileNotFoundException e) {
21912214
log.error(e);
2192-
error("The file '" + file + "' was not found.");
2215+
error("The file\n'" + file + "' was not found.");
21932216
}
21942217
catch (final Exception e) {
21952218
log.error(e);
2196-
error("There was an error while opening '" + file + "': " + e);
2219+
error("There was an error while opening\n'" + file + "': " + e);
21972220
}
21982221
return null;
21992222
}

0 commit comments

Comments
 (0)