svn-gvsig-desktop / tags / v1_0_2_Build_912 / libraries / libIverUtiles / src / com / iver / utiles / console / jedit / JEditTextArea.java @ 11422
History | View | Annotate | Download (48.4 KB)
1 | 4890 | caballero | package com.iver.utiles.console.jedit; |
---|---|---|---|
2 | /*
|
||
3 | * JEditTextArea.java - jEdit's text component
|
||
4 | * Copyright (C) 1999 Slava Pestov
|
||
5 | *
|
||
6 | * You may use and modify this package for any purpose. Redistribution is
|
||
7 | * permitted, in both source and binary form, provided that this notice
|
||
8 | * remains intact in all source distributions of this package.
|
||
9 | */
|
||
10 | |||
11 | 8765 | jjdelcerro | import java.awt.AWTEvent; |
12 | import java.awt.Component; |
||
13 | import java.awt.Container; |
||
14 | import java.awt.Dimension; |
||
15 | import java.awt.Font; |
||
16 | import java.awt.FontMetrics; |
||
17 | import java.awt.Insets; |
||
18 | import java.awt.LayoutManager; |
||
19 | import java.awt.Toolkit; |
||
20 | import java.awt.datatransfer.Clipboard; |
||
21 | import java.awt.datatransfer.DataFlavor; |
||
22 | import java.awt.datatransfer.StringSelection; |
||
23 | import java.awt.event.ActionEvent; |
||
24 | import java.awt.event.ActionListener; |
||
25 | import java.awt.event.AdjustmentEvent; |
||
26 | import java.awt.event.AdjustmentListener; |
||
27 | import java.awt.event.ComponentAdapter; |
||
28 | import java.awt.event.ComponentEvent; |
||
29 | import java.awt.event.FocusEvent; |
||
30 | import java.awt.event.FocusListener; |
||
31 | import java.awt.event.InputEvent; |
||
32 | import java.awt.event.KeyEvent; |
||
33 | import java.awt.event.MouseAdapter; |
||
34 | import java.awt.event.MouseEvent; |
||
35 | import java.awt.event.MouseMotionListener; |
||
36 | 4890 | caballero | import java.util.Enumeration; |
37 | import java.util.Vector; |
||
38 | |||
39 | 8765 | jjdelcerro | import javax.swing.JComponent; |
40 | import javax.swing.JPopupMenu; |
||
41 | import javax.swing.JScrollBar; |
||
42 | import javax.swing.SwingUtilities; |
||
43 | import javax.swing.Timer; |
||
44 | import javax.swing.event.CaretEvent; |
||
45 | import javax.swing.event.CaretListener; |
||
46 | import javax.swing.event.DocumentEvent; |
||
47 | import javax.swing.event.DocumentListener; |
||
48 | import javax.swing.event.EventListenerList; |
||
49 | import javax.swing.text.BadLocationException; |
||
50 | import javax.swing.text.Document; |
||
51 | import javax.swing.text.Element; |
||
52 | import javax.swing.text.Segment; |
||
53 | import javax.swing.text.Utilities; |
||
54 | import javax.swing.undo.AbstractUndoableEdit; |
||
55 | import javax.swing.undo.CannotRedoException; |
||
56 | import javax.swing.undo.CannotUndoException; |
||
57 | import javax.swing.undo.UndoableEdit; |
||
58 | |||
59 | 4890 | caballero | /**
|
60 | * jEdit's text area component. It is more suited for editing program
|
||
61 | * source code than JEditorPane, because it drops the unnecessary features
|
||
62 | * (images, variable-width lines, and so on) and adds a whole bunch of
|
||
63 | * useful goodies such as:
|
||
64 | * <ul>
|
||
65 | * <li>More flexible key binding scheme
|
||
66 | * <li>Supports macro recorders
|
||
67 | * <li>Rectangular selection
|
||
68 | * <li>Bracket highlighting
|
||
69 | * <li>Syntax highlighting
|
||
70 | * <li>Command repetition
|
||
71 | * <li>Block caret can be enabled
|
||
72 | * </ul>
|
||
73 | * It is also faster and doesn't have as many problems. It can be used
|
||
74 | * in other applications; the only other part of jEdit it depends on is
|
||
75 | * the syntax package.<p>
|
||
76 | *
|
||
77 | * To use it in your app, treat it like any other component, for example:
|
||
78 | * <pre>JEditTextArea ta = new JEditTextArea();
|
||
79 | * ta.setTokenMarker(new JavaTokenMarker());
|
||
80 | * ta.setText("public class Test {\n"
|
||
81 | * + " public static void main(String[] args) {\n"
|
||
82 | * + " System.out.println(\"Hello World\");\n"
|
||
83 | * + " }\n"
|
||
84 | * + "}");</pre>
|
||
85 | *
|
||
86 | * @author Slava Pestov
|
||
87 | * @version $Id$
|
||
88 | */
|
||
89 | 4963 | caballero | public class JEditTextArea extends JComponent |
90 | 4890 | caballero | { |
91 | /**
|
||
92 | * Adding components with this name to the text area will place
|
||
93 | * them left of the horizontal scroll bar. In jEdit, the status
|
||
94 | * bar is added this way.
|
||
95 | */
|
||
96 | public static String LEFT_OF_SCROLLBAR = "los"; |
||
97 | |||
98 | /**
|
||
99 | * Creates a new JEditTextArea with the default settings.
|
||
100 | */
|
||
101 | public JEditTextArea()
|
||
102 | { |
||
103 | this(TextAreaDefaults.getDefaults());
|
||
104 | } |
||
105 | |||
106 | /**
|
||
107 | * Creates a new JEditTextArea with the specified settings.
|
||
108 | * @param defaults The default settings
|
||
109 | */
|
||
110 | public JEditTextArea(TextAreaDefaults defaults)
|
||
111 | { |
||
112 | // Enable the necessary events
|
||
113 | enableEvents(AWTEvent.KEY_EVENT_MASK);
|
||
114 | |||
115 | // Initialize some misc. stuff
|
||
116 | painter = new TextAreaPainter(this,defaults); |
||
117 | documentHandler = new DocumentHandler();
|
||
118 | listenerList = new EventListenerList(); |
||
119 | caretEvent = new MutableCaretEvent();
|
||
120 | lineSegment = new Segment(); |
||
121 | bracketLine = bracketPosition = -1;
|
||
122 | blink = true;
|
||
123 | |||
124 | // Initialize the GUI
|
||
125 | setLayout(new ScrollLayout());
|
||
126 | add(CENTER,painter); |
||
127 | add(RIGHT,vertical = new JScrollBar(JScrollBar.VERTICAL)); |
||
128 | add(BOTTOM,horizontal = new JScrollBar(JScrollBar.HORIZONTAL)); |
||
129 | |||
130 | // Add some event listeners
|
||
131 | vertical.addAdjustmentListener(new AdjustHandler());
|
||
132 | horizontal.addAdjustmentListener(new AdjustHandler());
|
||
133 | painter.addComponentListener(new ComponentHandler());
|
||
134 | painter.addMouseListener(new MouseHandler());
|
||
135 | painter.addMouseMotionListener(new DragHandler());
|
||
136 | addFocusListener(new FocusHandler());
|
||
137 | |||
138 | // Load the defaults
|
||
139 | setInputHandler(defaults.inputHandler); |
||
140 | setDocument(defaults.document); |
||
141 | editable = defaults.editable; |
||
142 | caretVisible = defaults.caretVisible; |
||
143 | caretBlinks = defaults.caretBlinks; |
||
144 | electricScroll = defaults.electricScroll; |
||
145 | |||
146 | popup = defaults.popup; |
||
147 | |||
148 | // We don't seem to get the initial focus event?
|
||
149 | focusedComponent = this;
|
||
150 | } |
||
151 | |||
152 | /**
|
||
153 | * Returns if this component can be traversed by pressing
|
||
154 | * the Tab key. This returns false.
|
||
155 | */
|
||
156 | public final boolean isManagingFocus() |
||
157 | { |
||
158 | return true; |
||
159 | } |
||
160 | |||
161 | /**
|
||
162 | * Returns the object responsible for painting this text area.
|
||
163 | */
|
||
164 | public final TextAreaPainter getPainter() |
||
165 | { |
||
166 | return painter;
|
||
167 | } |
||
168 | |||
169 | /**
|
||
170 | * Returns the input handler.
|
||
171 | */
|
||
172 | public final InputHandler getInputHandler() |
||
173 | { |
||
174 | return inputHandler;
|
||
175 | } |
||
176 | |||
177 | /**
|
||
178 | * Sets the input handler.
|
||
179 | * @param inputHandler The new input handler
|
||
180 | */
|
||
181 | public void setInputHandler(InputHandler inputHandler) |
||
182 | { |
||
183 | this.inputHandler = inputHandler;
|
||
184 | } |
||
185 | |||
186 | /**
|
||
187 | * Returns true if the caret is blinking, false otherwise.
|
||
188 | */
|
||
189 | public final boolean isCaretBlinkEnabled() |
||
190 | { |
||
191 | return caretBlinks;
|
||
192 | } |
||
193 | |||
194 | /**
|
||
195 | * Toggles caret blinking.
|
||
196 | * @param caretBlinks True if the caret should blink, false otherwise
|
||
197 | */
|
||
198 | public void setCaretBlinkEnabled(boolean caretBlinks) |
||
199 | { |
||
200 | this.caretBlinks = caretBlinks;
|
||
201 | if(!caretBlinks)
|
||
202 | blink = false;
|
||
203 | |||
204 | painter.invalidateSelectedLines(); |
||
205 | } |
||
206 | |||
207 | /**
|
||
208 | * Returns true if the caret is visible, false otherwise.
|
||
209 | */
|
||
210 | public final boolean isCaretVisible() |
||
211 | { |
||
212 | return (!caretBlinks || blink) && caretVisible;
|
||
213 | } |
||
214 | |||
215 | /**
|
||
216 | * Sets if the caret should be visible.
|
||
217 | * @param caretVisible True if the caret should be visible, false
|
||
218 | * otherwise
|
||
219 | */
|
||
220 | public void setCaretVisible(boolean caretVisible) |
||
221 | { |
||
222 | this.caretVisible = caretVisible;
|
||
223 | blink = true;
|
||
224 | |||
225 | painter.invalidateSelectedLines(); |
||
226 | } |
||
227 | |||
228 | /**
|
||
229 | * Blinks the caret.
|
||
230 | */
|
||
231 | public final void blinkCaret() |
||
232 | { |
||
233 | if(caretBlinks)
|
||
234 | { |
||
235 | blink = !blink; |
||
236 | painter.invalidateSelectedLines(); |
||
237 | } |
||
238 | else
|
||
239 | blink = true;
|
||
240 | } |
||
241 | |||
242 | /**
|
||
243 | * Returns the number of lines from the top and button of the
|
||
244 | * text area that are always visible.
|
||
245 | */
|
||
246 | public final int getElectricScroll() |
||
247 | { |
||
248 | return electricScroll;
|
||
249 | } |
||
250 | |||
251 | /**
|
||
252 | * Sets the number of lines from the top and bottom of the text
|
||
253 | * area that are always visible
|
||
254 | * @param electricScroll The number of lines always visible from
|
||
255 | * the top or bottom
|
||
256 | */
|
||
257 | public final void setElectricScroll(int electricScroll) |
||
258 | { |
||
259 | this.electricScroll = electricScroll;
|
||
260 | } |
||
261 | |||
262 | /**
|
||
263 | * Updates the state of the scroll bars. This should be called
|
||
264 | * if the number of lines in the document changes, or when the
|
||
265 | * size of the text are changes.
|
||
266 | */
|
||
267 | public void updateScrollBars() |
||
268 | { |
||
269 | if(vertical != null && visibleLines != 0) |
||
270 | { |
||
271 | vertical.setValues(firstLine,visibleLines,0,getLineCount());
|
||
272 | vertical.setUnitIncrement(2);
|
||
273 | vertical.setBlockIncrement(visibleLines); |
||
274 | } |
||
275 | |||
276 | int width = painter.getWidth();
|
||
277 | if(horizontal != null && width != 0) |
||
278 | { |
||
279 | horizontal.setValues(-horizontalOffset,width,0,width * 5); |
||
280 | horizontal.setUnitIncrement(painter.getFontMetrics() |
||
281 | .charWidth('w'));
|
||
282 | horizontal.setBlockIncrement(width / 2);
|
||
283 | } |
||
284 | } |
||
285 | |||
286 | /**
|
||
287 | * Returns the line displayed at the text area's origin.
|
||
288 | */
|
||
289 | public final int getFirstLine() |
||
290 | { |
||
291 | return firstLine;
|
||
292 | } |
||
293 | |||
294 | /**
|
||
295 | * Sets the line displayed at the text area's origin without
|
||
296 | * updating the scroll bars.
|
||
297 | */
|
||
298 | public void setFirstLine(int firstLine) |
||
299 | { |
||
300 | if(firstLine == this.firstLine) |
||
301 | return;
|
||
302 | int oldFirstLine = this.firstLine; |
||
303 | this.firstLine = firstLine;
|
||
304 | if(firstLine != vertical.getValue())
|
||
305 | updateScrollBars(); |
||
306 | painter.repaint(); |
||
307 | } |
||
308 | |||
309 | /**
|
||
310 | * Returns the number of lines visible in this text area.
|
||
311 | */
|
||
312 | public final int getVisibleLines() |
||
313 | { |
||
314 | return visibleLines;
|
||
315 | } |
||
316 | |||
317 | /**
|
||
318 | * Recalculates the number of visible lines. This should not
|
||
319 | * be called directly.
|
||
320 | */
|
||
321 | public final void recalculateVisibleLines() |
||
322 | { |
||
323 | if(painter == null) |
||
324 | return;
|
||
325 | int height = painter.getHeight();
|
||
326 | int lineHeight = painter.getFontMetrics().getHeight();
|
||
327 | int oldVisibleLines = visibleLines;
|
||
328 | visibleLines = height / lineHeight; |
||
329 | updateScrollBars(); |
||
330 | } |
||
331 | |||
332 | /**
|
||
333 | * Returns the horizontal offset of drawn lines.
|
||
334 | */
|
||
335 | public final int getHorizontalOffset() |
||
336 | { |
||
337 | return horizontalOffset;
|
||
338 | } |
||
339 | |||
340 | /**
|
||
341 | * Sets the horizontal offset of drawn lines. This can be used to
|
||
342 | * implement horizontal scrolling.
|
||
343 | * @param horizontalOffset offset The new horizontal offset
|
||
344 | */
|
||
345 | public void setHorizontalOffset(int horizontalOffset) |
||
346 | { |
||
347 | if(horizontalOffset == this.horizontalOffset) |
||
348 | return;
|
||
349 | this.horizontalOffset = horizontalOffset;
|
||
350 | if(horizontalOffset != horizontal.getValue())
|
||
351 | updateScrollBars(); |
||
352 | painter.repaint(); |
||
353 | } |
||
354 | |||
355 | /**
|
||
356 | * A fast way of changing both the first line and horizontal
|
||
357 | * offset.
|
||
358 | * @param firstLine The new first line
|
||
359 | * @param horizontalOffset The new horizontal offset
|
||
360 | * @return True if any of the values were changed, false otherwise
|
||
361 | */
|
||
362 | public boolean setOrigin(int firstLine, int horizontalOffset) |
||
363 | { |
||
364 | boolean changed = false; |
||
365 | int oldFirstLine = this.firstLine; |
||
366 | |||
367 | if(horizontalOffset != this.horizontalOffset) |
||
368 | { |
||
369 | this.horizontalOffset = horizontalOffset;
|
||
370 | changed = true;
|
||
371 | } |
||
372 | |||
373 | if(firstLine != this.firstLine) |
||
374 | { |
||
375 | this.firstLine = firstLine;
|
||
376 | changed = true;
|
||
377 | } |
||
378 | |||
379 | if(changed)
|
||
380 | { |
||
381 | updateScrollBars(); |
||
382 | painter.repaint(); |
||
383 | } |
||
384 | |||
385 | return changed;
|
||
386 | } |
||
387 | |||
388 | /**
|
||
389 | * Ensures that the caret is visible by scrolling the text area if
|
||
390 | * necessary.
|
||
391 | * @return True if scrolling was actually performed, false if the
|
||
392 | * caret was already visible
|
||
393 | */
|
||
394 | public boolean scrollToCaret() |
||
395 | { |
||
396 | int line = getCaretLine();
|
||
397 | int lineStart = getLineStartOffset(line);
|
||
398 | int offset = Math.max(0,Math.min(getLineLength(line) - 1, |
||
399 | getCaretPosition() - lineStart)); |
||
400 | |||
401 | return scrollTo(line,offset);
|
||
402 | } |
||
403 | |||
404 | /**
|
||
405 | * Ensures that the specified line and offset is visible by scrolling
|
||
406 | * the text area if necessary.
|
||
407 | * @param line The line to scroll to
|
||
408 | * @param offset The offset in the line to scroll to
|
||
409 | * @return True if scrolling was actually performed, false if the
|
||
410 | * line and offset was already visible
|
||
411 | */
|
||
412 | public boolean scrollTo(int line, int offset) |
||
413 | { |
||
414 | // visibleLines == 0 before the component is realized
|
||
415 | // we can't do any proper scrolling then, so we have
|
||
416 | // this hack...
|
||
417 | if(visibleLines == 0) |
||
418 | { |
||
419 | setFirstLine(Math.max(0,line - electricScroll)); |
||
420 | return true; |
||
421 | } |
||
422 | |||
423 | int newFirstLine = firstLine;
|
||
424 | int newHorizontalOffset = horizontalOffset;
|
||
425 | |||
426 | if(line < firstLine + electricScroll)
|
||
427 | { |
||
428 | newFirstLine = Math.max(0,line - electricScroll); |
||
429 | } |
||
430 | else if(line + electricScroll >= firstLine + visibleLines) |
||
431 | { |
||
432 | newFirstLine = (line - visibleLines) + electricScroll + 1;
|
||
433 | if(newFirstLine + visibleLines >= getLineCount())
|
||
434 | newFirstLine = getLineCount() - visibleLines; |
||
435 | if(newFirstLine < 0) |
||
436 | newFirstLine = 0;
|
||
437 | } |
||
438 | |||
439 | int x = _offsetToX(line,offset);
|
||
440 | int width = painter.getFontMetrics().charWidth('w'); |
||
441 | |||
442 | if(x < 0) |
||
443 | { |
||
444 | newHorizontalOffset = Math.min(0,horizontalOffset |
||
445 | - x + width + 5);
|
||
446 | } |
||
447 | else if(x + width >= painter.getWidth()) |
||
448 | { |
||
449 | newHorizontalOffset = horizontalOffset + |
||
450 | (painter.getWidth() - x) - width - 5;
|
||
451 | } |
||
452 | |||
453 | return setOrigin(newFirstLine,newHorizontalOffset);
|
||
454 | } |
||
455 | |||
456 | /**
|
||
457 | * Converts a line index to a y co-ordinate.
|
||
458 | * @param line The line
|
||
459 | */
|
||
460 | public int lineToY(int line) |
||
461 | { |
||
462 | FontMetrics fm = painter.getFontMetrics();
|
||
463 | return (line - firstLine) * fm.getHeight()
|
||
464 | - (fm.getLeading() + fm.getMaxDescent()); |
||
465 | } |
||
466 | |||
467 | /**
|
||
468 | * Converts a y co-ordinate to a line index.
|
||
469 | * @param y The y co-ordinate
|
||
470 | */
|
||
471 | public int yToLine(int y) |
||
472 | { |
||
473 | FontMetrics fm = painter.getFontMetrics();
|
||
474 | int height = fm.getHeight();
|
||
475 | return Math.max(0,Math.min(getLineCount() - 1, |
||
476 | y / height + firstLine)); |
||
477 | } |
||
478 | |||
479 | /**
|
||
480 | * Converts an offset in a line into an x co-ordinate. This is a
|
||
481 | * slow version that can be used any time.
|
||
482 | * @param line The line
|
||
483 | * @param offset The offset, from the start of the line
|
||
484 | */
|
||
485 | public final int offsetToX(int line, int offset) |
||
486 | { |
||
487 | // don't use cached tokens
|
||
488 | painter.currentLineTokens = null;
|
||
489 | return _offsetToX(line,offset);
|
||
490 | } |
||
491 | |||
492 | /**
|
||
493 | * Converts an offset in a line into an x co-ordinate. This is a
|
||
494 | * fast version that should only be used if no changes were made
|
||
495 | * to the text since the last repaint.
|
||
496 | * @param line The line
|
||
497 | * @param offset The offset, from the start of the line
|
||
498 | */
|
||
499 | public int _offsetToX(int line, int offset) |
||
500 | { |
||
501 | TokenMarker tokenMarker = getTokenMarker(); |
||
502 | |||
503 | /* Use painter's cached info for speed */
|
||
504 | FontMetrics fm = painter.getFontMetrics();
|
||
505 | |||
506 | getLineText(line,lineSegment); |
||
507 | |||
508 | int segmentOffset = lineSegment.offset;
|
||
509 | int x = horizontalOffset;
|
||
510 | |||
511 | /* If syntax coloring is disabled, do simple translation */
|
||
512 | if(tokenMarker == null) |
||
513 | { |
||
514 | lineSegment.count = offset; |
||
515 | return x + Utilities.getTabbedTextWidth(lineSegment, |
||
516 | fm,x,painter,0);
|
||
517 | } |
||
518 | /* If syntax coloring is enabled, we have to do this because
|
||
519 | * tokens can vary in width */
|
||
520 | else
|
||
521 | { |
||
522 | Token tokens; |
||
523 | if(painter.currentLineIndex == line
|
||
524 | && painter.currentLineTokens != null)
|
||
525 | tokens = painter.currentLineTokens; |
||
526 | else
|
||
527 | { |
||
528 | painter.currentLineIndex = line; |
||
529 | tokens = painter.currentLineTokens |
||
530 | = tokenMarker.markTokens(lineSegment,line); |
||
531 | } |
||
532 | |||
533 | Toolkit toolkit = painter.getToolkit();
|
||
534 | Font defaultFont = painter.getFont();
|
||
535 | SyntaxStyle[] styles = painter.getStyles();
|
||
536 | |||
537 | for(;;)
|
||
538 | { |
||
539 | byte id = tokens.id;
|
||
540 | if(id == Token.END)
|
||
541 | { |
||
542 | return x;
|
||
543 | } |
||
544 | |||
545 | if(id == Token.NULL)
|
||
546 | fm = painter.getFontMetrics(); |
||
547 | else
|
||
548 | fm = styles[id].getFontMetrics(defaultFont); |
||
549 | |||
550 | int length = tokens.length;
|
||
551 | |||
552 | if(offset + segmentOffset < lineSegment.offset + length)
|
||
553 | { |
||
554 | lineSegment.count = offset - (lineSegment.offset - segmentOffset); |
||
555 | return x + Utilities.getTabbedTextWidth( |
||
556 | lineSegment,fm,x,painter,0);
|
||
557 | } |
||
558 | else
|
||
559 | { |
||
560 | lineSegment.count = length; |
||
561 | x += Utilities.getTabbedTextWidth(
|
||
562 | lineSegment,fm,x,painter,0);
|
||
563 | lineSegment.offset += length; |
||
564 | } |
||
565 | tokens = tokens.next; |
||
566 | } |
||
567 | } |
||
568 | } |
||
569 | |||
570 | /**
|
||
571 | * Converts an x co-ordinate to an offset within a line.
|
||
572 | * @param line The line
|
||
573 | * @param x The x co-ordinate
|
||
574 | */
|
||
575 | public int xToOffset(int line, int x) |
||
576 | { |
||
577 | TokenMarker tokenMarker = getTokenMarker(); |
||
578 | |||
579 | /* Use painter's cached info for speed */
|
||
580 | FontMetrics fm = painter.getFontMetrics();
|
||
581 | |||
582 | getLineText(line,lineSegment); |
||
583 | |||
584 | char[] segmentArray = lineSegment.array; |
||
585 | int segmentOffset = lineSegment.offset;
|
||
586 | int segmentCount = lineSegment.count;
|
||
587 | |||
588 | int width = horizontalOffset;
|
||
589 | |||
590 | if(tokenMarker == null) |
||
591 | { |
||
592 | for(int i = 0; i < segmentCount; i++) |
||
593 | { |
||
594 | char c = segmentArray[i + segmentOffset];
|
||
595 | int charWidth;
|
||
596 | if(c == '\t') |
||
597 | charWidth = (int)painter.nextTabStop(width,i)
|
||
598 | - width; |
||
599 | else
|
||
600 | charWidth = fm.charWidth(c); |
||
601 | |||
602 | if(painter.isBlockCaretEnabled())
|
||
603 | { |
||
604 | if(x - charWidth <= width)
|
||
605 | return i;
|
||
606 | } |
||
607 | else
|
||
608 | { |
||
609 | if(x - charWidth / 2 <= width) |
||
610 | return i;
|
||
611 | } |
||
612 | |||
613 | width += charWidth; |
||
614 | } |
||
615 | |||
616 | return segmentCount;
|
||
617 | } |
||
618 | else
|
||
619 | { |
||
620 | Token tokens; |
||
621 | if(painter.currentLineIndex == line && painter
|
||
622 | .currentLineTokens != null)
|
||
623 | tokens = painter.currentLineTokens; |
||
624 | else
|
||
625 | { |
||
626 | painter.currentLineIndex = line; |
||
627 | tokens = painter.currentLineTokens |
||
628 | = tokenMarker.markTokens(lineSegment,line); |
||
629 | } |
||
630 | |||
631 | int offset = 0; |
||
632 | Toolkit toolkit = painter.getToolkit();
|
||
633 | Font defaultFont = painter.getFont();
|
||
634 | SyntaxStyle[] styles = painter.getStyles();
|
||
635 | |||
636 | for(;;)
|
||
637 | { |
||
638 | byte id = tokens.id;
|
||
639 | if(id == Token.END)
|
||
640 | return offset;
|
||
641 | |||
642 | if(id == Token.NULL)
|
||
643 | fm = painter.getFontMetrics(); |
||
644 | else
|
||
645 | fm = styles[id].getFontMetrics(defaultFont); |
||
646 | |||
647 | int length = tokens.length;
|
||
648 | |||
649 | for(int i = 0; i < length; i++) |
||
650 | { |
||
651 | char c = segmentArray[segmentOffset + offset + i];
|
||
652 | int charWidth;
|
||
653 | if(c == '\t') |
||
654 | charWidth = (int)painter.nextTabStop(width,offset + i)
|
||
655 | - width; |
||
656 | else
|
||
657 | charWidth = fm.charWidth(c); |
||
658 | |||
659 | if(painter.isBlockCaretEnabled())
|
||
660 | { |
||
661 | if(x - charWidth <= width)
|
||
662 | return offset + i;
|
||
663 | } |
||
664 | else
|
||
665 | { |
||
666 | if(x - charWidth / 2 <= width) |
||
667 | return offset + i;
|
||
668 | } |
||
669 | |||
670 | width += charWidth; |
||
671 | } |
||
672 | |||
673 | offset += length; |
||
674 | tokens = tokens.next; |
||
675 | } |
||
676 | } |
||
677 | } |
||
678 | |||
679 | /**
|
||
680 | * Converts a point to an offset, from the start of the text.
|
||
681 | * @param x The x co-ordinate of the point
|
||
682 | * @param y The y co-ordinate of the point
|
||
683 | */
|
||
684 | public int xyToOffset(int x, int y) |
||
685 | { |
||
686 | int line = yToLine(y);
|
||
687 | int start = getLineStartOffset(line);
|
||
688 | return start + xToOffset(line,x);
|
||
689 | } |
||
690 | |||
691 | /**
|
||
692 | * Returns the document this text area is editing.
|
||
693 | */
|
||
694 | 4900 | caballero | public final Document getDocument() |
695 | 4890 | caballero | { |
696 | return document;
|
||
697 | } |
||
698 | |||
699 | /**
|
||
700 | * Sets the document this text area is editing.
|
||
701 | * @param document The document
|
||
702 | */
|
||
703 | public void setDocument(SyntaxDocument document) |
||
704 | { |
||
705 | if(this.document == document) |
||
706 | return;
|
||
707 | if(this.document != null) |
||
708 | this.document.removeDocumentListener(documentHandler);
|
||
709 | this.document = document;
|
||
710 | |||
711 | document.addDocumentListener(documentHandler); |
||
712 | |||
713 | select(0,0); |
||
714 | updateScrollBars(); |
||
715 | painter.repaint(); |
||
716 | } |
||
717 | |||
718 | /**
|
||
719 | * Returns the document's token marker. Equivalent to calling
|
||
720 | * <code>getDocument().getTokenMarker()</code>.
|
||
721 | */
|
||
722 | public final TokenMarker getTokenMarker() |
||
723 | { |
||
724 | return document.getTokenMarker();
|
||
725 | } |
||
726 | |||
727 | /**
|
||
728 | * Sets the document's token marker. Equivalent to caling
|
||
729 | * <code>getDocument().setTokenMarker()</code>.
|
||
730 | * @param tokenMarker The token marker
|
||
731 | */
|
||
732 | public final void setTokenMarker(TokenMarker tokenMarker) |
||
733 | { |
||
734 | document.setTokenMarker(tokenMarker); |
||
735 | } |
||
736 | |||
737 | /**
|
||
738 | * Returns the length of the document. Equivalent to calling
|
||
739 | * <code>getDocument().getLength()</code>.
|
||
740 | */
|
||
741 | public final int getDocumentLength() |
||
742 | { |
||
743 | return document.getLength();
|
||
744 | } |
||
745 | |||
746 | /**
|
||
747 | * Returns the number of lines in the document.
|
||
748 | */
|
||
749 | public final int getLineCount() |
||
750 | { |
||
751 | return document.getDefaultRootElement().getElementCount();
|
||
752 | } |
||
753 | |||
754 | /**
|
||
755 | * Returns the line containing the specified offset.
|
||
756 | * @param offset The offset
|
||
757 | */
|
||
758 | public final int getLineOfOffset(int offset) |
||
759 | { |
||
760 | return document.getDefaultRootElement().getElementIndex(offset);
|
||
761 | } |
||
762 | |||
763 | /**
|
||
764 | * Returns the start offset of the specified line.
|
||
765 | * @param line The line
|
||
766 | * @return The start offset of the specified line, or -1 if the line is
|
||
767 | * invalid
|
||
768 | */
|
||
769 | public int getLineStartOffset(int line) |
||
770 | { |
||
771 | Element lineElement = document.getDefaultRootElement()
|
||
772 | .getElement(line); |
||
773 | if(lineElement == null) |
||
774 | return -1; |
||
775 | else
|
||
776 | return lineElement.getStartOffset();
|
||
777 | } |
||
778 | |||
779 | /**
|
||
780 | * Returns the end offset of the specified line.
|
||
781 | * @param line The line
|
||
782 | * @return The end offset of the specified line, or -1 if the line is
|
||
783 | * invalid.
|
||
784 | */
|
||
785 | public int getLineEndOffset(int line) |
||
786 | { |
||
787 | Element lineElement = document.getDefaultRootElement()
|
||
788 | .getElement(line); |
||
789 | if(lineElement == null) |
||
790 | return -1; |
||
791 | else
|
||
792 | return lineElement.getEndOffset();
|
||
793 | } |
||
794 | |||
795 | /**
|
||
796 | * Returns the length of the specified line.
|
||
797 | * @param line The line
|
||
798 | */
|
||
799 | public int getLineLength(int line) |
||
800 | { |
||
801 | Element lineElement = document.getDefaultRootElement()
|
||
802 | .getElement(line); |
||
803 | if(lineElement == null) |
||
804 | return -1; |
||
805 | else
|
||
806 | return lineElement.getEndOffset()
|
||
807 | - lineElement.getStartOffset() - 1;
|
||
808 | } |
||
809 | |||
810 | /**
|
||
811 | * Returns the entire text of this text area.
|
||
812 | */
|
||
813 | public String getText() |
||
814 | { |
||
815 | try
|
||
816 | { |
||
817 | return document.getText(0,document.getLength()); |
||
818 | } |
||
819 | catch(BadLocationException bl) |
||
820 | { |
||
821 | bl.printStackTrace(); |
||
822 | return null; |
||
823 | } |
||
824 | } |
||
825 | |||
826 | /**
|
||
827 | * Sets the entire text of this text area.
|
||
828 | */
|
||
829 | public void setText(String text) |
||
830 | { |
||
831 | try
|
||
832 | { |
||
833 | document.beginCompoundEdit(); |
||
834 | document.remove(0,document.getLength());
|
||
835 | document.insertString(0,text,null); |
||
836 | } |
||
837 | catch(BadLocationException bl) |
||
838 | { |
||
839 | bl.printStackTrace(); |
||
840 | } |
||
841 | finally
|
||
842 | { |
||
843 | document.endCompoundEdit(); |
||
844 | } |
||
845 | } |
||
846 | |||
847 | /**
|
||
848 | * Returns the specified substring of the document.
|
||
849 | * @param start The start offset
|
||
850 | * @param len The length of the substring
|
||
851 | * @return The substring, or null if the offsets are invalid
|
||
852 | */
|
||
853 | public final String getText(int start, int len) |
||
854 | { |
||
855 | try
|
||
856 | { |
||
857 | return document.getText(start,len);
|
||
858 | } |
||
859 | catch(BadLocationException bl) |
||
860 | { |
||
861 | bl.printStackTrace(); |
||
862 | return null; |
||
863 | } |
||
864 | } |
||
865 | |||
866 | /**
|
||
867 | * Copies the specified substring of the document into a segment.
|
||
868 | * If the offsets are invalid, the segment will contain a null string.
|
||
869 | * @param start The start offset
|
||
870 | * @param len The length of the substring
|
||
871 | * @param segment The segment
|
||
872 | */
|
||
873 | public final void getText(int start, int len, Segment segment) |
||
874 | { |
||
875 | try
|
||
876 | { |
||
877 | document.getText(start,len,segment); |
||
878 | } |
||
879 | catch(BadLocationException bl) |
||
880 | { |
||
881 | bl.printStackTrace(); |
||
882 | segment.offset = segment.count = 0;
|
||
883 | } |
||
884 | } |
||
885 | |||
886 | /**
|
||
887 | * Returns the text on the specified line.
|
||
888 | * @param lineIndex The line
|
||
889 | * @return The text, or null if the line is invalid
|
||
890 | */
|
||
891 | public final String getLineText(int lineIndex) |
||
892 | { |
||
893 | int start = getLineStartOffset(lineIndex);
|
||
894 | return getText(start,getLineEndOffset(lineIndex) - start - 1); |
||
895 | } |
||
896 | |||
897 | /**
|
||
898 | * Copies the text on the specified line into a segment. If the line
|
||
899 | * is invalid, the segment will contain a null string.
|
||
900 | * @param lineIndex The line
|
||
901 | */
|
||
902 | public final void getLineText(int lineIndex, Segment segment) |
||
903 | { |
||
904 | int start = getLineStartOffset(lineIndex);
|
||
905 | getText(start,getLineEndOffset(lineIndex) - start - 1,segment);
|
||
906 | } |
||
907 | |||
908 | /**
|
||
909 | * Returns the selection start offset.
|
||
910 | */
|
||
911 | public final int getSelectionStart() |
||
912 | { |
||
913 | return selectionStart;
|
||
914 | } |
||
915 | |||
916 | /**
|
||
917 | * Returns the offset where the selection starts on the specified
|
||
918 | * line.
|
||
919 | */
|
||
920 | public int getSelectionStart(int line) |
||
921 | { |
||
922 | if(line == selectionStartLine)
|
||
923 | return selectionStart;
|
||
924 | else if(rectSelect) |
||
925 | { |
||
926 | Element map = document.getDefaultRootElement();
|
||
927 | int start = selectionStart - map.getElement(selectionStartLine)
|
||
928 | .getStartOffset(); |
||
929 | |||
930 | Element lineElement = map.getElement(line);
|
||
931 | int lineStart = lineElement.getStartOffset();
|
||
932 | int lineEnd = lineElement.getEndOffset() - 1; |
||
933 | return Math.min(lineEnd,lineStart + start); |
||
934 | } |
||
935 | else
|
||
936 | return getLineStartOffset(line);
|
||
937 | } |
||
938 | |||
939 | /**
|
||
940 | * Returns the selection start line.
|
||
941 | */
|
||
942 | public final int getSelectionStartLine() |
||
943 | { |
||
944 | return selectionStartLine;
|
||
945 | } |
||
946 | |||
947 | /**
|
||
948 | * Sets the selection start. The new selection will be the new
|
||
949 | * selection start and the old selection end.
|
||
950 | * @param selectionStart The selection start
|
||
951 | * @see #select(int,int)
|
||
952 | */
|
||
953 | public final void setSelectionStart(int selectionStart) |
||
954 | { |
||
955 | select(selectionStart,selectionEnd); |
||
956 | } |
||
957 | |||
958 | /**
|
||
959 | * Returns the selection end offset.
|
||
960 | */
|
||
961 | public final int getSelectionEnd() |
||
962 | { |
||
963 | return selectionEnd;
|
||
964 | } |
||
965 | |||
966 | /**
|
||
967 | * Returns the offset where the selection ends on the specified
|
||
968 | * line.
|
||
969 | */
|
||
970 | public int getSelectionEnd(int line) |
||
971 | { |
||
972 | if(line == selectionEndLine)
|
||
973 | return selectionEnd;
|
||
974 | else if(rectSelect) |
||
975 | { |
||
976 | Element map = document.getDefaultRootElement();
|
||
977 | int end = selectionEnd - map.getElement(selectionEndLine)
|
||
978 | .getStartOffset(); |
||
979 | |||
980 | Element lineElement = map.getElement(line);
|
||
981 | int lineStart = lineElement.getStartOffset();
|
||
982 | int lineEnd = lineElement.getEndOffset() - 1; |
||
983 | return Math.min(lineEnd,lineStart + end); |
||
984 | } |
||
985 | else
|
||
986 | return getLineEndOffset(line) - 1; |
||
987 | } |
||
988 | |||
989 | /**
|
||
990 | * Returns the selection end line.
|
||
991 | */
|
||
992 | public final int getSelectionEndLine() |
||
993 | { |
||
994 | return selectionEndLine;
|
||
995 | } |
||
996 | |||
997 | /**
|
||
998 | * Sets the selection end. The new selection will be the old
|
||
999 | * selection start and the bew selection end.
|
||
1000 | * @param selectionEnd The selection end
|
||
1001 | * @see #select(int,int)
|
||
1002 | */
|
||
1003 | public final void setSelectionEnd(int selectionEnd) |
||
1004 | { |
||
1005 | select(selectionStart,selectionEnd); |
||
1006 | } |
||
1007 | |||
1008 | /**
|
||
1009 | * Returns the caret position. This will either be the selection
|
||
1010 | * start or the selection end, depending on which direction the
|
||
1011 | * selection was made in.
|
||
1012 | */
|
||
1013 | public final int getCaretPosition() |
||
1014 | { |
||
1015 | return (biasLeft ? selectionStart : selectionEnd);
|
||
1016 | } |
||
1017 | |||
1018 | /**
|
||
1019 | * Returns the caret line.
|
||
1020 | */
|
||
1021 | public final int getCaretLine() |
||
1022 | { |
||
1023 | return (biasLeft ? selectionStartLine : selectionEndLine);
|
||
1024 | } |
||
1025 | |||
1026 | /**
|
||
1027 | * Returns the mark position. This will be the opposite selection
|
||
1028 | * bound to the caret position.
|
||
1029 | * @see #getCaretPosition()
|
||
1030 | */
|
||
1031 | public final int getMarkPosition() |
||
1032 | { |
||
1033 | return (biasLeft ? selectionEnd : selectionStart);
|
||
1034 | } |
||
1035 | |||
1036 | /**
|
||
1037 | * Returns the mark line.
|
||
1038 | */
|
||
1039 | public final int getMarkLine() |
||
1040 | { |
||
1041 | return (biasLeft ? selectionEndLine : selectionStartLine);
|
||
1042 | } |
||
1043 | |||
1044 | /**
|
||
1045 | * Sets the caret position. The new selection will consist of the
|
||
1046 | * caret position only (hence no text will be selected)
|
||
1047 | * @param caret The caret position
|
||
1048 | * @see #select(int,int)
|
||
1049 | */
|
||
1050 | public final void setCaretPosition(int caret) |
||
1051 | { |
||
1052 | select(caret,caret); |
||
1053 | } |
||
1054 | |||
1055 | /**
|
||
1056 | * Selects all text in the document.
|
||
1057 | */
|
||
1058 | public final void selectAll() |
||
1059 | { |
||
1060 | select(0,getDocumentLength());
|
||
1061 | } |
||
1062 | |||
1063 | /**
|
||
1064 | * Moves the mark to the caret position.
|
||
1065 | */
|
||
1066 | public final void selectNone() |
||
1067 | { |
||
1068 | select(getCaretPosition(),getCaretPosition()); |
||
1069 | } |
||
1070 | |||
1071 | /**
|
||
1072 | * Selects from the start offset to the end offset. This is the
|
||
1073 | * general selection method used by all other selecting methods.
|
||
1074 | * The caret position will be start if start < end, and end
|
||
1075 | * if end > start.
|
||
1076 | * @param start The start offset
|
||
1077 | * @param end The end offset
|
||
1078 | */
|
||
1079 | public void select(int start, int end) |
||
1080 | { |
||
1081 | int newStart, newEnd;
|
||
1082 | boolean newBias;
|
||
1083 | if(start <= end)
|
||
1084 | { |
||
1085 | newStart = start; |
||
1086 | newEnd = end; |
||
1087 | newBias = false;
|
||
1088 | } |
||
1089 | else
|
||
1090 | { |
||
1091 | newStart = end; |
||
1092 | newEnd = start; |
||
1093 | newBias = true;
|
||
1094 | } |
||
1095 | |||
1096 | if(newStart < 0 || newEnd > getDocumentLength()) |
||
1097 | { |
||
1098 | throw new IllegalArgumentException("Bounds out of" |
||
1099 | + " range: " + newStart + "," + |
||
1100 | newEnd); |
||
1101 | } |
||
1102 | |||
1103 | // If the new position is the same as the old, we don't
|
||
1104 | // do all this crap, however we still do the stuff at
|
||
1105 | // the end (clearing magic position, scrolling)
|
||
1106 | if(newStart != selectionStart || newEnd != selectionEnd
|
||
1107 | || newBias != biasLeft) |
||
1108 | { |
||
1109 | int newStartLine = getLineOfOffset(newStart);
|
||
1110 | int newEndLine = getLineOfOffset(newEnd);
|
||
1111 | |||
1112 | if(painter.isBracketHighlightEnabled())
|
||
1113 | { |
||
1114 | if(bracketLine != -1) |
||
1115 | painter.invalidateLine(bracketLine); |
||
1116 | updateBracketHighlight(end); |
||
1117 | if(bracketLine != -1) |
||
1118 | painter.invalidateLine(bracketLine); |
||
1119 | } |
||
1120 | |||
1121 | painter.invalidateLineRange(selectionStartLine,selectionEndLine); |
||
1122 | painter.invalidateLineRange(newStartLine,newEndLine); |
||
1123 | |||
1124 | document.addUndoableEdit(new CaretUndo(
|
||
1125 | selectionStart,selectionEnd)); |
||
1126 | |||
1127 | selectionStart = newStart; |
||
1128 | selectionEnd = newEnd; |
||
1129 | selectionStartLine = newStartLine; |
||
1130 | selectionEndLine = newEndLine; |
||
1131 | biasLeft = newBias; |
||
1132 | |||
1133 | fireCaretEvent(); |
||
1134 | } |
||
1135 | |||
1136 | // When the user is typing, etc, we don't want the caret
|
||
1137 | // to blink
|
||
1138 | blink = true;
|
||
1139 | caretTimer.restart(); |
||
1140 | |||
1141 | // Disable rectangle select if selection start = selection end
|
||
1142 | if(selectionStart == selectionEnd)
|
||
1143 | rectSelect = false;
|
||
1144 | |||
1145 | // Clear the `magic' caret position used by up/down
|
||
1146 | magicCaret = -1;
|
||
1147 | |||
1148 | scrollToCaret(); |
||
1149 | } |
||
1150 | |||
1151 | /**
|
||
1152 | * Returns the selected text, or null if no selection is active.
|
||
1153 | */
|
||
1154 | public final String getSelectedText() |
||
1155 | { |
||
1156 | if(selectionStart == selectionEnd)
|
||
1157 | return null; |
||
1158 | |||
1159 | if(rectSelect)
|
||
1160 | { |
||
1161 | // Return each row of the selection on a new line
|
||
1162 | |||
1163 | Element map = document.getDefaultRootElement();
|
||
1164 | |||
1165 | int start = selectionStart - map.getElement(selectionStartLine)
|
||
1166 | .getStartOffset(); |
||
1167 | int end = selectionEnd - map.getElement(selectionEndLine)
|
||
1168 | .getStartOffset(); |
||
1169 | |||
1170 | // Certain rectangles satisfy this condition...
|
||
1171 | if(end < start)
|
||
1172 | { |
||
1173 | int tmp = end;
|
||
1174 | end = start; |
||
1175 | start = tmp; |
||
1176 | } |
||
1177 | |||
1178 | StringBuffer buf = new StringBuffer(); |
||
1179 | Segment seg = new Segment(); |
||
1180 | |||
1181 | for(int i = selectionStartLine; i <= selectionEndLine; i++) |
||
1182 | { |
||
1183 | Element lineElement = map.getElement(i);
|
||
1184 | int lineStart = lineElement.getStartOffset();
|
||
1185 | int lineEnd = lineElement.getEndOffset() - 1; |
||
1186 | int lineLen = lineEnd - lineStart;
|
||
1187 | |||
1188 | lineStart = Math.min(lineStart + start,lineEnd);
|
||
1189 | lineLen = Math.min(end - start,lineEnd - lineStart);
|
||
1190 | |||
1191 | getText(lineStart,lineLen,seg); |
||
1192 | buf.append(seg.array,seg.offset,seg.count); |
||
1193 | |||
1194 | if(i != selectionEndLine)
|
||
1195 | buf.append('\n');
|
||
1196 | } |
||
1197 | |||
1198 | return buf.toString();
|
||
1199 | } |
||
1200 | else
|
||
1201 | { |
||
1202 | return getText(selectionStart,
|
||
1203 | selectionEnd - selectionStart); |
||
1204 | } |
||
1205 | } |
||
1206 | |||
1207 | /**
|
||
1208 | * Replaces the selection with the specified text.
|
||
1209 | * @param selectedText The replacement text for the selection
|
||
1210 | */
|
||
1211 | public void setSelectedText(String selectedText) |
||
1212 | { |
||
1213 | if(!editable)
|
||
1214 | { |
||
1215 | throw new InternalError("Text component" |
||
1216 | + " read only");
|
||
1217 | } |
||
1218 | |||
1219 | document.beginCompoundEdit(); |
||
1220 | |||
1221 | try
|
||
1222 | { |
||
1223 | if(rectSelect)
|
||
1224 | { |
||
1225 | Element map = document.getDefaultRootElement();
|
||
1226 | |||
1227 | int start = selectionStart - map.getElement(selectionStartLine)
|
||
1228 | .getStartOffset(); |
||
1229 | int end = selectionEnd - map.getElement(selectionEndLine)
|
||
1230 | .getStartOffset(); |
||
1231 | |||
1232 | // Certain rectangles satisfy this condition...
|
||
1233 | if(end < start)
|
||
1234 | { |
||
1235 | int tmp = end;
|
||
1236 | end = start; |
||
1237 | start = tmp; |
||
1238 | } |
||
1239 | |||
1240 | int lastNewline = 0; |
||
1241 | int currNewline = 0; |
||
1242 | |||
1243 | for(int i = selectionStartLine; i <= selectionEndLine; i++) |
||
1244 | { |
||
1245 | Element lineElement = map.getElement(i);
|
||
1246 | int lineStart = lineElement.getStartOffset();
|
||
1247 | int lineEnd = lineElement.getEndOffset() - 1; |
||
1248 | int rectStart = Math.min(lineEnd,lineStart + start); |
||
1249 | |||
1250 | document.remove(rectStart,Math.min(lineEnd - rectStart,
|
||
1251 | end - start)); |
||
1252 | |||
1253 | if(selectedText == null) |
||
1254 | continue;
|
||
1255 | |||
1256 | currNewline = selectedText.indexOf('\n',lastNewline);
|
||
1257 | if(currNewline == -1) |
||
1258 | currNewline = selectedText.length(); |
||
1259 | |||
1260 | document.insertString(rectStart,selectedText |
||
1261 | .substring(lastNewline,currNewline),null);
|
||
1262 | |||
1263 | lastNewline = Math.min(selectedText.length(),
|
||
1264 | currNewline + 1);
|
||
1265 | } |
||
1266 | |||
1267 | if(selectedText != null && |
||
1268 | currNewline != selectedText.length()) |
||
1269 | { |
||
1270 | int offset = map.getElement(selectionEndLine)
|
||
1271 | .getEndOffset() - 1;
|
||
1272 | document.insertString(offset,"\n",null); |
||
1273 | document.insertString(offset + 1,selectedText
|
||
1274 | .substring(currNewline + 1),null); |
||
1275 | } |
||
1276 | } |
||
1277 | else
|
||
1278 | { |
||
1279 | document.remove(selectionStart, |
||
1280 | selectionEnd - selectionStart); |
||
1281 | if(selectedText != null) |
||
1282 | { |
||
1283 | document.insertString(selectionStart, |
||
1284 | selectedText,null);
|
||
1285 | } |
||
1286 | } |
||
1287 | } |
||
1288 | catch(BadLocationException bl) |
||
1289 | { |
||
1290 | bl.printStackTrace(); |
||
1291 | throw new InternalError("Cannot replace" |
||
1292 | + " selection");
|
||
1293 | } |
||
1294 | // No matter what happends... stops us from leaving document
|
||
1295 | // in a bad state
|
||
1296 | finally
|
||
1297 | { |
||
1298 | document.endCompoundEdit(); |
||
1299 | } |
||
1300 | |||
1301 | setCaretPosition(selectionEnd); |
||
1302 | } |
||
1303 | |||
1304 | /**
|
||
1305 | * Returns true if this text area is editable, false otherwise.
|
||
1306 | */
|
||
1307 | public final boolean isEditable() |
||
1308 | { |
||
1309 | return editable;
|
||
1310 | } |
||
1311 | |||
1312 | /**
|
||
1313 | * Sets if this component is editable.
|
||
1314 | * @param editable True if this text area should be editable,
|
||
1315 | * false otherwise
|
||
1316 | */
|
||
1317 | public final void setEditable(boolean editable) |
||
1318 | { |
||
1319 | this.editable = editable;
|
||
1320 | } |
||
1321 | |||
1322 | /**
|
||
1323 | * Returns the right click popup menu.
|
||
1324 | */
|
||
1325 | public final JPopupMenu getRightClickPopup() |
||
1326 | { |
||
1327 | return popup;
|
||
1328 | } |
||
1329 | |||
1330 | /**
|
||
1331 | * Sets the right click popup menu.
|
||
1332 | * @param popup The popup
|
||
1333 | */
|
||
1334 | public final void setRightClickPopup(JPopupMenu popup) |
||
1335 | { |
||
1336 | this.popup = popup;
|
||
1337 | } |
||
1338 | |||
1339 | /**
|
||
1340 | * Returns the `magic' caret position. This can be used to preserve
|
||
1341 | * the column position when moving up and down lines.
|
||
1342 | */
|
||
1343 | public final int getMagicCaretPosition() |
||
1344 | { |
||
1345 | return magicCaret;
|
||
1346 | } |
||
1347 | |||
1348 | /**
|
||
1349 | * Sets the `magic' caret position. This can be used to preserve
|
||
1350 | * the column position when moving up and down lines.
|
||
1351 | * @param magicCaret The magic caret position
|
||
1352 | */
|
||
1353 | public final void setMagicCaretPosition(int magicCaret) |
||
1354 | { |
||
1355 | this.magicCaret = magicCaret;
|
||
1356 | } |
||
1357 | |||
1358 | /**
|
||
1359 | * Similar to <code>setSelectedText()</code>, but overstrikes the
|
||
1360 | * appropriate number of characters if overwrite mode is enabled.
|
||
1361 | * @param str The string
|
||
1362 | * @see #setSelectedText(String)
|
||
1363 | * @see #isOverwriteEnabled()
|
||
1364 | */
|
||
1365 | public void overwriteSetSelectedText(String str) |
||
1366 | { |
||
1367 | // Don't overstrike if there is a selection
|
||
1368 | if(!overwrite || selectionStart != selectionEnd)
|
||
1369 | { |
||
1370 | setSelectedText(str); |
||
1371 | return;
|
||
1372 | } |
||
1373 | |||
1374 | // Don't overstrike if we're on the end of
|
||
1375 | // the line
|
||
1376 | int caret = getCaretPosition();
|
||
1377 | int caretLineEnd = getLineEndOffset(getCaretLine());
|
||
1378 | if(caretLineEnd - caret <= str.length())
|
||
1379 | { |
||
1380 | setSelectedText(str); |
||
1381 | return;
|
||
1382 | } |
||
1383 | |||
1384 | document.beginCompoundEdit(); |
||
1385 | |||
1386 | try
|
||
1387 | { |
||
1388 | document.remove(caret,str.length()); |
||
1389 | document.insertString(caret,str,null);
|
||
1390 | } |
||
1391 | catch(BadLocationException bl) |
||
1392 | { |
||
1393 | bl.printStackTrace(); |
||
1394 | } |
||
1395 | finally
|
||
1396 | { |
||
1397 | document.endCompoundEdit(); |
||
1398 | } |
||
1399 | } |
||
1400 | |||
1401 | /**
|
||
1402 | * Returns true if overwrite mode is enabled, false otherwise.
|
||
1403 | */
|
||
1404 | public final boolean isOverwriteEnabled() |
||
1405 | { |
||
1406 | return overwrite;
|
||
1407 | } |
||
1408 | |||
1409 | /**
|
||
1410 | * Sets if overwrite mode should be enabled.
|
||
1411 | * @param overwrite True if overwrite mode should be enabled,
|
||
1412 | * false otherwise.
|
||
1413 | */
|
||
1414 | public final void setOverwriteEnabled(boolean overwrite) |
||
1415 | { |
||
1416 | this.overwrite = overwrite;
|
||
1417 | painter.invalidateSelectedLines(); |
||
1418 | } |
||
1419 | |||
1420 | /**
|
||
1421 | * Returns true if the selection is rectangular, false otherwise.
|
||
1422 | */
|
||
1423 | public final boolean isSelectionRectangular() |
||
1424 | { |
||
1425 | return rectSelect;
|
||
1426 | } |
||
1427 | |||
1428 | /**
|
||
1429 | * Sets if the selection should be rectangular.
|
||
1430 | * @param overwrite True if the selection should be rectangular,
|
||
1431 | * false otherwise.
|
||
1432 | */
|
||
1433 | public final void setSelectionRectangular(boolean rectSelect) |
||
1434 | { |
||
1435 | this.rectSelect = rectSelect;
|
||
1436 | painter.invalidateSelectedLines(); |
||
1437 | } |
||
1438 | |||
1439 | /**
|
||
1440 | * Returns the position of the highlighted bracket (the bracket
|
||
1441 | * matching the one before the caret)
|
||
1442 | */
|
||
1443 | public final int getBracketPosition() |
||
1444 | { |
||
1445 | return bracketPosition;
|
||
1446 | } |
||
1447 | |||
1448 | /**
|
||
1449 | * Returns the line of the highlighted bracket (the bracket
|
||
1450 | * matching the one before the caret)
|
||
1451 | */
|
||
1452 | public final int getBracketLine() |
||
1453 | { |
||
1454 | return bracketLine;
|
||
1455 | } |
||
1456 | |||
1457 | /**
|
||
1458 | * Adds a caret change listener to this text area.
|
||
1459 | * @param listener The listener
|
||
1460 | */
|
||
1461 | public final void addCaretListener(CaretListener listener) |
||
1462 | { |
||
1463 | listenerList.add(CaretListener.class,listener);
|
||
1464 | } |
||
1465 | |||
1466 | /**
|
||
1467 | * Removes a caret change listener from this text area.
|
||
1468 | * @param listener The listener
|
||
1469 | */
|
||
1470 | public final void removeCaretListener(CaretListener listener) |
||
1471 | { |
||
1472 | listenerList.remove(CaretListener.class,listener);
|
||
1473 | } |
||
1474 | |||
1475 | /**
|
||
1476 | * Deletes the selected text from the text area and places it
|
||
1477 | * into the clipboard.
|
||
1478 | */
|
||
1479 | public void cut() |
||
1480 | { |
||
1481 | if(editable)
|
||
1482 | { |
||
1483 | copy(); |
||
1484 | setSelectedText("");
|
||
1485 | } |
||
1486 | } |
||
1487 | |||
1488 | /**
|
||
1489 | * Places the selected text into the clipboard.
|
||
1490 | */
|
||
1491 | public void copy() |
||
1492 | { |
||
1493 | if(selectionStart != selectionEnd)
|
||
1494 | { |
||
1495 | Clipboard clipboard = getToolkit().getSystemClipboard();
|
||
1496 | |||
1497 | String selection = getSelectedText();
|
||
1498 | |||
1499 | int repeatCount = inputHandler.getRepeatCount();
|
||
1500 | StringBuffer buf = new StringBuffer(); |
||
1501 | for(int i = 0; i < repeatCount; i++) |
||
1502 | buf.append(selection); |
||
1503 | |||
1504 | clipboard.setContents(new StringSelection(buf.toString()),null); |
||
1505 | } |
||
1506 | } |
||
1507 | |||
1508 | /**
|
||
1509 | * Inserts the clipboard contents into the text.
|
||
1510 | */
|
||
1511 | public void paste() |
||
1512 | { |
||
1513 | if(editable)
|
||
1514 | { |
||
1515 | Clipboard clipboard = getToolkit().getSystemClipboard();
|
||
1516 | try
|
||
1517 | { |
||
1518 | // The MacOS MRJ doesn't convert \r to \n,
|
||
1519 | // so do it here
|
||
1520 | String selection = ((String)clipboard |
||
1521 | .getContents(this).getTransferData(
|
||
1522 | DataFlavor.stringFlavor))
|
||
1523 | .replace('\r','\n'); |
||
1524 | |||
1525 | int repeatCount = inputHandler.getRepeatCount();
|
||
1526 | StringBuffer buf = new StringBuffer(); |
||
1527 | for(int i = 0; i < repeatCount; i++) |
||
1528 | buf.append(selection); |
||
1529 | selection = buf.toString(); |
||
1530 | setSelectedText(selection); |
||
1531 | } |
||
1532 | catch(Exception e) |
||
1533 | { |
||
1534 | getToolkit().beep(); |
||
1535 | System.err.println("Clipboard does not" |
||
1536 | + " contain a string");
|
||
1537 | } |
||
1538 | } |
||
1539 | } |
||
1540 | |||
1541 | /**
|
||
1542 | * Called by the AWT when this component is removed from it's parent.
|
||
1543 | * This stops clears the currently focused component.
|
||
1544 | */
|
||
1545 | public void removeNotify() |
||
1546 | { |
||
1547 | super.removeNotify();
|
||
1548 | if(focusedComponent == this) |
||
1549 | focusedComponent = null;
|
||
1550 | } |
||
1551 | |||
1552 | /**
|
||
1553 | * Forwards key events directly to the input handler.
|
||
1554 | * This is slightly faster than using a KeyListener
|
||
1555 | * because some Swing overhead is avoided.
|
||
1556 | */
|
||
1557 | public void processKeyEvent(KeyEvent evt) |
||
1558 | { |
||
1559 | if(inputHandler == null) |
||
1560 | return;
|
||
1561 | switch(evt.getID())
|
||
1562 | { |
||
1563 | case KeyEvent.KEY_TYPED: |
||
1564 | inputHandler.keyTyped(evt); |
||
1565 | break;
|
||
1566 | case KeyEvent.KEY_PRESSED: |
||
1567 | inputHandler.keyPressed(evt); |
||
1568 | break;
|
||
1569 | case KeyEvent.KEY_RELEASED: |
||
1570 | inputHandler.keyReleased(evt); |
||
1571 | break;
|
||
1572 | } |
||
1573 | } |
||
1574 | |||
1575 | // protected members
|
||
1576 | protected static String CENTER = "center"; |
||
1577 | protected static String RIGHT = "right"; |
||
1578 | protected static String BOTTOM = "bottom"; |
||
1579 | |||
1580 | protected static JEditTextArea focusedComponent; |
||
1581 | protected static Timer caretTimer; |
||
1582 | |||
1583 | protected TextAreaPainter painter;
|
||
1584 | |||
1585 | protected JPopupMenu popup; |
||
1586 | |||
1587 | protected EventListenerList listenerList; |
||
1588 | protected MutableCaretEvent caretEvent;
|
||
1589 | |||
1590 | protected boolean caretBlinks; |
||
1591 | protected boolean caretVisible; |
||
1592 | protected boolean blink; |
||
1593 | |||
1594 | protected boolean editable; |
||
1595 | |||
1596 | protected int firstLine; |
||
1597 | protected int visibleLines; |
||
1598 | protected int electricScroll; |
||
1599 | |||
1600 | protected int horizontalOffset; |
||
1601 | |||
1602 | protected JScrollBar vertical; |
||
1603 | protected JScrollBar horizontal; |
||
1604 | protected boolean scrollBarsInitialized; |
||
1605 | |||
1606 | protected InputHandler inputHandler;
|
||
1607 | protected SyntaxDocument document;
|
||
1608 | protected DocumentHandler documentHandler;
|
||
1609 | |||
1610 | protected Segment lineSegment; |
||
1611 | |||
1612 | protected int selectionStart; |
||
1613 | protected int selectionStartLine; |
||
1614 | protected int selectionEnd; |
||
1615 | protected int selectionEndLine; |
||
1616 | protected boolean biasLeft; |
||
1617 | |||
1618 | protected int bracketPosition; |
||
1619 | protected int bracketLine; |
||
1620 | |||
1621 | protected int magicCaret; |
||
1622 | protected boolean overwrite; |
||
1623 | protected boolean rectSelect; |
||
1624 | |||
1625 | protected void fireCaretEvent() |
||
1626 | { |
||
1627 | Object[] listeners = listenerList.getListenerList(); |
||
1628 | for(int i = listeners.length - 2; i >= 0; i--) |
||
1629 | { |
||
1630 | if(listeners[i] == CaretListener.class) |
||
1631 | { |
||
1632 | ((CaretListener)listeners[i+1]).caretUpdate(caretEvent); |
||
1633 | } |
||
1634 | } |
||
1635 | super.validate();
|
||
1636 | } |
||
1637 | |||
1638 | protected void updateBracketHighlight(int newCaretPosition) |
||
1639 | { |
||
1640 | if(newCaretPosition == 0) |
||
1641 | { |
||
1642 | bracketPosition = bracketLine = -1;
|
||
1643 | return;
|
||
1644 | } |
||
1645 | |||
1646 | try
|
||
1647 | { |
||
1648 | int offset = TextUtilities.findMatchingBracket(
|
||
1649 | document,newCaretPosition - 1);
|
||
1650 | if(offset != -1) |
||
1651 | { |
||
1652 | bracketLine = getLineOfOffset(offset); |
||
1653 | bracketPosition = offset - getLineStartOffset(bracketLine); |
||
1654 | return;
|
||
1655 | } |
||
1656 | } |
||
1657 | catch(BadLocationException bl) |
||
1658 | { |
||
1659 | bl.printStackTrace(); |
||
1660 | } |
||
1661 | |||
1662 | bracketLine = bracketPosition = -1;
|
||
1663 | } |
||
1664 | |||
1665 | protected void documentChanged(DocumentEvent evt) |
||
1666 | { |
||
1667 | DocumentEvent.ElementChange ch = evt.getChange(
|
||
1668 | document.getDefaultRootElement()); |
||
1669 | |||
1670 | int count;
|
||
1671 | if(ch == null) |
||
1672 | count = 0;
|
||
1673 | else
|
||
1674 | count = ch.getChildrenAdded().length - |
||
1675 | ch.getChildrenRemoved().length; |
||
1676 | |||
1677 | int line = getLineOfOffset(evt.getOffset());
|
||
1678 | if(count == 0) |
||
1679 | { |
||
1680 | painter.invalidateLine(line); |
||
1681 | } |
||
1682 | // do magic stuff
|
||
1683 | else if(line < firstLine) |
||
1684 | { |
||
1685 | setFirstLine(firstLine + count); |
||
1686 | } |
||
1687 | // end of magic stuff
|
||
1688 | else
|
||
1689 | { |
||
1690 | painter.invalidateLineRange(line,firstLine + visibleLines); |
||
1691 | updateScrollBars(); |
||
1692 | } |
||
1693 | } |
||
1694 | |||
1695 | class ScrollLayout implements LayoutManager |
||
1696 | { |
||
1697 | public void addLayoutComponent(String name, Component comp) |
||
1698 | { |
||
1699 | if(name.equals(CENTER))
|
||
1700 | center = comp; |
||
1701 | else if(name.equals(RIGHT)) |
||
1702 | right = comp; |
||
1703 | else if(name.equals(BOTTOM)) |
||
1704 | bottom = comp; |
||
1705 | else if(name.equals(LEFT_OF_SCROLLBAR)) |
||
1706 | leftOfScrollBar.addElement(comp); |
||
1707 | } |
||
1708 | |||
1709 | public void removeLayoutComponent(Component comp) |
||
1710 | { |
||
1711 | if(center == comp)
|
||
1712 | center = null;
|
||
1713 | if(right == comp)
|
||
1714 | right = null;
|
||
1715 | if(bottom == comp)
|
||
1716 | bottom = null;
|
||
1717 | else
|
||
1718 | leftOfScrollBar.removeElement(comp); |
||
1719 | } |
||
1720 | |||
1721 | public Dimension preferredLayoutSize(Container parent) |
||
1722 | { |
||
1723 | Dimension dim = new Dimension(); |
||
1724 | Insets insets = getInsets();
|
||
1725 | dim.width = insets.left + insets.right; |
||
1726 | dim.height = insets.top + insets.bottom; |
||
1727 | |||
1728 | Dimension centerPref = center.getPreferredSize();
|
||
1729 | dim.width += centerPref.width; |
||
1730 | dim.height += centerPref.height; |
||
1731 | Dimension rightPref = right.getPreferredSize();
|
||
1732 | dim.width += rightPref.width; |
||
1733 | Dimension bottomPref = bottom.getPreferredSize();
|
||
1734 | dim.height += bottomPref.height; |
||
1735 | |||
1736 | return dim;
|
||
1737 | } |
||
1738 | |||
1739 | public Dimension minimumLayoutSize(Container parent) |
||
1740 | { |
||
1741 | Dimension dim = new Dimension(); |
||
1742 | Insets insets = getInsets();
|
||
1743 | dim.width = insets.left + insets.right; |
||
1744 | dim.height = insets.top + insets.bottom; |
||
1745 | |||
1746 | Dimension centerPref = center.getMinimumSize();
|
||
1747 | dim.width += centerPref.width; |
||
1748 | dim.height += centerPref.height; |
||
1749 | Dimension rightPref = right.getMinimumSize();
|
||
1750 | dim.width += rightPref.width; |
||
1751 | Dimension bottomPref = bottom.getMinimumSize();
|
||
1752 | dim.height += bottomPref.height; |
||
1753 | |||
1754 | return dim;
|
||
1755 | } |
||
1756 | |||
1757 | public void layoutContainer(Container parent) |
||
1758 | { |
||
1759 | Dimension size = parent.getSize();
|
||
1760 | Insets insets = parent.getInsets();
|
||
1761 | int itop = insets.top;
|
||
1762 | int ileft = insets.left;
|
||
1763 | int ibottom = insets.bottom;
|
||
1764 | int iright = insets.right;
|
||
1765 | |||
1766 | int rightWidth = right.getPreferredSize().width;
|
||
1767 | int bottomHeight = bottom.getPreferredSize().height;
|
||
1768 | int centerWidth = size.width - rightWidth - ileft - iright;
|
||
1769 | int centerHeight = size.height - bottomHeight - itop - ibottom;
|
||
1770 | |||
1771 | center.setBounds( |
||
1772 | ileft, |
||
1773 | itop, |
||
1774 | centerWidth, |
||
1775 | centerHeight); |
||
1776 | |||
1777 | right.setBounds( |
||
1778 | ileft + centerWidth, |
||
1779 | itop, |
||
1780 | rightWidth, |
||
1781 | centerHeight); |
||
1782 | |||
1783 | // Lay out all status components, in order
|
||
1784 | Enumeration status = leftOfScrollBar.elements();
|
||
1785 | while(status.hasMoreElements())
|
||
1786 | { |
||
1787 | Component comp = (Component)status.nextElement(); |
||
1788 | Dimension dim = comp.getPreferredSize();
|
||
1789 | comp.setBounds(ileft, |
||
1790 | itop + centerHeight, |
||
1791 | dim.width, |
||
1792 | bottomHeight); |
||
1793 | ileft += dim.width; |
||
1794 | } |
||
1795 | |||
1796 | bottom.setBounds( |
||
1797 | ileft, |
||
1798 | itop + centerHeight, |
||
1799 | size.width - rightWidth - ileft - iright, |
||
1800 | bottomHeight); |
||
1801 | } |
||
1802 | |||
1803 | // private members
|
||
1804 | private Component center; |
||
1805 | private Component right; |
||
1806 | private Component bottom; |
||
1807 | private Vector leftOfScrollBar = new Vector(); |
||
1808 | } |
||
1809 | |||
1810 | static class CaretBlinker implements ActionListener |
||
1811 | { |
||
1812 | public void actionPerformed(ActionEvent evt) |
||
1813 | { |
||
1814 | if(focusedComponent != null |
||
1815 | && focusedComponent.hasFocus()) |
||
1816 | focusedComponent.blinkCaret(); |
||
1817 | } |
||
1818 | } |
||
1819 | |||
1820 | class MutableCaretEvent extends CaretEvent |
||
1821 | { |
||
1822 | MutableCaretEvent() |
||
1823 | { |
||
1824 | super(JEditTextArea.this);
|
||
1825 | } |
||
1826 | |||
1827 | public int getDot() |
||
1828 | { |
||
1829 | return getCaretPosition();
|
||
1830 | } |
||
1831 | |||
1832 | public int getMark() |
||
1833 | { |
||
1834 | return getMarkPosition();
|
||
1835 | } |
||
1836 | } |
||
1837 | |||
1838 | class AdjustHandler implements AdjustmentListener |
||
1839 | { |
||
1840 | public void adjustmentValueChanged(final AdjustmentEvent evt) |
||
1841 | { |
||
1842 | if(!scrollBarsInitialized)
|
||
1843 | return;
|
||
1844 | |||
1845 | // If this is not done, mousePressed events accumilate
|
||
1846 | // and the result is that scrolling doesn't stop after
|
||
1847 | // the mouse is released
|
||
1848 | SwingUtilities.invokeLater(new Runnable() { |
||
1849 | public void run() |
||
1850 | { |
||
1851 | if(evt.getAdjustable() == vertical)
|
||
1852 | setFirstLine(vertical.getValue()); |
||
1853 | else
|
||
1854 | setHorizontalOffset(-horizontal.getValue()); |
||
1855 | } |
||
1856 | }); |
||
1857 | } |
||
1858 | } |
||
1859 | |||
1860 | class ComponentHandler extends ComponentAdapter |
||
1861 | { |
||
1862 | public void componentResized(ComponentEvent evt) |
||
1863 | { |
||
1864 | recalculateVisibleLines(); |
||
1865 | scrollBarsInitialized = true;
|
||
1866 | } |
||
1867 | } |
||
1868 | |||
1869 | class DocumentHandler implements DocumentListener |
||
1870 | { |
||
1871 | public void insertUpdate(DocumentEvent evt) |
||
1872 | { |
||
1873 | documentChanged(evt); |
||
1874 | |||
1875 | int offset = evt.getOffset();
|
||
1876 | int length = evt.getLength();
|
||
1877 | |||
1878 | int newStart;
|
||
1879 | int newEnd;
|
||
1880 | |||
1881 | if(selectionStart > offset || (selectionStart
|
||
1882 | == selectionEnd && selectionStart == offset)) |
||
1883 | newStart = selectionStart + length; |
||
1884 | else
|
||
1885 | newStart = selectionStart; |
||
1886 | |||
1887 | if(selectionEnd >= offset)
|
||
1888 | newEnd = selectionEnd + length; |
||
1889 | else
|
||
1890 | newEnd = selectionEnd; |
||
1891 | |||
1892 | select(newStart,newEnd); |
||
1893 | } |
||
1894 | |||
1895 | public void removeUpdate(DocumentEvent evt) |
||
1896 | { |
||
1897 | documentChanged(evt); |
||
1898 | |||
1899 | int offset = evt.getOffset();
|
||
1900 | int length = evt.getLength();
|
||
1901 | |||
1902 | int newStart;
|
||
1903 | int newEnd;
|
||
1904 | |||
1905 | if(selectionStart > offset)
|
||
1906 | { |
||
1907 | if(selectionStart > offset + length)
|
||
1908 | newStart = selectionStart - length; |
||
1909 | else
|
||
1910 | newStart = offset; |
||
1911 | } |
||
1912 | else
|
||
1913 | newStart = selectionStart; |
||
1914 | |||
1915 | if(selectionEnd > offset)
|
||
1916 | { |
||
1917 | if(selectionEnd > offset + length)
|
||
1918 | newEnd = selectionEnd - length; |
||
1919 | else
|
||
1920 | newEnd = offset; |
||
1921 | } |
||
1922 | else
|
||
1923 | newEnd = selectionEnd; |
||
1924 | |||
1925 | select(newStart,newEnd); |
||
1926 | } |
||
1927 | |||
1928 | public void changedUpdate(DocumentEvent evt) |
||
1929 | { |
||
1930 | } |
||
1931 | } |
||
1932 | |||
1933 | class DragHandler implements MouseMotionListener |
||
1934 | { |
||
1935 | public void mouseDragged(MouseEvent evt) |
||
1936 | { |
||
1937 | if(popup != null && popup.isVisible()) |
||
1938 | return;
|
||
1939 | |||
1940 | setSelectionRectangular((evt.getModifiers() |
||
1941 | & InputEvent.CTRL_MASK) != 0); |
||
1942 | select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY())); |
||
1943 | } |
||
1944 | |||
1945 | public void mouseMoved(MouseEvent evt) {} |
||
1946 | } |
||
1947 | |||
1948 | class FocusHandler implements FocusListener |
||
1949 | { |
||
1950 | public void focusGained(FocusEvent evt) |
||
1951 | { |
||
1952 | setCaretVisible(true);
|
||
1953 | focusedComponent = JEditTextArea.this; |
||
1954 | } |
||
1955 | |||
1956 | public void focusLost(FocusEvent evt) |
||
1957 | { |
||
1958 | setCaretVisible(false);
|
||
1959 | focusedComponent = null;
|
||
1960 | } |
||
1961 | } |
||
1962 | |||
1963 | class MouseHandler extends MouseAdapter |
||
1964 | { |
||
1965 | public void mousePressed(MouseEvent evt) |
||
1966 | { |
||
1967 | requestFocus(); |
||
1968 | |||
1969 | // Focus events not fired sometimes?
|
||
1970 | setCaretVisible(true);
|
||
1971 | focusedComponent = JEditTextArea.this; |
||
1972 | |||
1973 | if((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0 |
||
1974 | && popup != null)
|
||
1975 | { |
||
1976 | popup.show(painter,evt.getX(),evt.getY()); |
||
1977 | return;
|
||
1978 | } |
||
1979 | |||
1980 | int line = yToLine(evt.getY());
|
||
1981 | int offset = xToOffset(line,evt.getX());
|
||
1982 | int dot = getLineStartOffset(line) + offset;
|
||
1983 | |||
1984 | switch(evt.getClickCount())
|
||
1985 | { |
||
1986 | case 1: |
||
1987 | doSingleClick(evt,line,offset,dot); |
||
1988 | break;
|
||
1989 | case 2: |
||
1990 | // It uses the bracket matching stuff, so
|
||
1991 | // it can throw a BLE
|
||
1992 | try
|
||
1993 | { |
||
1994 | doDoubleClick(evt,line,offset,dot); |
||
1995 | } |
||
1996 | catch(BadLocationException bl) |
||
1997 | { |
||
1998 | bl.printStackTrace(); |
||
1999 | } |
||
2000 | break;
|
||
2001 | case 3: |
||
2002 | doTripleClick(evt,line,offset,dot); |
||
2003 | break;
|
||
2004 | } |
||
2005 | } |
||
2006 | |||
2007 | private void doSingleClick(MouseEvent evt, int line, |
||
2008 | int offset, int dot) |
||
2009 | { |
||
2010 | if((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) |
||
2011 | { |
||
2012 | rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0; |
||
2013 | select(getMarkPosition(),dot); |
||
2014 | } |
||
2015 | else
|
||
2016 | setCaretPosition(dot); |
||
2017 | } |
||
2018 | |||
2019 | private void doDoubleClick(MouseEvent evt, int line, |
||
2020 | int offset, int dot) throws BadLocationException |
||
2021 | { |
||
2022 | // Ignore empty lines
|
||
2023 | if(getLineLength(line) == 0) |
||
2024 | return;
|
||
2025 | |||
2026 | try
|
||
2027 | { |
||
2028 | int bracket = TextUtilities.findMatchingBracket(
|
||
2029 | document,Math.max(0,dot - 1)); |
||
2030 | if(bracket != -1) |
||
2031 | { |
||
2032 | int mark = getMarkPosition();
|
||
2033 | // Hack
|
||
2034 | if(bracket > mark)
|
||
2035 | { |
||
2036 | bracket++; |
||
2037 | mark--; |
||
2038 | } |
||
2039 | select(mark,bracket); |
||
2040 | return;
|
||
2041 | } |
||
2042 | } |
||
2043 | catch(BadLocationException bl) |
||
2044 | { |
||
2045 | bl.printStackTrace(); |
||
2046 | } |
||
2047 | |||
2048 | // Ok, it's not a bracket... select the word
|
||
2049 | String lineText = getLineText(line);
|
||
2050 | char ch = lineText.charAt(Math.max(0,offset - 1)); |
||
2051 | |||
2052 | String noWordSep = (String)document.getProperty("noWordSep"); |
||
2053 | if(noWordSep == null) |
||
2054 | noWordSep = "";
|
||
2055 | |||
2056 | // If the user clicked on a non-letter char,
|
||
2057 | // we select the surrounding non-letters
|
||
2058 | boolean selectNoLetter = (!Character |
||
2059 | .isLetterOrDigit(ch) |
||
2060 | && noWordSep.indexOf(ch) == -1);
|
||
2061 | |||
2062 | int wordStart = 0; |
||
2063 | |||
2064 | for(int i = offset - 1; i >= 0; i--) |
||
2065 | { |
||
2066 | ch = lineText.charAt(i); |
||
2067 | if(selectNoLetter ^ (!Character |
||
2068 | .isLetterOrDigit(ch) && |
||
2069 | noWordSep.indexOf(ch) == -1))
|
||
2070 | { |
||
2071 | wordStart = i + 1;
|
||
2072 | break;
|
||
2073 | } |
||
2074 | } |
||
2075 | |||
2076 | int wordEnd = lineText.length();
|
||
2077 | for(int i = offset; i < lineText.length(); i++) |
||
2078 | { |
||
2079 | ch = lineText.charAt(i); |
||
2080 | if(selectNoLetter ^ (!Character |
||
2081 | .isLetterOrDigit(ch) && |
||
2082 | noWordSep.indexOf(ch) == -1))
|
||
2083 | { |
||
2084 | wordEnd = i; |
||
2085 | break;
|
||
2086 | } |
||
2087 | } |
||
2088 | |||
2089 | int lineStart = getLineStartOffset(line);
|
||
2090 | select(lineStart + wordStart,lineStart + wordEnd); |
||
2091 | |||
2092 | /*
|
||
2093 | String lineText = getLineText(line);
|
||
2094 | String noWordSep = (String)document.getProperty("noWordSep");
|
||
2095 | int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep);
|
||
2096 | int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep);
|
||
2097 | |||
2098 | int lineStart = getLineStartOffset(line);
|
||
2099 | select(lineStart + wordStart,lineStart + wordEnd);
|
||
2100 | */
|
||
2101 | } |
||
2102 | |||
2103 | private void doTripleClick(MouseEvent evt, int line, |
||
2104 | int offset, int dot) |
||
2105 | { |
||
2106 | select(getLineStartOffset(line),getLineEndOffset(line)-1);
|
||
2107 | } |
||
2108 | } |
||
2109 | |||
2110 | class CaretUndo extends AbstractUndoableEdit |
||
2111 | { |
||
2112 | private int start; |
||
2113 | private int end; |
||
2114 | |||
2115 | CaretUndo(int start, int end) |
||
2116 | { |
||
2117 | this.start = start;
|
||
2118 | this.end = end;
|
||
2119 | } |
||
2120 | |||
2121 | public boolean isSignificant() |
||
2122 | { |
||
2123 | return false; |
||
2124 | } |
||
2125 | |||
2126 | public String getPresentationName() |
||
2127 | { |
||
2128 | return "caret move"; |
||
2129 | } |
||
2130 | |||
2131 | public void undo() throws CannotUndoException |
||
2132 | { |
||
2133 | super.undo();
|
||
2134 | |||
2135 | select(start,end); |
||
2136 | } |
||
2137 | |||
2138 | public void redo() throws CannotRedoException |
||
2139 | { |
||
2140 | super.redo();
|
||
2141 | |||
2142 | select(start,end); |
||
2143 | } |
||
2144 | |||
2145 | public boolean addEdit(UndoableEdit edit) |
||
2146 | { |
||
2147 | if(edit instanceof CaretUndo) |
||
2148 | { |
||
2149 | CaretUndo cedit = (CaretUndo)edit; |
||
2150 | start = cedit.start; |
||
2151 | end = cedit.end; |
||
2152 | cedit.die(); |
||
2153 | |||
2154 | return true; |
||
2155 | } |
||
2156 | else
|
||
2157 | return false; |
||
2158 | } |
||
2159 | } |
||
2160 | |||
2161 | static
|
||
2162 | { |
||
2163 | caretTimer = new Timer(500,new CaretBlinker()); |
||
2164 | caretTimer.setInitialDelay(500);
|
||
2165 | caretTimer.start(); |
||
2166 | } |
||
2167 | } |