]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/richtext/uiimpl/TabRulerImpl.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / richtext / uiimpl / TabRulerImpl.java
1 /*\r
2  * (C) Copyright IBM Corp. 1998-2004.  All Rights Reserved.\r
3  *\r
4  * The program is provided "as is" without any warranty express or\r
5  * implied, including the warranty of non-infringement and the implied\r
6  * warranties of merchantibility and fitness for a particular purpose.\r
7  * IBM will not be liable for any damages suffered by you as a result\r
8  * of using the Program. In no event will IBM be liable for any\r
9  * special, indirect or consequential damages or lost profits even if\r
10  * IBM has been advised of the possibility of their occurrence. IBM\r
11  * will not be liable for any third party claims against you.\r
12  */\r
13 package com.ibm.richtext.uiimpl;\r
14 \r
15 import java.awt.Color;\r
16 import java.awt.Component;\r
17 import java.awt.Dimension;\r
18 import java.awt.FontMetrics;\r
19 import java.awt.Graphics;\r
20 import java.awt.Image;\r
21 \r
22 import java.awt.event.MouseMotionListener;\r
23 import java.awt.event.MouseListener;\r
24 import java.awt.event.MouseEvent;\r
25 \r
26 import com.ibm.richtext.textlayout.attributes.AttributeMap;\r
27 import com.ibm.richtext.textlayout.attributes.TextAttribute;\r
28 \r
29 import com.ibm.richtext.styledtext.MTabRuler;\r
30 import com.ibm.richtext.styledtext.StandardTabRuler;\r
31 import com.ibm.richtext.styledtext.TabStop;\r
32 import com.ibm.richtext.styledtext.StyleModifier;\r
33 \r
34 import com.ibm.richtext.textpanel.TextPanelListener;\r
35 import com.ibm.richtext.textpanel.MTextPanel;\r
36 import com.ibm.richtext.textpanel.TextPanel;\r
37 import com.ibm.richtext.textpanel.TextPanelEvent;\r
38 \r
39 /**\r
40  * TabRuler is a Component which presents a user interface for\r
41  * setting the leading margin, trailing margin, first line indent,\r
42  * and tab types and positions.\r
43  * <p>\r
44  * TabRuler does not implement TextPanelListener directly;  however,\r
45  * it can receive updates from a MTextPanel.  To have a TabRuler listen\r
46  * to a panel, call <code>listenToPanel</code>.  TabRuler responds to\r
47  * user manipulation by modifying the paragraph styles on its MTextPanel\r
48  * (if any).\r
49  */\r
50 public final class TabRulerImpl implements MouseListener, MouseMotionListener\r
51 {\r
52     static final String COPYRIGHT =\r
53                 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";\r
54     private static final class TabStopBuffer {\r
55         public int fPosition;\r
56         public byte fType;\r
57 \r
58         TabStopBuffer(int position, byte type) {\r
59             fPosition = position;\r
60             fType = type;\r
61         }\r
62 \r
63         TabStopBuffer(TabStop tab) {\r
64             this(tab.getPosition(), tab.getType());\r
65         }\r
66 \r
67         TabStop getTabStop() {\r
68             return new TabStop(fPosition, fType);\r
69         }\r
70     }\r
71 \r
72     private static final class TabRulerModifier extends StyleModifier {\r
73 \r
74         private TabStop fOldTab; // tab to remove\r
75         private TabStop fNewTab; // tab to add\r
76         private AttributeMap fPanelDefaults;\r
77         \r
78         TabRulerModifier(TabStop oldTab,\r
79                          TabStop newTab,\r
80                          AttributeMap panelDefaults) {\r
81             fOldTab = oldTab;\r
82             fNewTab = newTab;\r
83             fPanelDefaults = panelDefaults;\r
84         }\r
85 \r
86         public AttributeMap modifyStyle(AttributeMap oldStyle) {\r
87 \r
88             MTabRuler oldRuler = (MTabRuler) getWithDefault(TextAttribute.TAB_RULER, \r
89                                                             oldStyle, \r
90                                                             fPanelDefaults);\r
91             MTabRuler ruler = oldRuler;\r
92 \r
93             if (fOldTab != null) {\r
94                 if (ruler.containsTab(fOldTab)) {\r
95                     ruler = ruler.removeTab(fOldTab.getPosition());\r
96                 }\r
97             }\r
98             if (fNewTab != null) {\r
99                 ruler = ruler.addTab(fNewTab);\r
100             }\r
101 \r
102             if (ruler != oldRuler) {\r
103                 return oldStyle.addAttribute(TextAttribute.TAB_RULER, ruler);\r
104             }\r
105             else {\r
106                 return oldStyle;\r
107             }\r
108         }\r
109     }\r
110 \r
111     private static final class ImageCache {\r
112 \r
113         static final String COPYRIGHT =\r
114                     "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";\r
115         private Image fImage = null;\r
116         private boolean fIsValid = false;\r
117         private Component fComponent;   // workaround for compiler bug, \r
118                                         // should just be able to say Component.this\r
119                                         // if this were not a static class\r
120         \r
121         ImageCache(Component component) {\r
122         \r
123             fComponent = component;\r
124         }\r
125 \r
126         Graphics getGraphics(int width, int height) {\r
127 \r
128             if (width <= 0 || height <= 0) {\r
129                 return null;\r
130             }\r
131             \r
132             Image image = fImage;\r
133             if (image == null || image.getWidth(fComponent) < width\r
134                               || image.getHeight(fComponent) < height) {\r
135         if (!fComponent.isVisible()) { // fix race condition if component not fully initialized\r
136             return null;\r
137         }\r
138                 image = fComponent.createImage(width, height);\r
139             }\r
140             Graphics g = image.getGraphics();\r
141             fImage = image;\r
142             return g;\r
143         }\r
144 \r
145         void drawImage(Graphics g, int x, int y, Color color) {\r
146 \r
147             if (!fIsValid) {\r
148                 throw new Error("Drawing image when not valid");\r
149             }\r
150             g.drawImage(fImage, x, y, color, fComponent);\r
151         }\r
152 \r
153         boolean isValid() {\r
154 \r
155             return fIsValid;\r
156         }\r
157 \r
158         void setValid(boolean isValid) {\r
159 \r
160             fIsValid = isValid;\r
161         }\r
162     }\r
163 \r
164     /**\r
165     * This class listens to a MTextPanel for changes which\r
166     * affect a TabRuler's appearance, and updates the TabRuler\r
167     * as necessary.\r
168     * @see TabRuler\r
169     * @see com.ibm.richtext.textpanel.MTextPanel\r
170     */\r
171     private static final class Updater implements TextPanelListener {\r
172 \r
173         static final String COPYRIGHT =\r
174                     "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";\r
175         private TabRulerImpl fTabRuler;\r
176         private MTextPanel fTextPanel;\r
177 \r
178         /**\r
179         * Create a new TabRulerUpdater.\r
180         * @param tabRuler the TabRuler to update when a change occurs\r
181         *     in the MTextPanel\r
182         */\r
183         Updater(TabRulerImpl tabRuler) {\r
184 \r
185             fTabRuler = tabRuler;\r
186         }\r
187 \r
188         /**\r
189         * Remove self as listener from previous MTextPanel,\r
190         * set current MTextPanel and listen to it (if not null).\r
191         */\r
192         void setTextPanel(MTextPanel textPanel) {\r
193 \r
194             if (fTextPanel != null) {\r
195                 fTextPanel.removeListener(this);\r
196             }\r
197 \r
198             fTextPanel = textPanel;\r
199 \r
200             if (fTextPanel != null) {\r
201                 fTextPanel.addListener(this);\r
202                 setAll();\r
203             }\r
204         }\r
205 \r
206         private void setAll() {\r
207 \r
208             int offset = fTextPanel.getSelectionStart();\r
209             boolean leftToRight = fTextPanel.paragraphIsLeftToRight(offset);\r
210             AttributeMap style = fTextPanel.getText().paragraphStyleAt(offset);\r
211             fTabRuler.set(style, false);\r
212             fTabRuler.setFormatWidth(fTextPanel.getFormatWidth(), false);\r
213             fTabRuler.setLeftToRight(leftToRight, true);\r
214         }\r
215 \r
216         /**\r
217         * TextPanelListener method.  This class responds to text\r
218         * changes by updating its TabRuler.\r
219         */\r
220         public void textEventOccurred(TextPanelEvent event) {\r
221 \r
222             int changeCode = event.getID();\r
223 \r
224             if (changeCode == TextPanelEvent.SELECTION_STYLES_CHANGED ||\r
225                     changeCode == TextPanelEvent.TEXT_CHANGED) {\r
226 \r
227                 int offset = fTextPanel.getSelectionStart();\r
228                 AttributeMap style = fTextPanel.getText().paragraphStyleAt(offset);\r
229                 boolean leftToRight = fTextPanel.paragraphIsLeftToRight(offset);\r
230                 fTabRuler.set(style, false);\r
231                 fTabRuler.setLeftToRight(leftToRight, true);\r
232             }\r
233             else if (changeCode == TextPanelEvent.FORMAT_WIDTH_CHANGED) {\r
234                 \r
235                 fTabRuler.setFormatWidth(fTextPanel.getFormatWidth(), true);\r
236             }\r
237         }\r
238         \r
239         /**\r
240         * TextPanelListener method.\r
241         */\r
242         public boolean respondsToEventType(int type) {\r
243             \r
244             return type == TextPanelEvent.SELECTION_STYLES_CHANGED ||\r
245                     type == TextPanelEvent.TEXT_CHANGED ||\r
246                     type == TextPanelEvent.FORMAT_WIDTH_CHANGED;\r
247         }\r
248     }\r
249 \r
250     /**\r
251      * The default background color for TabRulers.\r
252      * @see #setBackColor\r
253      */\r
254     public static final Color DEFAULT_BACK_COLOR = Color.lightGray;\r
255 \r
256     private static final int kTrackNone = 0;\r
257     private static final int kTrackTab = 1;\r
258     private static final int kTrackLM = 2;\r
259     private static final int kTrackFLI = 3;\r
260     private static final int kTrackTM = 4;\r
261 \r
262     private Component fHost;\r
263     private MTabRuler fRuler;\r
264     private int fLeadingMargin;\r
265     private int fFirstLineIndent;\r
266     private int fFormatWidth;\r
267     private int fTrailingMarginPosition; // opposite of actual trailing margin\r
268     private boolean fLeftToRight;\r
269     private int fBaseline;\r
270     private int fOrigin;\r
271     private Color fBackColor = DEFAULT_BACK_COLOR;\r
272 \r
273     private int fTrackItem; // 0 - none, 1 - tab, 2 - lm, 3 - fli, 4 - tm\r
274     private TabStopBuffer fTrackTab;\r
275     private TabStop fOldTab;\r
276     private int fTrackDelta;\r
277     private boolean fTrackVisible;\r
278     private Updater fUpdater;\r
279     private MTextPanel fTextPanel = null;\r
280 \r
281     private ImageCache fImageCache;\r
282 \r
283     /**\r
284      * Create a new TabRuler.\r
285      * @param baseline the y-coordinate of the ruler's baseline\r
286      * @param origin the x-coordinate in this Component where\r
287      *     the left margin appears\r
288      * @param textPanel the MTextPanel to listen to.  This TabRuler\r
289      *     will reflect the MTextPanel's paragraph styles, and update\r
290      *     the paragraph styles when manipulated.\r
291      */\r
292     public TabRulerImpl(int baseline, \r
293                         int origin, \r
294                         MTextPanel textPanel,\r
295                         Component host) {\r
296 \r
297         fHost = host;\r
298         fImageCache = new ImageCache(host);\r
299         fUpdater = new Updater(this);\r
300         fBaseline = baseline;\r
301         fOrigin = origin;\r
302         host.addMouseListener(this);\r
303         host.addMouseMotionListener(this);\r
304         if (textPanel != null) {\r
305             listenToTextPanel(textPanel);\r
306         }\r
307         else {\r
308             fRuler = new StandardTabRuler();\r
309         }\r
310     }\r
311 \r
312     /**\r
313      * Listen to the given MTextPanel and reflect its changes,\r
314      * and update its paragraph styles when TabRuler is\r
315      * manipulated.\r
316      * @param textPanel the MTextPanel to listen to\r
317      */\r
318     public void listenToTextPanel(MTextPanel textPanel) {\r
319 \r
320         fTextPanel = textPanel;\r
321         fUpdater.setTextPanel(textPanel);\r
322     }\r
323 \r
324     /**\r
325      * Return the background color of this TabRuler.\r
326      * @return the background color of this TabRuler\r
327      */\r
328     public Color getBackColor() {\r
329 \r
330         return fBackColor;\r
331     }\r
332 \r
333     /**\r
334      * Set the background color of this TabRuler.\r
335      * @param backColor the new background color of this TabRuler\r
336      */\r
337     public void setBackColor(Color backColor) {\r
338 \r
339         if (!backColor.equals(fBackColor)) {\r
340             fBackColor = backColor;\r
341             Graphics g = fHost.getGraphics();\r
342             if (g != null) {\r
343                 paint(g);\r
344             }\r
345         }\r
346     }\r
347 \r
348     private static Object getWithDefault(Object key,\r
349                                          AttributeMap style,\r
350                                          AttributeMap defaults) {\r
351         Object value = style.get(key);\r
352         if (value == null) {\r
353             value = defaults.get(key);\r
354         }\r
355         return value;\r
356     }\r
357 \r
358     private static float getFloatWithDefault(Object key,\r
359                                              AttributeMap style,\r
360                                              AttributeMap defaults) {\r
361         Object value = getWithDefault(key, style, defaults);\r
362         return ((Float)value).floatValue();\r
363     }\r
364     \r
365     private void setLeftToRight(boolean leftToRight, boolean update) {\r
366         \r
367         if (fLeftToRight != leftToRight) {\r
368             \r
369             fLeftToRight = leftToRight;\r
370             redrawSelf(update);\r
371         }\r
372     }\r
373 \r
374     private void setFormatWidth(int formatWidth, boolean update) {\r
375         \r
376         if (fFormatWidth != formatWidth) {\r
377             \r
378             fTrailingMarginPosition += (formatWidth - fFormatWidth);\r
379             fFormatWidth = formatWidth;\r
380             redrawSelf(update);\r
381         }\r
382     }\r
383     \r
384     /**\r
385      * Set TabRuler from values in paragraphStyle.  Only TabRulerUpdater\r
386      * should call this method.\r
387      * @param paragraphStyle the paragraph style which the TabRuler will\r
388      *     reflect\r
389      */\r
390     private void set(AttributeMap paragraphStyle, boolean update) {\r
391 \r
392         AttributeMap panelDefaults;\r
393         \r
394         if (fTextPanel==null) {\r
395             panelDefaults = TextPanel.getDefaultSettings().getDefaultValues();\r
396         }\r
397         else {\r
398              panelDefaults = fTextPanel.getDefaultValues();\r
399         }\r
400                                                          \r
401         int leadingMargin =  (int) getFloatWithDefault(TextAttribute.LEADING_MARGIN,\r
402                                                        paragraphStyle,\r
403                                                        panelDefaults);\r
404         int firstLineIndent = (int) getFloatWithDefault(TextAttribute.FIRST_LINE_INDENT,\r
405                                                         paragraphStyle,\r
406                                                         panelDefaults);\r
407         int trailingMargin = (int) getFloatWithDefault(TextAttribute.TRAILING_MARGIN,\r
408                                                        paragraphStyle,\r
409                                                        panelDefaults);\r
410 \r
411         MTabRuler ruler = (MTabRuler) getWithDefault(TextAttribute.TAB_RULER,\r
412                                                      paragraphStyle,\r
413                                                      panelDefaults);\r
414 \r
415         int ourFli = leadingMargin + firstLineIndent;\r
416         int ourTmp = fFormatWidth - trailingMargin;\r
417         \r
418         if (leadingMargin == fLeadingMargin &&\r
419                 fFirstLineIndent == ourFli &&\r
420                 fTrailingMarginPosition == ourTmp &&\r
421                 ruler.equals(fRuler)) {\r
422             return;\r
423         }\r
424         \r
425         fLeadingMargin = leadingMargin;\r
426         fFirstLineIndent = ourFli;\r
427         fTrailingMarginPosition = ourTmp;\r
428         fRuler = ruler;\r
429     \r
430         redrawSelf(update);\r
431     }\r
432     \r
433     private void redrawSelf(boolean drawNow) {\r
434 \r
435         fImageCache.setValid(false);\r
436 \r
437         Graphics g = fHost.getGraphics();\r
438         if (g != null)\r
439             paint(g);\r
440     }\r
441 \r
442     /**\r
443      * Return debugging info.\r
444      */\r
445     public String toString() {\r
446 \r
447         return "TabRuler{fLeadingMargin="+fLeadingMargin+\r
448                 "}{fFirstLineIndent="+fFirstLineIndent+\r
449                 "}{fFormatWidth="+fFormatWidth+\r
450                 "}{fTrailingMarginPosition="+fTrailingMarginPosition+\r
451                 "}{fRuler="+fRuler+\r
452                 "}";\r
453     }\r
454 \r
455     /**\r
456      * Return the MTabRuler represented by this TabRuler.\r
457      * @return the MTabRuler represented by this TabRuler\r
458      */\r
459     public MTabRuler getRuler()\r
460     {\r
461         return fRuler;\r
462     }\r
463 \r
464     /**\r
465      * Return the leading margin of this TabRuler.\r
466      * @return the leading margin of this TabRuler\r
467      */\r
468     public int getLeadingMargin()\r
469     {\r
470         return fLeadingMargin;\r
471     }\r
472 \r
473     /**\r
474      * Return the first line indent of this TabRuler.\r
475      * @return the first line indent of this TabRuler\r
476      */\r
477     public int getFirstLineIndent()\r
478     {\r
479         return fFirstLineIndent - fLeadingMargin;\r
480     }\r
481 \r
482     /**\r
483      * Return the trailing margin of this TabRuler.\r
484      * @return the trailing margin of this TabRuler\r
485      */\r
486     public final int getTrailingMargin()\r
487     {\r
488         return fFormatWidth - fTrailingMarginPosition;\r
489     }\r
490     \r
491     private int visualToRulerPos(int visPos) {\r
492         \r
493         if (fLeftToRight) {\r
494             return visPos - fOrigin;\r
495         }\r
496         else {\r
497             return fOrigin + fFormatWidth - visPos;\r
498         }\r
499     }\r
500     \r
501     private int rulerToVisualPos(int rulerPos) {\r
502         \r
503         if (fLeftToRight) {\r
504             return fOrigin + rulerPos;\r
505         }\r
506         else {\r
507             return fOrigin + fFormatWidth - rulerPos;\r
508         }\r
509     }\r
510     \r
511     private int dirMult() {\r
512         \r
513         return fLeftToRight? 1 : -1;\r
514     }\r
515 \r
516     /**\r
517      * @param tabPosition the logical (ruler) position of the tab\r
518      */\r
519     private void drawTab(Graphics g, int tabPosition, byte tabType, int tabTop, int tabBottom)\r
520     {\r
521         int pos = rulerToVisualPos(tabPosition);\r
522         int wid = 0;\r
523         switch (tabType) {\r
524             case TabStop.kLeading: wid = 3; break;\r
525             case TabStop.kCenter: wid = 0; break;\r
526             case TabStop.kTrailing: wid = -3; break;\r
527             case TabStop.kDecimal: wid = 0; break;\r
528             default: break;\r
529         }\r
530         wid *= dirMult();\r
531         \r
532         if (tabType != TabStop.kAuto) {\r
533             g.drawLine(pos, tabTop, pos, tabBottom);\r
534             if (wid != 0)\r
535                 g.drawLine(pos, tabBottom, pos + wid, tabBottom);\r
536         }\r
537         g.drawLine(pos-2, tabTop+2, pos, tabTop);\r
538         g.drawLine(pos, tabTop, pos+2, tabTop+2);\r
539         if (tabType == TabStop.kDecimal) {\r
540             g.drawLine(pos + 3, tabBottom, pos + 4, tabBottom);\r
541         }\r
542     }\r
543 \r
544     private void drawLM(Graphics g)\r
545     {\r
546         int pos = rulerToVisualPos(fLeadingMargin);\r
547         int[] xpts = { pos, pos, pos + (4*dirMult()), pos };\r
548         int[] ypts = { fBaseline + 12, fBaseline + 7, fBaseline + 7, fBaseline + 12 };\r
549         g.fillPolygon(xpts, ypts, 3);\r
550         g.drawPolygon(xpts, ypts, 4);\r
551     }\r
552 \r
553     private void drawFLI(Graphics g)\r
554     {\r
555         int pos = rulerToVisualPos(fFirstLineIndent);\r
556         int[] xpts = { pos, pos, pos + (4*dirMult()), pos };\r
557         int[] ypts = { fBaseline, fBaseline + 5, fBaseline + 5, fBaseline };\r
558         g.fillPolygon(xpts, ypts, 3);\r
559         g.drawPolygon(xpts, ypts, 4);\r
560     }\r
561 \r
562     private void drawRM(Graphics g)\r
563     {\r
564         int pos = rulerToVisualPos(fTrailingMarginPosition);\r
565         int[] xpts = { pos, pos, pos - (6*dirMult()), pos };\r
566         int[] ypts = { fBaseline, fBaseline + 12, fBaseline + 6, fBaseline };\r
567         g.fillPolygon(xpts, ypts, 3);\r
568         g.drawPolygon(xpts, ypts, 4);\r
569     }\r
570 \r
571     private static int alignInt(int value) {\r
572         \r
573         return (int)((int)(value / 4.5) * 4.5);\r
574     }\r
575 \r
576     private static final int[] fgLengths = { 10, 2, 4, 2, 6, 2, 4, 2 };\r
577 \r
578     /**\r
579      * Component method override.\r
580      */\r
581     public void paint(Graphics g)\r
582     {\r
583         Dimension size = fHost.getSize();\r
584 \r
585         int width = size.width;\r
586         int baseline = fBaseline;\r
587         int baseline2 = baseline + 2;\r
588         int baseline10 = baseline + 10;\r
589         int baseline12 = baseline + 12;\r
590 \r
591         if (!fImageCache.isValid()) {\r
592 \r
593             Graphics gCache = fImageCache.getGraphics(width, baseline12 + 1);\r
594             if (gCache == null) {\r
595                 return;\r
596             }\r
597             \r
598             // set background color\r
599 \r
600             gCache.setColor(fBackColor);\r
601             gCache.setPaintMode();\r
602             gCache.fillRect(0, 0, width, baseline12 + 1);\r
603 \r
604             // paint ticks\r
605 \r
606             gCache.setColor(Color.black);\r
607             gCache.drawLine(0, 0, width, 0);\r
608             gCache.drawLine(0, baseline, width, baseline);\r
609 \r
610             int[] lengths = fgLengths;\r
611 \r
612             int index = 0;\r
613             int inchnum = 0;\r
614             FontMetrics fm = null;\r
615             if (!fLeftToRight) {\r
616                 fm = gCache.getFontMetrics();\r
617             }\r
618             \r
619             for (int i = 0; i < fFormatWidth; i += 9) {\r
620                 int len = lengths[index];\r
621                 int pos = rulerToVisualPos(i);\r
622                 gCache.drawLine(pos, baseline, pos, baseline - len);\r
623 \r
624                 if (index == 0) {\r
625                     String str = Integer.toString(inchnum++);\r
626                     int drawX;\r
627                     if (fLeftToRight) {\r
628                         drawX = pos + 2;\r
629                     }\r
630                     else {\r
631                         drawX = pos - fm.stringWidth(str) - 2;\r
632                     }\r
633                         \r
634                     gCache.drawString(str, drawX, baseline - 2);\r
635                 }\r
636 \r
637                 if (++index == lengths.length)\r
638                     index = 0;\r
639             }\r
640 \r
641             // paint tabs\r
642             TabStop tab = fRuler.firstTab();\r
643             while (tab != null && tab.getPosition() < fTrailingMarginPosition) {\r
644                 boolean dodraw = true;\r
645                 if (tab.getType() == TabStop.kAuto) {\r
646                     if (tab.getPosition() <= Math.max(fLeadingMargin, fFirstLineIndent))\r
647                         dodraw = false;\r
648                     else if (tab.getPosition() >= fTrailingMarginPosition)\r
649                         dodraw = false;\r
650                 }\r
651 \r
652                 if (dodraw)\r
653                     drawTab(gCache, tab.getPosition(), tab.getType(), baseline2, baseline10);\r
654 \r
655                 tab = fRuler.nextTab(tab.getPosition());\r
656             }\r
657 \r
658             gCache.drawLine(0, baseline12, width, baseline12);\r
659 \r
660             // paint others except for tracked item\r
661             if (fTrackItem != kTrackLM) drawLM(gCache);\r
662             if (fTrackItem != kTrackTM) drawRM(gCache);\r
663             if (fTrackItem != kTrackFLI && fTrackItem != kTrackLM) drawFLI(gCache);\r
664             fImageCache.setValid(true);\r
665         }\r
666 \r
667         fImageCache.drawImage(g, 0, 0, Color.lightGray);\r
668 \r
669         switch (fTrackItem) {\r
670             case kTrackTab: if (fTrackVisible) drawTab(g, fTrackTab.fPosition, fTrackTab.fType, baseline2, baseline10); break;\r
671             case kTrackLM: drawLM(g); drawFLI(g); break;\r
672             case kTrackTM: drawRM(g); break;\r
673             case kTrackFLI: drawFLI(g); break;\r
674             default: break;\r
675         }\r
676     }\r
677 \r
678     /**\r
679      * MouseListener method.\r
680      */\r
681     public void mouseClicked(MouseEvent e) {}\r
682     /**\r
683      * MouseListener method.\r
684      */\r
685     public void mouseEntered(MouseEvent e) {}\r
686     /**\r
687      * MouseListener method.\r
688      */\r
689     public void mouseExited(MouseEvent e) {}\r
690 \r
691     /**\r
692      * MouseListener method.\r
693      */\r
694     public void mousePressed(MouseEvent e)\r
695     {\r
696         // find out if we hit a tabstop\r
697         int x = visualToRulerPos(e.getX());\r
698         int y = e.getY();\r
699 \r
700         if (y > fBaseline && y < fBaseline + 12) {\r
701             if (y >= fBaseline + 7 && x >= fLeadingMargin - 3 && x <= fLeadingMargin + 3) {\r
702                 fTrackItem = kTrackLM;\r
703                 fTrackDelta = fLeadingMargin - x;\r
704             } else if (y < fBaseline + 7 && x >= fFirstLineIndent - 3 && x <= fFirstLineIndent + 3) {\r
705                 fTrackItem = kTrackFLI;\r
706                 fTrackDelta = fFirstLineIndent - x;\r
707             } else if (x >= fTrailingMarginPosition - 3 && x <= fTrailingMarginPosition + 3) {\r
708                 fTrackItem = kTrackTM;\r
709                 fTrackDelta = fTrailingMarginPosition - x;\r
710             } else if (e.isControlDown()) {\r
711                 fTrackItem = kTrackTab;\r
712                 fTrackTab = new TabStopBuffer(alignInt(x), TabStop.kLeading);\r
713                 fTrackDelta = fTrackTab.fPosition - x;\r
714                 fTrackVisible = true;\r
715             } else {\r
716                 TabStop tab = fRuler.firstTab();\r
717                 while (tab.getType() != TabStop.kAuto) {\r
718                     if (x < tab.getPosition() - 3)\r
719                         break;\r
720                     if (x < tab.getPosition() + 3) {\r
721                         fOldTab = tab;\r
722                         fTrackTab = new TabStopBuffer(tab);\r
723                         fRuler = fRuler.removeTab(fOldTab.getPosition());\r
724 \r
725                         if (e.getClickCount() > 1) {\r
726                             switch (fTrackTab.fType) {\r
727                                 case TabStop.kLeading: fTrackTab.fType = TabStop.kCenter; break;\r
728                                 case TabStop.kCenter: fTrackTab.fType = TabStop.kTrailing; break;\r
729                                 case TabStop.kTrailing: fTrackTab.fType = TabStop.kDecimal; break;\r
730                                 case TabStop.kDecimal: fTrackTab.fType = TabStop.kLeading; break;\r
731                                 default: break;\r
732                             }\r
733                         }\r
734                         fTrackItem = kTrackTab;\r
735                         fTrackDelta = tab.getPosition() - x;\r
736                         fTrackVisible = true;\r
737                         break;\r
738                     }\r
739 \r
740                     tab = fRuler.nextTab(tab.getPosition());\r
741                 }\r
742             }\r
743 \r
744             if (fTrackItem != kTrackNone) {\r
745                 fImageCache.setValid(false);\r
746                 paint(fHost.getGraphics());\r
747                 return;\r
748             }\r
749         }\r
750     }\r
751 \r
752     /**\r
753      * MouseListener method.\r
754      */\r
755     public void mouseDragged(MouseEvent e)\r
756     {\r
757         int x = visualToRulerPos(e.getX());\r
758         int y = e.getY();\r
759 \r
760         if (fTrackItem != kTrackNone) {\r
761             boolean repaint = false;\r
762             boolean inrange = y > fBaseline && y < fBaseline + 12;\r
763             boolean inbigrange = y > 0 && y < fHost.getSize().height + 20;\r
764             int newpos = alignInt(x + fTrackDelta);\r
765             if (newpos < 0)\r
766                 newpos = 0;\r
767 \r
768             switch (fTrackItem) {\r
769                 case kTrackTab: {\r
770                     if (inrange) {\r
771                         repaint = !fTrackVisible;\r
772                         fTrackVisible = true;\r
773                         if (newpos != fTrackTab.fPosition) {\r
774                             fTrackTab.fPosition = newpos;\r
775                             repaint = true;\r
776                         }\r
777                     } else if (fTrackVisible) {\r
778                         fTrackVisible = false;\r
779                         repaint = true;\r
780                     }\r
781                 } break;\r
782 \r
783 /* It would be nice to optionally track the margin 'independently' of the first line indent.\r
784  Unfortunately this makes for more work when we have multiple paragraph styles selected.\r
785  Since internally the first line indent is relative to the margin, moving the margin\r
786  independently so that all affected paragraphs share the same margin but retain first\r
787  line indents in the 'same' positions means that I need to also adjust the first line\r
788  indents in each paragraph by some delta. I'm not ready to do that yet. */\r
789 \r
790                 case kTrackLM: {\r
791                     if (inbigrange && newpos != fLeadingMargin) {\r
792                         fFirstLineIndent += newpos - fLeadingMargin;\r
793                         fLeadingMargin = newpos;\r
794                         repaint = true;\r
795                     }\r
796                 } break;\r
797 \r
798                 case kTrackFLI: {\r
799                     if (inbigrange && newpos != fFirstLineIndent) {\r
800                         fFirstLineIndent = newpos;\r
801                         repaint = true;\r
802                     }\r
803                 } break;\r
804 \r
805                 case kTrackTM: {\r
806                     if (inbigrange && newpos != fTrailingMarginPosition) {\r
807                         fTrailingMarginPosition = newpos;\r
808                         repaint = true;\r
809                     }\r
810                 } break;\r
811             }\r
812 \r
813 \r
814             if (repaint)\r
815                 paint(fHost.getGraphics());\r
816         }\r
817     }\r
818 \r
819     /**\r
820      * MouseListener method.\r
821      */\r
822     public void mouseReleased(MouseEvent e)\r
823     {\r
824         if (fTrackItem != kTrackNone) {\r
825             if (fTrackItem == kTrackTab && fTrackVisible) {\r
826                 fRuler = fRuler.addTab(fTrackTab.getTabStop());\r
827             } else {\r
828                 fTrackTab = null;\r
829             }\r
830 \r
831             notify(fTrackItem);\r
832 \r
833             fTrackItem = kTrackNone;\r
834             fTrackTab = null;\r
835             fOldTab = null;\r
836 \r
837             fImageCache.setValid(false);\r
838             paint(fHost.getGraphics());\r
839         }\r
840     }\r
841 \r
842     /**\r
843      * MouseListener method.\r
844      */\r
845     public void mouseMoved(MouseEvent e) {}\r
846 \r
847     private void notify(int change)\r
848     {\r
849         if (fTextPanel != null) {\r
850 \r
851             StyleModifier modifier;\r
852 \r
853             if (change == kTrackTab) {\r
854                 TabStop newTab = fTrackTab==null? null : fTrackTab.getTabStop();\r
855                 modifier = new TabRulerModifier(fOldTab, newTab, fTextPanel.getDefaultValues());\r
856             }\r
857             else {\r
858                 Object key;\r
859                 Object value;\r
860 \r
861                 switch(change) {\r
862                     case kTrackLM:\r
863                         key = TextAttribute.LEADING_MARGIN;\r
864                         value = new Float(getLeadingMargin());\r
865                         break;\r
866 \r
867                     case kTrackTM:\r
868                         key = TextAttribute.TRAILING_MARGIN;\r
869                         value = new Float(getTrailingMargin());\r
870                         break;\r
871 \r
872                     case kTrackFLI:\r
873                         key = TextAttribute.FIRST_LINE_INDENT;\r
874                         value = new Float(getFirstLineIndent());\r
875                         break;\r
876 \r
877                     default:\r
878                         throw new Error("Invalid change code.");\r
879                 }\r
880 \r
881                 modifier = StyleModifier.createAddModifier(key, value);\r
882             }\r
883 \r
884             fTextPanel.modifyParagraphStyleOnSelection(modifier);\r
885         }\r
886     }\r
887 \r
888     /**\r
889      * Component override.\r
890      */\r
891     public Dimension getMinimumSize()\r
892     {\r
893         return new Dimension(100, fBaseline + 13);\r
894     }\r
895 \r
896     /**\r
897      * Component override.\r
898      */\r
899     public Dimension getPreferredSize()\r
900     {\r
901         return getMinimumSize();\r
902     }\r
903 }\r