OSDN Git Service

[#34645] add a feature which pastes abs-paths via DnD to ConsoleTextArea
[stew/Stew4.git] / src / net / argius / stew / ui / window / ConsoleTextArea.java
1 package net.argius.stew.ui.window;
2
3 import static java.awt.event.InputEvent.ALT_DOWN_MASK;
4 import static java.awt.event.KeyEvent.*;
5 import static javax.swing.KeyStroke.getKeyStroke;
6 import static net.argius.stew.ui.window.AnyActionKey.breakCommand;
7 import static net.argius.stew.ui.window.AnyActionKey.execute;
8 import static net.argius.stew.ui.window.ConsoleTextArea.ActionKey.*;
9 import java.awt.datatransfer.*;
10 import java.awt.dnd.*;
11 import java.awt.event.*;
12 import java.io.*;
13 import java.util.*;
14 import javax.swing.*;
15 import javax.swing.text.*;
16 import javax.swing.text.Highlighter.Highlight;
17 import javax.swing.text.Highlighter.HighlightPainter;
18 import javax.swing.undo.*;
19 import net.argius.stew.*;
20 import net.argius.stew.text.*;
21
22 /**
23  * The console style text area.
24  */
25 final class ConsoleTextArea extends JTextArea implements AnyActionListener, TextSearch {
26
27     enum ActionKey {
28         submit, copyOrBreak, addNewLine, jumpToHomePosition, outputMessage, insertText, doNothing;
29     }
30
31     private static final Logger log = Logger.getLogger(ConsoleTextArea.class);
32     private static final String BREAK_PROMPT = "[BREAK] > ";
33
34     private final AnyActionListener anyActionListener;
35     private final UndoManager undoManager;
36
37     private int homePosition;
38
39     ConsoleTextArea(AnyActionListener anyActionListener) {
40         // [Instances]
41         this.anyActionListener = anyActionListener;
42         this.undoManager = AnyAction.setUndoAction(this);
43         ((AbstractDocument)getDocument()).setDocumentFilter(new ConsoleTextAreaDocumentFilter());
44         // [Actions]
45         final int shortcutKey = Utilities.getMenuShortcutKeyMask();
46         AnyAction aa = new AnyAction(this);
47         aa.setUndoAction();
48         aa.bindSelf(submit, getKeyStroke(VK_ENTER, 0));
49         aa.bindSelf(copyOrBreak, getKeyStroke(VK_C, shortcutKey));
50         aa.bindSelf(breakCommand, getKeyStroke(VK_B, ALT_DOWN_MASK));
51         aa.bindSelf(addNewLine, getKeyStroke(VK_ENTER, shortcutKey));
52         aa.bindSelf(jumpToHomePosition, getKeyStroke(VK_HOME, 0));
53         // [Events]
54         class DropTargetAdapterImpl extends DropTargetAdapter {
55             @Override
56             public void drop(DropTargetDropEvent dtde) {
57                 Transferable t = dtde.getTransferable();
58                 if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
59                     dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
60                     try {
61                         StringBuilder buffer = new StringBuilder();
62                         @SuppressWarnings("unchecked")
63                         List<File> fileList = (List<File>)t.getTransferData(DataFlavor.javaFileListFlavor);
64                         for (File file : fileList) {
65                             buffer.append(file.getAbsolutePath()).append(" ");
66                         }
67                         append(buffer.toString());
68                     } catch (UnsupportedFlavorException ex) {
69                         throw new RuntimeException(ex);
70                     } catch (IOException ex) {
71                         throw new RuntimeException(ex);
72                     }
73                 }
74             }
75         }
76         setDropTarget(new DropTarget(this, new DropTargetAdapterImpl()));
77     }
78
79     private final class ConsoleTextAreaDocumentFilter extends DocumentFilter {
80
81         ConsoleTextAreaDocumentFilter() {
82         } // empty
83
84         @Override
85         public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
86             if (isEditablePosition(offset)) {
87                 super.insertString(fb, offset, string, attr);
88             }
89         }
90
91         @Override
92         public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
93             if (isEditablePosition(offset)) {
94                 super.remove(fb, offset, length);
95             }
96         }
97
98         @Override
99         public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
100             if (isEditablePosition(offset)) {
101                 super.replace(fb, offset, length, text, attrs);
102             }
103         }
104
105     }
106
107     @Override
108     public void anyActionPerformed(AnyActionEvent ev) {
109         log.atEnter("anyActionPerformed", ev);
110         if (ev.isAnyOf(submit)) {
111             final int ep = getEndPosition();
112             if (getCaretPosition() == ep) {
113                 anyActionListener.anyActionPerformed(new AnyActionEvent(this, execute));
114             } else {
115                 setCaretPosition(ep);
116             }
117         } else if (ev.isAnyOf(copyOrBreak)) {
118             if (getSelectedText() == null) {
119                 sendBreak();
120             } else {
121                 Action copyAction = new DefaultEditorKit.CopyAction();
122                 copyAction.actionPerformed(ev);
123             }
124         } else if (ev.isAnyOf(breakCommand)) {
125             sendBreak();
126         } else if (ev.isAnyOf(addNewLine)) {
127             insert("\n", getCaretPosition());
128         } else if (ev.isAnyOf(jumpToHomePosition)) {
129             setCaretPosition(getHomePosition());
130         } else if (ev.isAnyOf(insertText)) {
131             if (!isEditablePosition(getCaretPosition())) {
132                 setCaretPosition(getEndPosition());
133             }
134             replaceSelection(TextUtilities.join(" ", Arrays.asList(ev.getArgs())));
135             requestFocus();
136         } else if (ev.isAnyOf(outputMessage)) {
137             for (Object o : ev.getArgs()) {
138                 output(String.valueOf(o));
139             }
140         } else if (ev.isAnyOf(doNothing)) {
141             // do nothing
142         } else {
143             log.warn("not expected: Event=%s", ev);
144         }
145         log.atExit("anyActionPerformed");
146     }
147
148     boolean canUndo() {
149         return undoManager.canUndo();
150     }
151
152     boolean canRedo() {
153         return undoManager.canRedo();
154     }
155
156     /**
157      * Appends text.
158      * @param s
159      * @param movesCaretToEnd true if it moves Caret to the end, otherwise false
160      */
161     void append(String s, boolean movesCaretToEnd) {
162         super.append(s);
163         if (movesCaretToEnd) {
164             setCaretPosition(getEndPosition());
165         }
166     }
167
168     /**
169      * Outputs text.
170      * @param s
171      */
172     void output(String s) {
173         super.append(s);
174         undoManager.discardAllEdits();
175         homePosition = getEndPosition();
176         setCaretPosition(homePosition);
177     }
178
179     /**
180      * Replaces text from prompt to the end.
181      * @param s
182      */
183     void replace(String s) {
184         replaceRange(s, homePosition, getEndPosition());
185     }
186
187     /**
188      * Clears text.
189      */
190     void clear() {
191         homePosition = 0;
192         setText("");
193     }
194
195     /**
196      * Returns the text that is editable.
197      * @return
198      */
199     String getEditableText() {
200         try {
201             return getText(homePosition, getEndPosition() - homePosition);
202         } catch (BadLocationException ex) {
203             throw new RuntimeException(ex);
204         }
205     }
206
207     /**
208      * Tests whether the specified position is editable.
209      * @param position
210      * @return
211      */
212     boolean isEditablePosition(int position) {
213         return (position >= homePosition);
214     }
215
216     int getHomePosition() {
217         return homePosition;
218     }
219
220     int getEndPosition() {
221         Document document = getDocument();
222         Position position = document.getEndPosition();
223         return position.getOffset() - 1;
224     }
225
226     void resetHomePosition() {
227         undoManager.discardAllEdits();
228         homePosition = getEndPosition();
229     }
230
231     void sendBreak() {
232         append(BREAK_PROMPT);
233         resetHomePosition();
234         validate();
235     }
236
237     @Override
238     public void updateUI() {
239         if (getCaret() == null) {
240             super.updateUI();
241         } else {
242             final int p = getCaretPosition();
243             super.updateUI();
244             setCaretPosition(p);
245         }
246     }
247
248     // text search
249
250     @Override
251     public boolean search(Matcher matcher) {
252         removeHighlights();
253         try {
254             Highlighter highlighter = getHighlighter();
255             HighlightPainter painter = TextSearch.Matcher.getHighlightPainter();
256             final String text = getText();
257             int start = 0;
258             boolean matched = false;
259             while (matcher.find(text, start)) {
260                 matched = true;
261                 int matchedIndex = matcher.getStart();
262                 highlighter.addHighlight(matchedIndex, matcher.getEnd(), painter);
263                 start = matchedIndex + 1;
264             }
265             addKeyListener(new TextSearchKeyListener());
266             return matched;
267         } catch (BadLocationException ex) {
268             throw new RuntimeException(ex);
269         }
270     }
271
272     private final class TextSearchKeyListener extends KeyAdapter {
273         @Override
274         public void keyTyped(KeyEvent e) {
275             removeKeyListener(this);
276             removeHighlights();
277         }
278     }
279
280     @Override
281     public void reset() {
282         removeHighlights();
283     }
284
285     void removeHighlights() {
286         for (Highlight highlight : getHighlighter().getHighlights()) {
287             getHighlighter().removeHighlight(highlight);
288         }
289     }
290
291 }