OSDN Git Service

Add NIOS2 support. Code from SourceyG++.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / PlainView.java
1 /* PlainView.java -- 
2    Copyright (C) 2004, 2005, 2006  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.text;
40
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.Font;
44 import java.awt.FontMetrics;
45 import java.awt.Graphics;
46 import java.awt.Rectangle;
47 import java.awt.Shape;
48
49 import javax.swing.SwingUtilities;
50 import javax.swing.event.DocumentEvent;
51 import javax.swing.event.DocumentEvent.ElementChange;
52
53 public class PlainView extends View implements TabExpander
54 {
55   Color selectedColor;
56   Color unselectedColor;
57
58   /**
59    * The color that is used to draw disabled text fields.
60    */
61   Color disabledColor;
62   
63   /**
64    * While painting this is the textcomponent's current start index
65    * of the selection.
66    */
67   int selectionStart;
68
69   /**
70    * While painting this is the textcomponent's current end index
71    * of the selection.
72    */
73   int selectionEnd;
74
75   Font font;
76   
77   /** The length of the longest line in the Document **/
78   float maxLineLength = -1;
79   
80   /** The longest line in the Document **/
81   Element longestLine = null;
82   
83   protected FontMetrics metrics;
84
85   /**
86    * The instance returned by {@link #getLineBuffer()}.
87    */
88   private transient Segment lineBuffer;
89
90   /**
91    * The base offset for tab calculations.
92    */
93   private int tabBase;
94
95   /**
96    * The tab size.
97    */
98   private int tabSize;
99
100   public PlainView(Element elem)
101   {
102     super(elem);
103   }
104
105   /**
106    * @since 1.4
107    */
108   protected void updateMetrics()
109   {
110     Component component = getContainer();
111     Font font = component.getFont();
112
113     if (this.font != font)
114       {
115         this.font = font;
116         metrics = component.getFontMetrics(font);
117         tabSize = getTabSize() * metrics.charWidth('m');
118       }
119   }
120   
121   /**
122    * @since 1.4
123    */
124   protected Rectangle lineToRect(Shape a, int line)
125   {
126     // Ensure metrics are up-to-date.
127     updateMetrics();
128     
129     Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
130     int fontHeight = metrics.getHeight();
131     return new Rectangle(rect.x, rect.y + (line * fontHeight),
132                          rect.width, fontHeight);
133   }
134
135   public Shape modelToView(int position, Shape a, Position.Bias b)
136     throws BadLocationException
137   {
138     // Ensure metrics are up-to-date.
139     updateMetrics();
140     
141     Document document = getDocument();
142
143     // Get rectangle of the line containing position.
144     int lineIndex = getElement().getElementIndex(position);
145     Rectangle rect = lineToRect(a, lineIndex);
146     tabBase = rect.x;
147
148     // Get the rectangle for position.
149     Element line = getElement().getElement(lineIndex);
150     int lineStart = line.getStartOffset();
151     Segment segment = getLineBuffer();
152     document.getText(lineStart, position - lineStart, segment);
153     int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase,
154                                                this, lineStart);
155
156     // Calc the real rectangle.
157     rect.x += xoffset;
158     rect.width = 1;
159     rect.height = metrics.getHeight();
160
161     return rect;
162   }
163   
164   /**
165    * Draws a line of text. The X and Y coordinates specify the start of
166    * the <em>baseline</em> of the line.
167    *
168    * @param lineIndex the index of the line
169    * @param g the graphics to use for drawing the text
170    * @param x the X coordinate of the baseline
171    * @param y the Y coordinate of the baseline
172    */
173   protected void drawLine(int lineIndex, Graphics g, int x, int y)
174   {
175     try
176       {
177         Element line = getElement().getElement(lineIndex);
178         int startOffset = line.getStartOffset();
179         int endOffset = line.getEndOffset() - 1;
180         
181         if (selectionStart <= startOffset)
182           // Selection starts before the line ...
183           if (selectionEnd <= startOffset)
184             {
185               // end ends before the line: Draw completely unselected text.
186               drawUnselectedText(g, x, y, startOffset, endOffset);
187             }
188           else if (selectionEnd <= endOffset)
189             {
190               // and ends within the line: First part is selected,
191               // second is not.
192               x = drawSelectedText(g, x, y, startOffset, selectionEnd);
193               drawUnselectedText(g, x, y, selectionEnd, endOffset);
194             }
195           else
196             // and ends behind the line: Draw completely selected text.
197             drawSelectedText(g, x, y, startOffset, endOffset);
198         else if (selectionStart < endOffset)
199           // Selection starts within the line ..
200           if (selectionEnd < endOffset)
201             {
202               // and ends within it: First part unselected, second part
203               // selected, third part unselected.
204               x = drawUnselectedText(g, x, y, startOffset, selectionStart);
205               x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
206               drawUnselectedText(g, x, y, selectionEnd, endOffset);
207             }
208           else
209             {
210               // and ends behind the line: First part unselected, second
211               // part selected.
212               x = drawUnselectedText(g, x, y, startOffset, selectionStart);
213               drawSelectedText(g, x, y, selectionStart, endOffset);
214             }
215         else
216           // Selection is behind this line: Draw completely unselected text.
217           drawUnselectedText(g, x, y, startOffset, endOffset);
218       }
219     catch (BadLocationException e)
220     {
221       AssertionError ae = new AssertionError("Unexpected bad location");
222       ae.initCause(e);
223       throw ae;
224     }
225   }
226
227   protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
228     throws BadLocationException
229   {
230     g.setColor(selectedColor);
231     Segment segment = getLineBuffer();
232     getDocument().getText(p0, p1 - p0, segment);
233     return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
234   }
235
236   /**
237    * Draws a chunk of unselected text.
238    *
239    * @param g the graphics to use for drawing the text
240    * @param x the X coordinate of the baseline
241    * @param y the Y coordinate of the baseline
242    * @param p0 the start position in the text model
243    * @param p1 the end position in the text model
244    *
245    * @return the X location of the end of the range
246    *
247    * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
248    *         invalid
249    */
250   protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
251     throws BadLocationException
252   {
253     JTextComponent textComponent = (JTextComponent) getContainer();
254     if (textComponent.isEnabled())
255       g.setColor(unselectedColor);
256     else
257       g.setColor(disabledColor);
258
259     Segment segment = getLineBuffer();
260     getDocument().getText(p0, p1 - p0, segment);
261     return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
262   }
263
264   public void paint(Graphics g, Shape s)
265   {
266     // Ensure metrics are up-to-date.
267     updateMetrics();
268     
269     JTextComponent textComponent = (JTextComponent) getContainer();
270
271     selectedColor = textComponent.getSelectedTextColor();
272     unselectedColor = textComponent.getForeground();
273     disabledColor = textComponent.getDisabledTextColor();
274     selectionStart = textComponent.getSelectionStart();
275     selectionEnd = textComponent.getSelectionEnd();
276
277     Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
278     tabBase = rect.x;
279
280     // FIXME: Text may be scrolled.
281     Document document = textComponent.getDocument();
282     Element root = getElement();
283     int height = metrics.getHeight();
284
285     // For layered highlighters we need to paint the layered highlights
286     // before painting any text.
287     LayeredHighlighter hl = null;
288     Highlighter h = textComponent.getHighlighter();
289     if (h instanceof LayeredHighlighter)
290       hl = (LayeredHighlighter) h;
291
292     int count = root.getElementCount();
293
294     // Determine first and last line inside the clip.
295     Rectangle clip = g.getClipBounds();
296     SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height,
297                                        clip);
298     int line0 = (clip.y - rect.y) / height;
299     line0 = Math.max(0, Math.min(line0, count - 1));
300     int line1 = (clip.y + clip.height - rect.y) / height;
301     line1 = Math.max(0, Math.min(line1, count - 1));
302     int y = rect.y + metrics.getAscent() + height * line0;
303     for (int i = line0; i <= line1; i++)
304       {
305         if (hl != null)
306           {
307             Element lineEl = root.getElement(i);
308             // Exclude the trailing newline from beeing highlighted.
309             if (i == count)
310               hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
311                                         lineEl.getEndOffset(), s, textComponent,
312                                         this);
313             else
314               hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
315                                         lineEl.getEndOffset() - 1, s,
316                                         textComponent, this);
317           }
318         drawLine(i, g, rect.x, y);
319         y += height;
320       }
321   }
322
323   /**
324    * Returns the tab size of a tab.  Checks the Document's
325    * properties for PlainDocument.tabSizeAttribute and returns it if it is
326    * defined, otherwise returns 8.
327    * 
328    * @return the tab size.
329    */
330   protected int getTabSize()
331   {
332     Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
333     if (tabSize == null)
334       return 8;
335     return ((Integer)tabSize).intValue();
336   }
337
338   /**
339    * Returns the next tab stop position after a given reference position.
340    *
341    * This implementation ignores the <code>tabStop</code> argument.
342    * 
343    * @param x the current x position in pixels
344    * @param tabStop the position within the text stream that the tab occured at
345    */
346   public float nextTabStop(float x, int tabStop)
347   {
348     float next = x;
349     if (tabSize != 0)
350       {
351         int numTabs = (((int) x) - tabBase) / tabSize;
352         next = tabBase + (numTabs + 1) * tabSize;
353       }
354     return next; 
355   }
356
357   /**
358    * Returns the length of the longest line, used for getting the span
359    * @return the length of the longest line
360    */
361   float determineMaxLineLength()
362   {
363     // if the longest line is cached, return the cached value
364     if (maxLineLength != -1)
365       return maxLineLength;
366     
367     // otherwise we have to go through all the lines and find it
368     Element el = getElement();
369     Segment seg = getLineBuffer();
370     float span = 0;
371     for (int i = 0; i < el.getElementCount(); i++)
372       {
373         Element child = el.getElement(i);
374         int start = child.getStartOffset();
375         int end = child.getEndOffset() - 1;
376         try
377           {
378             el.getDocument().getText(start, end - start, seg);
379           }
380         catch (BadLocationException ex)
381           {
382             AssertionError ae = new AssertionError("Unexpected bad location");
383             ae.initCause(ex);
384             throw ae;
385           }
386         
387         if (seg == null || seg.array == null || seg.count == 0)
388           continue;
389         
390         int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
391         if (width > span)
392           {
393             longestLine = child;
394             span = width;
395           }
396       }
397     maxLineLength = span;
398     return maxLineLength;
399   }
400   
401   public float getPreferredSpan(int axis)
402   {
403     if (axis != X_AXIS && axis != Y_AXIS)
404       throw new IllegalArgumentException();
405
406     // make sure we have the metrics
407     updateMetrics();
408
409     Element el = getElement();
410     float span;
411
412     switch (axis)
413       {
414       case X_AXIS:
415         span = determineMaxLineLength();
416         break;
417       case Y_AXIS:
418       default:
419         span = metrics.getHeight() * el.getElementCount();
420         break;
421       }
422     
423     return span;
424   }
425
426   /**
427    * Maps coordinates from the <code>View</code>'s space into a position
428    * in the document model.
429    *
430    * @param x the x coordinate in the view space
431    * @param y the y coordinate in the view space
432    * @param a the allocation of this <code>View</code>
433    * @param b the bias to use
434    *
435    * @return the position in the document that corresponds to the screen
436    *         coordinates <code>x, y</code>
437    */
438   public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
439   {
440     Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
441     tabBase = rec.x;
442
443     int pos;
444     if ((int) y < rec.y)
445       // Above our area vertically. Return start offset.
446       pos = getStartOffset();
447     else if ((int) y > rec.y + rec.height)
448       // Below our area vertically. Return end offset.
449       pos = getEndOffset() - 1;
450     else
451       {
452         // Inside the allocation vertically. Determine line and X offset.
453         Document doc = getDocument();
454         Element root = doc.getDefaultRootElement();
455         int line = Math.abs(((int) y - rec.y) / metrics.getHeight());
456         if (line >= root.getElementCount())
457           pos = getEndOffset() - 1;
458         else
459           {
460             Element lineEl = root.getElement(line);
461             if (x < rec.x)
462               // To the left of the allocation area.
463               pos = lineEl.getStartOffset();
464             else if (x > rec.x + rec.width)
465               // To the right of the allocation area.
466               pos = lineEl.getEndOffset() - 1;
467             else
468               {
469                 try
470                   {
471                     int p0 = lineEl.getStartOffset();
472                     int p1 = lineEl.getEndOffset();
473                     Segment s = new Segment();
474                     doc.getText(p0, p1 - p0, s);
475                     tabBase = rec.x;
476                     pos = p0 + Utilities.getTabbedTextOffset(s, metrics,
477                                                              tabBase, (int) x,
478                                                              this, p0);
479                   }
480                 catch (BadLocationException ex)
481                   {
482                     // Should not happen.
483                     pos = -1;
484                   }
485               }
486             
487           }
488       }
489     // Bias is always forward.
490     b[0] = Position.Bias.Forward;
491     return pos;
492   }     
493   
494   /**
495    * Since insertUpdate and removeUpdate each deal with children
496    * Elements being both added and removed, they both have to perform
497    * the same checks.  So they both simply call this method.
498    * @param changes the DocumentEvent for the changes to the Document.
499    * @param a the allocation of the View.
500    * @param f the ViewFactory to use for rebuilding.
501    */
502   protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
503   {
504     // This happens during initialization.
505     if (metrics == null)
506       {
507         updateMetrics();
508         preferenceChanged(null, true, true);
509         return;
510       }
511
512     Element element = getElement();
513
514     // Find longest line if it hasn't been initialized yet.
515     if (longestLine == null)
516       findLongestLine(0, element.getElementCount() - 1);
517
518     ElementChange change = changes.getChange(element);
519     if (changes.getType() == DocumentEvent.EventType.INSERT)
520       {
521         // Handles character/line insertion.
522
523         // Determine if lines have been added. In this case we repaint
524         // differently.
525         boolean linesAdded = true;
526         if (change == null)
527           linesAdded = false;
528
529         // Determine the start line.
530         int start;
531         if (linesAdded)
532           start = change.getIndex();
533         else
534           start = element.getElementIndex(changes.getOffset());
535
536         // Determine the length of the updated region.
537         int length = 0;
538         if (linesAdded)
539           length = change.getChildrenAdded().length - 1;
540
541         // Update the longest line and length.
542         int oldMaxLength = (int) maxLineLength;
543         if (longestLine.getEndOffset() < changes.getOffset()
544             || longestLine.getStartOffset() > changes.getOffset()
545                                              + changes.getLength())
546           {
547             findLongestLine(start, start + length);
548           }
549         else
550           {
551             findLongestLine(0, element.getElementCount() - 1);
552           }
553
554         // Trigger a preference change so that the layout gets updated
555         // correctly.
556         preferenceChanged(null, maxLineLength != oldMaxLength, linesAdded);
557
558         // Damage the updated line range.
559         int endLine = start;
560         if (linesAdded)
561           endLine = element.getElementCount() - 1;
562         damageLineRange(start, endLine, a, getContainer());
563
564       }
565     else
566       {
567         // Handles character/lines removals.
568
569         // Update the longest line and length and trigger preference changed.
570         int oldMaxLength = (int) maxLineLength;
571         if (change != null)
572           {
573             // Line(s) have been removed.
574             findLongestLine(0, element.getElementCount() - 1);
575             preferenceChanged(null, maxLineLength != oldMaxLength, true);
576           }
577         else
578           {
579             // No line has been removed.
580             int lineNo = getElement().getElementIndex(changes.getOffset());
581             Element line = getElement().getElement(lineNo);
582             if (longestLine == line)
583               {
584                 findLongestLine(0, element.getElementCount() - 1);
585                 preferenceChanged(null, maxLineLength != oldMaxLength, false);
586             }
587             damageLineRange(lineNo, lineNo, a, getContainer());
588         }
589       }
590   }
591
592   /**
593    * This method is called when something is inserted into the Document
594    * that this View is displaying.
595    * 
596    * @param changes the DocumentEvent for the changes.
597    * @param a the allocation of the View
598    * @param f the ViewFactory used to rebuild
599    */
600   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
601   {
602     updateDamage(changes, a, f);
603   }
604
605   /**
606    * This method is called when something is removed from the Document
607    * that this View is displaying.
608    * 
609    * @param changes the DocumentEvent for the changes.
610    * @param a the allocation of the View
611    * @param f the ViewFactory used to rebuild
612    */
613   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
614   {
615     updateDamage(changes, a, f);
616   }
617   
618   /**
619    * This method is called when attributes were changed in the 
620    * Document in a location that this view is responsible for.
621    */
622   public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
623   {
624     updateDamage(changes, a, f);
625   }
626   
627   /**
628    * Repaint the given line range.  This is called from insertUpdate,
629    * changedUpdate, and removeUpdate when no new lines were added 
630    * and no lines were removed, to repaint the line that was 
631    * modified.
632    * 
633    * @param line0 the start of the range
634    * @param line1 the end of the range
635    * @param a the rendering region of the host
636    * @param host the Component that uses this View (used to call repaint
637    * on that Component)
638    * 
639    * @since 1.4
640    */
641   protected void damageLineRange (int line0, int line1, Shape a, Component host)
642   {
643     if (a == null)
644       return;
645
646     Rectangle rec0 = lineToRect(a, line0);
647     Rectangle rec1 = lineToRect(a, line1);
648
649     if (rec0 == null || rec1 == null)
650       // something went wrong, repaint the entire host to be safe
651       host.repaint();
652     else
653       {
654         Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y,
655                                                            rec0.width,
656                                                            rec0.height, rec1);
657         host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
658                      repaintRec.height);
659       }    
660   }
661
662   /**
663    * Provides a {@link Segment} object, that can be used to fetch text from
664    * the document.
665    *
666    * @returna {@link Segment} object, that can be used to fetch text from
667    *          the document
668    */
669   protected final Segment getLineBuffer()
670   {
671     if (lineBuffer == null)
672       lineBuffer = new Segment();
673     return lineBuffer;
674   }
675
676   /**
677    * Finds and updates the longest line in the view inside an interval of
678    * lines.
679    *
680    * @param start the start of the search interval
681    * @param end the end of the search interval
682    */
683   private void findLongestLine(int start, int end)
684   {
685     for (int i = start; i <= end; i++)
686       {
687         int w = getLineLength(i);
688         if (w > maxLineLength)
689           {
690             maxLineLength = w;
691             longestLine = getElement().getElement(i);
692           }
693       }
694   }
695
696   /**
697    * Determines the length of the specified line.
698    *
699    * @param line the number of the line
700    *
701    * @return the length of the line in pixels
702    */
703   private int getLineLength(int line)
704   {
705     Element lineEl = getElement().getElement(line);
706     Segment buffer = getLineBuffer();
707     try
708       {
709         Document doc = getDocument();
710         doc.getText(lineEl.getStartOffset(),
711                     lineEl.getEndOffset() - lineEl.getStartOffset() - 1,
712                     buffer);
713       }
714     catch (BadLocationException ex)
715       {
716         AssertionError err = new AssertionError("Unexpected bad location");
717         err.initCause(ex);
718         throw err;
719       }
720
721     return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this,
722                                         lineEl.getStartOffset());
723   }
724 }
725