source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rtextarea/RTextAreaUI.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 17.5 KB
Line 
1/*
2 * 04/25/2007
3 *
4 * RTextAreaUI.java - UI used by instances of RTextArea.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rtextarea;
10
11import java.awt.*;
12import java.awt.event.*;
13import javax.swing.*;
14import javax.swing.text.*;
15import javax.swing.border.Border;
16import javax.swing.plaf.*;
17import javax.swing.plaf.basic.*;
18
19
20/**
21 * The UI used by instances of <code>RTextArea</code>. This UI takes into
22 * account all of the "extras" involved in an <code>RTextArea</code>, including
23 * having a special caret (for insert and overwrite), background images,
24 * highlighting the current line, etc.
25 *
26 * @author Robert Futrell
27 * @version 0.5
28 */
29public class RTextAreaUI extends BasicTextAreaUI implements ViewFactory {
30
31 private static final String SHARED_ACTION_MAP_NAME = "RTextAreaUI.actionMap";
32 private static final String SHARED_INPUT_MAP_NAME = "RTextAreaUI.inputMap";
33
34 protected RTextArea textArea; // The text area for which we are the UI.
35
36 private static final EditorKit defaultKit = new RTextAreaEditorKit();
37 private static final TransferHandler defaultTransferHandler =
38 new RTATextTransferHandler();
39
40 private static final String RTEXTAREA_KEYMAP_NAME = "RTextAreaKeymap";
41
42
43 /**
44 * Creates a UI for an RTextArea.
45 *
46 * @param textArea A text area.
47 * @return The UI.
48 */
49 public static ComponentUI createUI(JComponent textArea) {
50 return new RTextAreaUI(textArea);
51 }
52
53
54 /**
55 * Constructor.
56 *
57 * @param textArea An instance of <code>RTextArea</code>.
58 * @throws IllegalArgumentException If <code>textArea</code> is not an
59 * instance of <code>RTextArea</code>.
60 */
61 public RTextAreaUI(JComponent textArea) {
62 if (!(textArea instanceof RTextArea)) {
63 throw new IllegalArgumentException("RTextAreaUI is for " +
64 "instances of RTextArea only!");
65 }
66 this.textArea = (RTextArea)textArea;
67 }
68
69
70 /**
71 * The Nimbus LAF (and any Synth laf might have similar issues) doesn't set
72 * many UIManager properties that BasicLAF UI's look for. This causes
73 * problems for custom Basic-based UI's such as RTextAreaUI. This method
74 * attempts to detect if Nimbus has been installed, and if so, sets proper
75 * values for some editor properties.
76 *
77 * @param editor The text area.
78 */
79 private void correctNimbusDefaultProblems(JTextComponent editor) {
80
81 // Don't check UIManager.getLookAndFeel().getName() for "Nimbus",
82 // as other Synth-based LaFs might have not set these properties,
83 // in which case we'll need to use our fallback values.
84
85 // Check for null, but not for UIResource, for these properties,
86 // because if Nimbus was installed these values would all be given
87 // null values. Another laf might have successfully installed
88 // UIResource values, which we don't want to override.
89
90 Color c = editor.getCaretColor();
91 if (c==null) {
92 editor.setCaretColor(RTextArea.getDefaultCaretColor());
93 }
94
95 c = editor.getSelectionColor();
96 if (c==null) {
97 c = UIManager.getColor("nimbusSelectionBackground");
98 if (c==null) { // Not Nimbus, but still need a value - fallback
99 c = UIManager.getColor("textHighlight");
100 if (c==null) {
101 c = new ColorUIResource(Color.BLUE);
102 }
103 }
104 editor.setSelectionColor(c);
105 }
106
107 c = editor.getSelectedTextColor();
108 if (c==null) {
109 c = UIManager.getColor("nimbusSelectedText");
110 if (c==null) { // Not Nimbus, but still need a value - fallback
111 c = UIManager.getColor("textHighlightText");
112 if (c==null) {
113 c = new ColorUIResource(Color.WHITE);
114 }
115 }
116 editor.setSelectedTextColor(c);
117 }
118
119 c = editor.getDisabledTextColor();
120 if (c==null) {
121 c = UIManager.getColor("nimbusDisabledText");
122 if (c==null) { // Not Nimbus, but still need a value - fallback
123 c = UIManager.getColor("textInactiveText");
124 if (c==null) {
125 c = new ColorUIResource(Color.DARK_GRAY);
126 }
127 }
128 editor.setDisabledTextColor(c);
129 }
130
131 Border border = editor.getBorder();
132 if (border==null) {
133 editor.setBorder(new BasicBorders.MarginBorder());
134 }
135
136 Insets margin = editor.getMargin();
137 if (margin==null) {
138 editor.setMargin(new InsetsUIResource(2, 2, 2, 2));
139 }
140
141 }
142
143
144 /**
145 * Creates the view for an element. Returns a WrappedPlainView or
146 * PlainView.
147 *
148 * @param elem The element.
149 * @return The view.
150 */
151 public View create(Element elem) {
152 if (textArea.getLineWrap())
153 return new WrappedPlainView(elem, textArea.getWrapStyleWord());
154 else
155 return new PlainView(elem);
156 }
157
158
159 /**
160 * Returns the default caret for an <code>RTextArea</code>. This caret is
161 * capable of displaying itself differently for insert/overwrite modes.
162 *
163 * @return The caret.
164 */
165 protected Caret createCaret() {
166 Caret caret = new ConfigurableCaret();
167 caret.setBlinkRate(500);
168 return caret;
169 }
170
171
172 /**
173 * Creates the keymap for this text area. This takes the super class's
174 * keymap, but sets the default keystroke to be RTextAreaEditorKit's
175 * DefaultKeyTypedAction. This must be done to override the default
176 * keymap's default key-typed action.
177 *
178 * @return The keymap.
179 */
180 protected Keymap createKeymap() {
181
182 // Load the keymap we'll be using (it's saved by
183 // JTextComponent.addKeymap).
184 Keymap map = JTextComponent.getKeymap(RTEXTAREA_KEYMAP_NAME);
185 if (map==null) {
186 Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
187 map = JTextComponent.addKeymap(RTEXTAREA_KEYMAP_NAME, parent);
188 map.setDefaultAction(new RTextAreaEditorKit.DefaultKeyTypedAction());
189 }
190
191 return map;
192
193 }
194
195
196 /**
197 * Creates a default action map. This action map contains actions for all
198 * basic text area work - cut, copy, paste, select, caret motion, etc.<p>
199 *
200 * This isn't named <code>createActionMap()</code> because there is a
201 * package-private member by that name in <code>BasicTextAreaUI</code>,
202 * and some compilers will give warnings that we are not overriding that
203 * method since it is package-private.
204 *
205 * @return The action map.
206 */
207 protected ActionMap createRTextAreaActionMap() {
208
209 // Get the actions of the text area (which in turn gets them from its
210 // DefaultEditorKit).
211 ActionMap map = new ActionMapUIResource();
212 Action[] actions = textArea.getActions();
213 int n = actions.length;
214 for (int i = 0; i < n; i++) {
215 Action a = actions[i];
216 map.put(a.getValue(Action.NAME), a);
217 }
218
219 // Not sure if we need these; not sure they are ever called
220 // (check their NAMEs).
221 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
222 TransferHandler.getCutAction());
223 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
224 TransferHandler.getCopyAction());
225 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
226 TransferHandler.getPasteAction());
227
228 return map;
229
230 }
231
232
233 /**
234 * Returns the name to use to cache/fetch the shared action map. This
235 * should be overridden by subclasses if the subclass has its own custom
236 * editor kit to install, so its actions get picked up.
237 *
238 * @return The name of the cached action map.
239 */
240 protected String getActionMapName() {
241 return SHARED_ACTION_MAP_NAME;
242 }
243
244
245 /**
246 * Fetches the EditorKit for the UI.
247 *
248 * @param tc the text component for which this UI is installed
249 * @return the editor capabilities
250 * @see TextUI#getEditorKit
251 */
252 public EditorKit getEditorKit(JTextComponent tc) {
253 return defaultKit;
254 }
255
256
257 /**
258 * Returns the text area for which we are the UI.
259 *
260 * @return The text area.
261 */
262 public RTextArea getRTextArea() {
263 return textArea;
264 }
265
266
267 /**
268 * Returns an action map to use by a text area.<p>
269 *
270 * This method is not named <code>getActionMap()</code> because there is
271 * a package-private method in <code>BasicTextAreaUI</code> with that name.
272 * Thus, creating a new method with that name causes certain compilers to
273 * issue warnings that you are not actually overriding the original method
274 * (since it is package-private).
275 *
276 * @return The action map.
277 * @see #createRTextAreaActionMap()
278 */
279 private ActionMap getRTextAreaActionMap() {
280
281 // Get the UIManager-cached action map; if this is the first
282 // RTextArea created, create the action map and cache it.
283 ActionMap map = (ActionMap)UIManager.get(getActionMapName());
284 if (map==null) {
285 map = createRTextAreaActionMap();
286 UIManager.put(getActionMapName(), map);
287 }
288
289 ActionMap componentMap = new ActionMapUIResource();
290 componentMap.put("requestFocus", new FocusAction());
291
292 if (map != null)
293 componentMap.setParent(map);
294 return componentMap;
295
296 }
297
298
299 /**
300 * Get the InputMap to use for the UI.<p>
301 *
302 * This method is not named <code>getInputMap()</code> because there is
303 * a package-private method in <code>BasicTextAreaUI</code> with that name.
304 * Thus, creating a new method with that name causes certain compilers to
305 * issue warnings that you are not actually overriding the original method
306 * (since it is package-private).
307 */
308 protected InputMap getRTextAreaInputMap() {
309 InputMap map = new InputMapUIResource();
310 InputMap shared = (InputMap)UIManager.get(SHARED_INPUT_MAP_NAME);
311 if (shared==null) {
312 shared = new RTADefaultInputMap();
313 UIManager.put(SHARED_INPUT_MAP_NAME, shared);
314 }
315 //KeyStroke[] keys = shared.allKeys();
316 //for (int i=0; i<keys.length; i++)
317 // System.err.println(keys[i] + " -> " + shared.get(keys[i]));
318 map.setParent(shared);
319 return map;
320 }
321
322
323 /**
324 * Gets the allocation to give the root View. Due
325 * to an unfortunate set of historical events this
326 * method is inappropriately named. The Rectangle
327 * returned has nothing to do with visibility.
328 * The component must have a non-zero positive size for
329 * this translation to be computed.
330 *
331 * @return the bounding box for the root view
332 */
333 protected Rectangle getVisibleEditorRect() {
334 Rectangle alloc = textArea.getBounds();
335 if ((alloc.width > 0) && (alloc.height > 0)) {
336 alloc.x = alloc.y = 0;
337 Insets insets = textArea.getInsets();
338 alloc.x += insets.left;
339 alloc.y += insets.top;
340 alloc.width -= insets.left + insets.right;
341 alloc.height -= insets.top + insets.bottom;
342 return alloc;
343 }
344 return null;
345 }
346
347
348 protected void installDefaults() {
349
350 super.installDefaults();
351
352 JTextComponent editor = getComponent();
353 editor.setFont(RTextAreaBase.getDefaultFont());
354
355 // Nimbus (and possibly other Synth lafs) doesn't play by BasicLaf
356 // rules and doesn't set properties needed by custom BasicTextAreaUI's.
357 correctNimbusDefaultProblems(editor);
358
359 editor.setTransferHandler(defaultTransferHandler);
360
361 }
362
363
364 /**
365 * {@inheritDoc}
366 */
367 protected void installKeyboardActions() {
368
369 // NOTE: Don't call super.installKeyboardActions(), as that causes
370 // JTextAreas to stop responding to certain keystrokes if an RTextArea
371 // is the first-instantiated text area. This is because of the code
372 // path installKeyboardActions() -> getActionMap() -> createActionMap().
373 // In BasicTextUI#createActionMap(), "editor.getActions()" is called,
374 // and the current editor's returned Actions are used to create the
375 // ActionMap, which is then cached and used in all future J/RTextAreas.
376 // Unfortunately, RTextArea actions don't worn in JTextAreas.
377 //super.installKeyboardActions();
378
379 RTextArea textArea = getRTextArea();
380
381 // backward compatibility support... keymaps for the UI
382 // are now installed in the more friendly input map.
383 textArea.setKeymap(createKeymap());
384
385 // Since BasicTextUI.getInputMap() is package-private, instead use
386 // our own version here.
387 InputMap map = getRTextAreaInputMap();
388 SwingUtilities.replaceUIInputMap(textArea,JComponent.WHEN_FOCUSED,map);
389
390 // Same thing here with action map.
391 ActionMap am = getRTextAreaActionMap();
392 if (am!=null) {
393 SwingUtilities.replaceUIActionMap(textArea, am);
394 }
395
396
397 }
398
399
400 /**
401 * Installs this UI to the given text component.
402 */
403 public void installUI(JComponent c) {
404 if (!(c instanceof RTextArea)) {
405 throw new Error("RTextAreaUI needs an instance of RTextArea!");
406 }
407 super.installUI(c);
408 }
409
410
411 protected void paintBackground(Graphics g) {
412
413 // Only fill in the background if an image isn't being used.
414 Color bg = textArea.getBackground();
415 if (bg!=null) {
416 g.setColor(bg);
417 //g.fillRect(0, 0, textArea.getWidth(), textArea.getHeight());
418 Rectangle r = g.getClipBounds();
419 g.fillRect(r.x,r.y, r.width,r.height);
420 }
421
422 Rectangle visibleRect = textArea.getVisibleRect();
423
424 paintLineHighlights(g);
425 paintCurrentLineHighlight(g, visibleRect);
426 paintMarginLine(g, visibleRect);
427
428 }
429
430
431 /**
432 * Paints the highlighted current line, if it is enabled.
433 *
434 * @param g The graphics context with which to paint.
435 * @param visibleRect The visible rectangle of the text area.
436 */
437 protected void paintCurrentLineHighlight(Graphics g, Rectangle visibleRect) {
438
439 if (textArea.getHighlightCurrentLine()) {
440
441 Caret caret = textArea.getCaret();
442 if (caret.getDot()==caret.getMark()) {
443
444 Color highlight = textArea.getCurrentLineHighlightColor();
445 // NOTE: We use the getLineHeight() method below instead
446 // of currentCaretRect.height because of a bug where
447 // currentCaretRect.height is incorrect when an
448 // RSyntaxTextArea is first displayed (it is initialized
449 // with the text area's font.getHeight() (via RTextArea),
450 // but isn't changed to account for the syntax styles
451 // before it is displayed).
452 //int height = textArea.currentCaretRect.height);
453 int height = textArea.getLineHeight();
454
455 if (textArea.getFadeCurrentLineHighlight()) {
456 Graphics2D g2d = (Graphics2D)g;
457 Color bg = textArea.getBackground();
458 GradientPaint paint = new GradientPaint(
459 visibleRect.x,0, highlight,
460 visibleRect.x+visibleRect.width,0,
461 bg==null ? Color.WHITE : bg);
462 g2d.setPaint(paint);
463 g2d.fillRect(visibleRect.x,textArea.currentCaretY,
464 visibleRect.width, height);
465 }
466 else {
467 g.setColor(highlight);
468 g.fillRect(visibleRect.x,textArea.currentCaretY,
469 visibleRect.width, height);
470 }
471
472 } // End of if (caret.getDot()==caret.getMark()).
473
474 } // End of if (textArea.isCurrentLineHighlightEnabled()...
475
476 }
477
478
479 /**
480 * Paints any line highlights.
481 *
482 * @param g The graphics context.
483 */
484 protected void paintLineHighlights(Graphics g) {
485 LineHighlightManager lhm = textArea.getLineHighlightManager();
486 if (lhm!=null) {
487 lhm.paintLineHighlights(g);
488 }
489 }
490
491
492 /**
493 * Draws the "margin line" if enabled.
494 *
495 * @param g The graphics context to paint with.
496 * @param visibleRect The visible rectangle of this text area.
497 */
498 protected void paintMarginLine(Graphics g, Rectangle visibleRect) {
499 if (textArea.isMarginLineEnabled()) {
500 g.setColor(textArea.getMarginLineColor());
501 Insets insets = textArea.getInsets();
502 int marginLineX = textArea.getMarginLinePixelLocation() +
503 (insets==null ? 0 : insets.left);
504 g.drawLine(marginLineX,visibleRect.y,
505 marginLineX,visibleRect.y+visibleRect.height);
506 }
507 }
508
509
510 /**
511 * Returns the y-coordinate of the specified line.<p>
512 *
513 * The default implementation is equivalent to:
514 * <pre>
515 * int startOffs = textArea.getLineStartOffset(line);
516 * return yForLineContaining(startOffs);</code>
517 * </pre>
518 *
519 * Subclasses that can calculate this value more quickly than traditional
520 * {@link #modelToView(JTextComponent, int)} calls should override this
521 * method to do so. This method may be used when the entire bounding box
522 * isn't needed, to speed up rendering.
523 *
524 * @param line The line number.
525 * @return The y-coordinate of the top of the line, or <code>-1</code> if
526 * this text area doesn't yet have a positive size or the line is
527 * hidden (i.e. from folding).
528 * @throws BadLocationException If <code>line</code> isn't a valid line
529 * number for this document.
530 */
531 public int yForLine(int line) throws BadLocationException {
532 int startOffs = textArea.getLineStartOffset(line);
533 return yForLineContaining(startOffs);
534 }
535
536
537 /**
538 * Returns the y-coordinate of the line containing an offset.<p>
539 *
540 * The default implementation is equivalent to:
541 * <pre>
542 * int line = textArea.getLineOfOffset(offs);
543 * int startOffs = textArea.getLineStartOffset(line);
544 * return modelToView(startOffs).y;</code>
545 * </pre>
546 *
547 * Subclasses that can calculate this value more quickly than traditional
548 * {@link #modelToView(JTextComponent, int)} calls should override this
549 * method to do so. This method may be used when the entire bounding box
550 * isn't needed, to speed up rendering.
551 *
552 * @param offs The offset info the document.
553 * @return The y-coordinate of the top of the offset, or <code>-1</code> if
554 * this text area doesn't yet have a positive size or the line is
555 * hidden (i.e. from folding).
556 * @throws BadLocationException If <code>offs</code> isn't a valid offset
557 * into the document.
558 */
559 public int yForLineContaining(int offs) throws BadLocationException {
560 Rectangle r = modelToView(textArea, offs);
561 return r!=null ? r.y : -1;
562 }
563
564
565 /**
566 * Registered in the ActionMap.
567 */
568 class FocusAction extends AbstractAction {
569
570 public void actionPerformed(ActionEvent e) {
571 textArea.requestFocus();
572 }
573
574 public boolean isEnabled() {
575 return textArea.isEditable();
576 }
577
578 }
579
580
581}
Note: See TracBrowser for help on using the repository browser.