8000 Implement "Clipboard manager", "Goto Type" and several utility commands: · scijava/script-editor@8a138e8 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 8a138e8

Browse files
committed
Implement "Clipboard manager", "Goto Type" and several utility commands:
- Lock/Unlock file - Comment/Uncomment Selection - Copy as styled text - Insert Time Stamp - Case transformation: (lower/upper/camel case) - Moving selections (line up/down, indent, de-indent)
1 parent 63aab2b commit 8a138e8

File tree

2 files changed

+264
-19
lines changed

2 files changed

+264
-19
lines changed

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

Lines changed: 207 additions & 18 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,44 @@
5050
import java.util.Collection;
5151
import java.util.List;
5252

53+
import javax.swing.Action;
5354
import javax.swing.ImageIcon;
55+
import javax.swing.JMenu;
56+
import javax.swing.JMenuItem;
5457
import javax.swing.JOptionPane;
58+
import javax.swing.JPopupMenu;
5559
import javax.swing.JScrollPane;
5660
import javax.swing.JViewport;
61+
import javax.swing.KeyStroke;
5762
import javax.swing.ToolTipManager;
63+
import javax.swing.UIManager;
5864
import javax.swing.event.DocumentEvent;
5965
import javax.swing.event.DocumentListener;
6066
import javax.swing.event.HyperlinkEvent;
6167
import javax.swing.event.HyperlinkListener;
6268
import javax.swing.text.BadLocationException;
69+
import javax.swing.text.Caret;
6370
import javax.swing.text.DefaultEditorKit;
71+
import javax.swing.text.Document;
72+
import javax.swing.text.Element;
73+
import javax.swing.text.Segment;
6474

6575
import org.fife.rsta.ac.LanguageSupport;
6676
import org.fife.rsta.ac.LanguageSupportFactory;
6777
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
6878
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
79+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
80+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.CopyAsStyledTextAction;
81+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.ToggleCommentAction;
82+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit.DecreaseIndentAction;
83+
import org.fife.ui.rtextarea.RTextAreaEditorKit.*;
6984
import org.fife.ui.rsyntaxtextarea.Style;
7085
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
7186
import org.fife.ui.rsyntaxtextarea.Theme;
7287
import org.fife.ui.rtextarea.Gutter;
7388
import org.fife.ui.rtextarea.GutterIconInfo;
7489
import org.fife.ui.rtextarea.RTextArea;
90+
import org.fife.ui.rtextarea.RTextAreaEditorKit;
7591
import org.fife.ui.rtextarea.RTextScrollPane;
7692
import org.fife.ui.rtextarea.RecordableTextAction;
7793
import org.fife.ui.rtextarea.SearchContext;
@@ -155,21 +171,28 @@ public void hyperlinkUpdate(final HyperlinkEvent hle) {
155171
}
156172
}
157173
});
158-
174+
159175
// load preferences
160176
loadPreferences();
161177

162-
getActionMap()
163-
.put(DefaultEditorKit.nextWordAction, wordMovement(+1, false));
164-
getActionMap().put(DefaultEditorKit.selectionNextWordAction,
165-
wordMovement(+1, true));
166-
getActionMap().put(DefaultEditorKit.previousWordAction,
167-
wordMovement(-1, false));
178+
// Register recordable actions
179+
getActionMap().put(DefaultEditorKit.nextWordAction, wordMovement("Next-Word-Action", +1, false));
180+
getActionMap().put(DefaultEditorKit.selectionNextWordAction, wordMovement("Next-Word-Select-Action", +1, true));
181+
getActionMap().put(DefaultEditorKit.previousWordAction, wordMovement("Prev-Word-Action", -1, false));
168182
getActionMap().put(DefaultEditorKit.selectionPreviousWordAction,
169-
wordMovement(-1, true));
183+
wordMovement("Prev-Word-Select-Action", -1, true));
184+
getActionMap().put(RTextAreaEditorKit.rtaTimeDateAction, new TimeDateAction());
185+
if (getActionMap().get(RTextAreaEditorKit.clipboardHistoryAction) != null)
186+
getActionMap().put(RTextAreaEditorKit.clipboardHistoryAction, new ClipboardHistoryAction());
187+
if (getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction) != null)
188+
getActionMap().put(RSyntaxTextAreaEditorKit.rstaToggleCommentAction, new ToggleCommentAction());
189+
if (getActionMap().get(RSyntaxTextAreaEditorKit.rstaCopyAsStyledTextAction) != null)
190+
getActionMap().put(RSyntaxTextAreaEditorKit.rstaCopyAsStyledTextAction, new CopyAsStyledTextAction());
191+
192+
adjustPopupMenu();
193+
170194
ToolTipManager.sharedInstance().registerComponent(this);
171195
getDocument().addDocumentListener(this);
172-
173196
addMouseListener(new MouseAdapter() {
174197

175198
SearchContext context;
@@ -207,6 +230,32 @@ public void mousePressed(final MouseEvent me) {
207230
});
208231
}
209232

233+
private void adjustPopupMenu() {
234+
final JPopupMenu popup = super.getPopupMenu();
235+
JMenu menu = new JMenu("Move");
236+
popup.add(menu);
237+
menu.add(getMenuItem("Decrease Indent", new DecreaseIndentAction()));
238+
menu.add(getMenuItem("Increase Indent", new IncreaseIndentAction()));
239+
menu.addSeparator();
240+
menu.add(getMenuItem("Move Up", new LineMoveAction(RTextAreaEditorKit.rtaLineUpAction, -1)));
241+
menu.add(getMenuItem("Move Down", new LineMoveAction(RTextAreaEditorKit.rtaLineDownAction, 1)));
242+
menu = new JMenu("Transform");
243+
popup.add(menu);
244+
menu.add(getMenuItem("Camel Case", new CamelCaseAction()));
245+
menu.add(getMenuItem("Invert Case", new InvertSelectionCaseAction()));
246+
menu.add(getMenuItem("Lower Case", new LowerSelectionCaseAction()));
247+
menu.add(getMenuItem("Upper Case", new UpperSelectionCaseAction()));
248+
}
249+
250+
private JMenuItem getMenuItem(final String label, final RecordableTextAction a) {
251+
JMenuItem item = new JMenuItem(a);
252+
item.setAccelerator((KeyStroke) a.getValue(Action.ACCELERATOR_KEY));
253+
if (getActionMap().get(a.getName()) == null)
254+
getActionMap().put(a.getName(), a); // make it recordable
255+
item.setText(label);
256+
return item;
257+
}
258+
210259
@Override
211260
public void setTabSize(final int width) {
212261
if (getTabSize() != width) super.setTabSize(width);
@@ -261,16 +310,21 @@ private ImageIcon createBookmarkIcon() {
261310
return new ImageIcon(image);
262311
}
263312

264-
/**
265-
* TODO
266-
*
267-
* @param direction
268-
* @param select
269-
* @return
270-
*/
271-
RecordableTextAction wordMovement(final int direction, final boolean select) {
272-
final String id = "WORD_MOVEMENT_" + select + direction;
313+
RecordableTextAction wordMovement(final String id, final int direction, final boolean select) {
273314
return new RecordableTextAction(id) {
315+
private static final long serialVersionUID = 1L;
316+
317+
@Override
318+
public String getDescription() {
319+
final StringBuilder sb = new StringBuilder();
320+
if (direction > 0)
321+
sb.append("Next");
322+
else
323+
sb.append("Previous");
324+
sb.append("Word");
325+
if (select) sb.append("Select");
326+
return sb.toString();
327+
}
274328

275329
@Override
276330
public void actionPerformedImpl(final ActionEvent e,
@@ -1007,4 +1061,139 @@ String getSupportStatus() {
10071061
return supportStatus;
10081062
}
10091063

1064+
static class CamelCaseAction extends RecordableTextAction {
1065+
private static final long serialVersionUID = 1L;
1066+
1067+
CamelCaseAction() {
1068+
super("RTA.CamelCaseAction");
1069+
}
1070+
1071+
@Override
1072+
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1073+
if (!textArea.isEditable() || !textArea.isEnabled()) {
1074+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1075+
return;
1076+
}
1077+
final String selection = textArea.getSelectedText();
1078+
if (selection != null) {
1079+
final String[] words = selection.split("[\\W_]+");
1080+
final StringBuilder buffer = new StringBuilder();
1081+
for (int i = 0; i < words.length; i++) {
1082+
String word = words[i];
1083+
if (i == 0) {
1084+
word = word.isEmpty() ? word : word.toLowerCase();
1085+
} else {
1086+
word = word.isEmpty() ? word
1087+
: Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase();
1088+
}
1089+
buffer.append(word);
1090+
}
1091+
textArea.replaceSelection(buffer.toString());
1092+
}
1093+
textArea.requestFocusInWindow();
1094+
}
1095+
1096+
@Override
1097+
public String getMacroID() {
1098+
return getName();
1099+
}
1100+
1101+
}
1102+
1103+
/** Modified from DecreaseIndentAction */
1104+
static class IncreaseIndentAction extends RecordableTextAction {
1105+
1106+
private static final long serialVersionUID = 1L;
1107+
1108+
private final Segment s;
1109+
1110+
public IncreaseIndentAction() {
1111+
super("RSTA.IncreaseIndentAction");
1112+
s = new Segment();
1113+
}
1114+
1115+
@Override
1116+
public void actionPerformedImpl(final ActionEvent e, final RTextArea textArea) {
1117+
1118+
if (!textArea.isEditable() || !textArea.isEnabled()) {
1119+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1120+
return;
1121+
}
1122+
1123+
final Document document = textArea.getDocument();
1124+
final Element map = document.getDefaultRootElement();
1125+
final Caret c = textArea.getCaret();
1126+
int dot = c.getDot();
1127+
int mark = c.getMark();
1128+
int line1 = map.getElementIndex(dot);
1129+
final int tabSize = textArea.getTabSize();
1130+
final StringBuilder sb = new StringBuilder();
1131+
if (textArea.getTabsEmulated()) {
1132+
while (sb.length() < tabSize) {
1133+
sb.append(' ');
1134+
}
1135+
} else {
1136+
sb.append('\t');
1137+
}
1138+
final String paddingString = sb.toString();
1139+
1140+
// If there is a selection, indent all lines in the selection.
1141+
// Otherwise, indent the line the caret is on.
1142+
if (dot != mark) {
1143+
final int line2 = map.getElementIndex(mark);
1144+
dot = Math.min(line1, line2);
1145+
mark = Math.max(line1, line2);
1146+
Element elem;
1147+
textArea.beginAtomicEdit();
1148+
try {
1149+
for (line1 = dot; line1 < mark; line1++) {
1150+
elem = map.getElement(line1);
1151+
handleIncreaseIndent(elem, document, paddingString);
1152+
}
1153+
// Don't do the last line if the caret is at its
1154+
// beginning. We must call getDot() again and not just
1155+
// use 'dot' as the caret's position may have changed
1156+
// due to the insertion of the tabs above.
1157+
elem = map.getElement(mark);
1158+
final int start = elem.getStartOffset();
1159+
if (Math.max(c.getDot(), c.getMark()) != start) {
1160+
handleIncreaseIndent(elem, document, paddingString);
1161+
}
1162+
} catch (final BadLocationException ble) {
1163+
ble.printStackTrace();
1164+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1165+
} finally {
1166+
textArea.endAtomicEdit();
1167+
}
1168+
} else {
1169+
final Element elem = map.getElement(line1);
1170+
try {
1171+
handleIncreaseIndent(elem, document, paddingString);
1172+
} catch (final BadLocationException ble) {
1173+
ble.printStackTrace();
1174+
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1175+
}
1176+
}
1177+
1178+
}
1179+
1180+
@Override
1181+
public final String getMacroID() {
1182+
return getName();
1183+
}
1184+
1185+
private void handleIncreaseIndent(final Element elem, final Document doc, final String pad)
1186+
throws BadLocationException {
1187+
final int start = elem.getStartOffset();
1188+
int end = elem.getEndOffset() - 1; // Why always true??
1189+
doc.getText(start, end - start, s);
1190+
final int i = s.offset;
1191+
end = i + s.count;
1192+
if (end > i || (end == i && i == 0)) {
1193+
doc.insertString(start, pad, null);
1194+
}
1195+
}
1196+
1197+
}
1198+
10101199
}

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,12 @@
102102
import javax.script.ScriptException;
103103
import javax.swing.AbstractAction;
104104
import javax.swing.AbstractButton;
105+
import javax.swing.Action;
106+
import javax.swing.ActionMap;
105107
import javax.swing.BorderFactory;
106108
import javax.swing.BoxLayout;
107109
import javax.swing.ButtonGroup;
110+
import javax.swing.InputMap;
108111
import javax.swing.JButton;
109112
import javax.swing.JCheckBoxMenuItem;
110113
import javax.swing.JComponent;
@@ -136,7 +139,13 @@
136139

137140
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
138141
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
142+
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
139143
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
144+
import org.fife.ui.rtextarea.ClipboardHistory;
145+
import org.fife.ui.rtextarea.RTextAreaEditorKit;
146+
import org.fife.ui.rtextarea.RTextAreaEditorKit.SetReadOnlyAction;
147+
import org.fife.ui.rtextarea.RTextAreaEditorKit.SetWritableAction;
148+
import org.fife.ui.rtextarea.RecordableTextAction;
140149
import org.scijava.Context;
141150
import org.scijava.app.AppService;
142151
import org.scijava.batch.BatchService;
@@ -335,6 +344,16 @@ public TextEditor(final Context context) {
335344
makeJarWithSource = addToMenu(file, "Export as JAR (With Source)", 0, 0);
336345
makeJarWithSource.setMnemonic(KeyEvent.VK_X);
337346
file.addSeparator();
347+
final JCheckBoxMenuItem lock = new JCheckBoxMenuItem("Lock File (Make Read Only)");
348+
file.add(lock);
349+
lock.addActionListener( e -> {
350+
if (lock.isSelected()) {
351+
new SetReadOnlyAction().actionPerformedImpl(e, getTextArea());
352+
} else {
353+
new SetWritableAction().actionPerformedImpl(e, getTextArea());
354+
}
355+
});
356+
file.addSeparator();
338357
final JMenuItem jmi = new JMenuItem("Show in System Explorer");
339358
jmi.addActionListener(e -> {
340359
final File f = getEditorPane().getFile();
@@ -364,7 +383,12 @@ public TextEditor(final Context context) {
364383
selectAll = addToMenu(edit, "Select All", KeyEvent.VK_A, ctrl);
365384
cut = addToMenu(edit, "Cut", KeyEvent.VK_X, ctrl);
366385
copy = addToMenu(edit, "Copy", KeyEvent.VK_C, ctrl);
386+
addMappedActionToMenu(edit, "Copy as Styled Text",
387+
RSyntaxTextAreaEditorKit.rstaCopyAsStyledTextAction);
367388
paste = addToMenu(edit, "Paste", KeyEvent.VK_V, ctrl);
389+
final JMenuItem clipHistory = addMappedActionToMenu(edit, "Paste from History...",
390+
RTextAreaEditorKit.clipboardHistoryAction);
391+
clipHistory.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ctrl + shift));
368392
addSeparator(edit, "Find:");
369393
find = addToMenu(edit, "Find/Replace...", KeyEvent.VK_F, ctrl);
370394
find.setMnemonic(KeyEvent.VK_F);
@@ -377,6 +401,17 @@ public TextEditor(final Context context) {
377401
gotoLine = addToMenu(edit, "Goto Line...", KeyEvent.VK_G, ctrl);
378402
gotoLine.setMnemonic(KeyEvent.VK_G);
379403

404+
final JMenuItem gotoType = new JMenuItem("Goto Type...");
405+
gotoType.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, ctrl + shift)); // default is Ctrl+Shift+O
406+
gotoType.addActionListener(e -> {
407+
try {
408+
getTextArea().getActionMap().get("GoToType").actionPerformed(e);
409+
} catch (final Exception | Error ignored) {
410+
error("\"Goto Type\" not availabe for current scripting language.");
411+
}
412+
});
413+
edit.add(gotoType);
414+
380415
final JMenuItem toggleBookmark = addToMenu(edit, "Toggle Bookmark", KeyEvent.VK_B, ctrl);
381416
toggleBookmark.setMnemonic(KeyEvent.VK_B);
382417
toggleBookmark.addActionListener( e -> toggleBookmark());
@@ -387,8 +422,11 @@ public TextEditor(final Context context) {
387422
clearBookmarks.addActionListener(e -> clearAllBookmarks());
388423

389424
addSeparator(edit, "Utilities:");
425+
final JMenuItem commentJMI = addMappedActionToMenu(edit, "Comment/Uncomment Selection",
426+
RSyntaxTextAreaEditorKit.rstaToggleCommentAction);
427+
commentJMI.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, ctrl + shift));
428+
addMappedActionToMenu(edit, "Insert Time Stamp", RTextAreaEditorKit.rtaTimeDateAction);
390429
removeTrailingWhitespace = addToMenu(edit, "Remove Trailing Whitespace", 0, 0);
391-
removeTrailingWhitespace.setMnemonic(KeyEvent.VK_W);
392430
zapGremlins = addToMenu(edit, "Zap Gremlins", 0, 0);
393431
zapGremlins.setToolTipText("Removes invalid (non-printable) ASCII characters");
394432

@@ -1136,6 +1174,24 @@ public JMenuItem addToMenu(final JMenu menu, final String menuEntry,
11361174
return item;
11371175
}
11381176

1177+
private JMenuItem addMappedActionToMenu(final JMenu menu, String label, String actionID) {
1178+
final JMenuItem jmi = new JMenuItem(label);
1179+
jmi.addActionListener(e -> {
1180+
if (RTextAreaEditorKit.clipboardHistoryAction.equals(actionID)
1181+
&& ClipboardHistory.get().getHistory().isEmpty()) {
1182+
warn("The internal clipboard manager is empty.");
1183+
return;
1184+
}
1185+
try {
1186+
getTextArea().getActionMap().get(actionID).actionPerformed(e);
1187+
} catch (final Exception | Error ignored) {
1188+
error("\"" + label + "\" not availabe for current scripting language.");
1189+
}
1190+
});
1191+
menu.add(jmi);
1192+
return jmi;
1193+
}
1194+
11391195
protected static class AcceleratorTriplet {
11401196

11411197
JMenuItem component;

0 commit comments

Comments
 (0)
0