View Javadoc

1   /*
2    * ObjectLab, http://www.objectlab.co.uk/open is supporting JTreeMap.
3    * 
4    * Based in London, we are world leaders in the design and development 
5    * of bespoke applications for the securities financing markets.
6    * 
7    * <a href="http://www.objectlab.co.uk/open">Click here to learn more</a>
8    *           ___  _     _           _   _          _
9    *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
10   *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
11   *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
12   *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
13   *                   |__/
14   *
15   *                     www.ObjectLab.co.uk
16   *
17   * $Id: KTreeMap.java 75 2006-10-24 23:00:51Z benoitx $
18   * 
19   * Copyright 2006 the original author or authors.
20   *
21   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
22   * use this file except in compliance with the License. You may obtain a copy of
23   * the License at
24   *
25   * http://www.apache.org/licenses/LICENSE-2.0
26   *
27   * Unless required by applicable law or agreed to in writing, software
28   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
29   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
30   * License for the specific language governing permissions and limitations under
31   * the License.
32   */
33  package net.sf.jtreemap.ktreemap;
34  
35  import org.eclipse.swt.SWT;
36  import org.eclipse.swt.graphics.Color;
37  import org.eclipse.swt.graphics.FontMetrics;
38  import org.eclipse.swt.graphics.GC;
39  import org.eclipse.swt.graphics.Point;
40  import org.eclipse.swt.graphics.RGB;
41  import org.eclipse.swt.graphics.Rectangle;
42  import org.eclipse.swt.widgets.Canvas;
43  import org.eclipse.swt.widgets.Composite;
44  import org.eclipse.swt.widgets.Display;
45  import org.eclipse.swt.events.MouseEvent;
46  import org.eclipse.swt.events.MouseMoveListener;
47  import org.eclipse.swt.events.PaintEvent;
48  import org.eclipse.swt.events.PaintListener;
49  
50  /**
51   * Widget who represents each element of a tree in a rectangle of more or
52   * less big size according to its importance in the tree.
53   * <p>
54   * A tree structure may includes more or less important elements. For example,
55   * in a tree structure of files, there can be files of big size. Then it can be
56   * interesting to know which repertory is the most important on a hard disk.
57   * <p>
58   * Moreover, we can add a code color which makes it possible to introduce new
59   * information into the representation of the tree structure.
60   * <p>
61   * So, in a KTreeMap, you can see the size and the value of an element in a
62   * tree.
63   *
64   * @see net.sf.jtreemap.ktreemap.TreeMapNode
65   * @author Laurent Dutheil
66   */
67  public class KTreeMap extends Canvas {
68    private static final long serialVersionUID = 7255952672238300249L;
69    private Color revealColor = null;
70    // active leaf
71    private TreeMapNode activeLeaf = null;
72    // color provider
73    private ITreeMapColorProvider colorProvider = null;
74    // treemap provider
75    private ITreeMapProvider treeMapProvider = null;
76    // displayed root
77    private TreeMapNode displayedRoot = null;
78    // root of the tree
79    private TreeMapNode root = null;
80    // divide strategy
81    private SplitStrategy strategy = null;
82    // zoom
83    private Zoom zoom;
84    //position of cursor
85    private Point cursorPosition = new Point(0,0);
86  
87    /**
88     * Constructor of JTreeMap. <BR>
89     * The chosen strategy is SplitSquarified. <BR>
90     * @param parent parent Composite
91     * @param style style
92     * @param root the root of the tree to display
93     *
94     * @see SplitSquarified
95     */
96    public KTreeMap(Composite parent, int style, TreeMapNode root) {
97      this(parent, style, root, new SplitSquarified());
98    }
99  
100   /**
101    * Constructor of JTreeMap. <BR>
102    * The chosen color provider is UniqueColorProvider.
103    * @param parent parent Composite
104    * @param style style
105    * @param root the root of the tree to display
106    * @param strategy the split strategy
107    */
108   public KTreeMap(Composite parent, int style, TreeMapNode root,
109       SplitStrategy strategy) {
110     super(parent, style);
111 
112     this.zoom = new Zoom();
113 
114     this.setRoot(root);
115     this.setStrategy(strategy);
116 
117     this.addPaintListener(new PaintListener() {
118       public void paintControl(PaintEvent e) {
119         KTreeMap.this.paintControl(e);
120       }
121     });
122 
123     this.addMouseMoveListener(new HandleMouseMotion());
124   }
125 
126   /**
127    * calculate the postitions for the displayed root. <BR>
128    * The positions of the root must be calculated first.
129    */
130   public void calculatePositions() {
131     if (this.getStrategy() != null && this.displayedRoot != null) {
132       this.getStrategy().calculatePositions(this.displayedRoot);
133     }
134   }
135 
136   /*
137    * (non-Javadoc)
138    *
139    * @see org.eclipse.swt.widgets.Widget#dispose()
140    */
141   @Override
142   public void dispose() {
143     if (revealColor != null)
144       revealColor.dispose();
145     super.dispose();
146   }
147 
148   /**
149    * get the active leaf (the one under the mouse).
150    *
151    * @return Returns the activeLeaf.
152    */
153   public TreeMapNode getActiveLeaf() {
154     return this.activeLeaf;
155   }
156 
157   /**
158    * @return the colorProvider
159    */
160   public ITreeMapColorProvider getColorProvider() {
161     return colorProvider;
162   }
163 
164   /**
165    * @return the cursorPosition
166    */
167   public Point getCursorPosition() {
168     return cursorPosition;
169   }
170 
171   /**
172    * get the displayed root.
173    * <p>
174    * This may be not the root of the jTreeMap. After a zoom, the displayed root
175    * can be the root of an under-tree.
176    * </p>
177    *
178    * @return the displayed root
179    */
180   public TreeMapNode getDisplayedRoot() {
181     return this.displayedRoot;
182   }
183 
184   /**
185    * get the root.
186    *
187    * @return the root
188    */
189   public TreeMapNode getRoot() {
190     return this.root;
191   }
192 
193   /**
194    * get the SplitStrategy.
195    *
196    * @return the SplitStrategy
197    */
198   public SplitStrategy getStrategy() {
199     return this.strategy;
200   }
201 
202   /**
203    * get the IColorLabelProvider.
204    *
205    * @return the IColorLabelProvider
206    */
207   public ITreeMapProvider getTreeMapProvider() {
208     return this.treeMapProvider;
209   }
210 
211   /**
212    * set the active leaf.
213    *
214    * @param newActiveLeaf the new active leaf
215    */
216   public void setActiveLeaf(TreeMapNode newActiveLeaf) {
217     if (newActiveLeaf == null || newActiveLeaf.isLeaf()) {
218       this.activeLeaf = newActiveLeaf;
219     }
220   }
221 
222   /**
223    * @param colorProvider the colorProvider to set
224    */
225   public void setColorProvider(ITreeMapColorProvider colorProvider) {
226     this.colorProvider = colorProvider;
227     redraw();
228   }
229 
230   /**
231    * set the displayed root.
232    * <p>
233    * This may be not the root of the jTreeMap. After a zoom, the displayed root
234    * can be the root of an under-tree.
235    * </p>
236    *
237    * @param newDisplayedRoot new DiplayedRoot
238    */
239   public void setDisplayedRoot(TreeMapNode newDisplayedRoot) {
240     this.displayedRoot = newDisplayedRoot;
241     redraw();
242   }
243 
244   /**
245    * set the new root.
246    *
247    * @param newRoot the new root to set
248    */
249   public void setRoot(TreeMapNode newRoot) {
250     this.root = newRoot;
251     int insets = getBorderWidth();
252     this.root.setX(insets);
253     this.root.setY(insets);
254     this.setDisplayedRoot(this.root);
255   }
256 
257   /**
258    * set the new strategy.
259    *
260    * @param newStrat the new strategy to set
261    */
262   public void setStrategy(SplitStrategy newStrat) {
263     this.strategy = newStrat;
264     redraw();
265   }
266 
267   /**
268    * set the ColorProvider.
269    *
270    * @param newColorProvider the new ColorPorvider
271    */
272   public void setTreeMapProvider(ITreeMapProvider newColorProvider) {
273     this.treeMapProvider = newColorProvider;
274     redraw();
275   }
276 
277   /**
278    * When you zoom the jTreeMap, you have the choice to keep proportions or not.
279    *
280    * @param keepProportion true if you want to keep proportions, else false
281    */
282   public void setZoomKeepProportion(boolean keepProportion) {
283     this.zoom.setKeepProportion(keepProportion);
284   }
285 
286   /**
287    * Undo the zoom to display the root.
288    */
289   public void unzoom() {
290     this.zoom.undo();
291   }
292 
293   /**
294    * Zoom the JTreeMap to the dest node.
295    *
296    * @param dest node we want to zoom
297    */
298   public void zoom(TreeMapNode dest) {
299     // undo the last zoom
300     unzoom();
301 
302     this.zoom.execute(dest);
303   }
304 
305   void paintControl(PaintEvent e) {
306     GC gc = e.gc;
307     int width = getBounds().width;
308     int height = getBounds().height;
309     int insets = getBorderWidth();
310 
311     int border = TreeMapNode.getBorder();
312     this.root.setBounds(new Rectangle(this.root.getX(), this.root.getY(), width
313         - border - insets - insets, height - border - insets - insets));
314 
315     if (!this.root.equals(this.displayedRoot)) {
316       this.displayedRoot.setBounds(new Rectangle(this.displayedRoot.getX(),
317           this.displayedRoot.getY(), width - border - insets - insets, height
318               - border - insets - insets));
319     }
320 
321     this.calculatePositions();
322 
323     if (!this.displayedRoot.getChildren().isEmpty()) {
324       // the background
325       gc.setBackground(this.getBackground());
326       gc.fillRectangle(this.displayedRoot.getX(), this.displayedRoot.getY(),
327           this.displayedRoot.getWidth() + border, this.displayedRoot
328               .getHeight()
329               + border);
330       // the JTreeMapExample
331       draw(gc, this.displayedRoot);
332       // reveal the active leaf
333       if (this.activeLeaf != null) {
334         reveal(gc, this.activeLeaf);
335       }
336       // the labels
337       drawLabels(gc, this.displayedRoot);
338     }
339   }
340 
341   /**
342    * draw the item.
343    *
344    * @param gc Graphics where you have to draw
345    * @param item item to draw
346    */
347   protected void draw(GC gc, TreeMapNode item) {
348     if (item.isLeaf()) {
349       gc.setBackground(getColorProvider().getBackground(item.getValue()));
350       gc.fillRectangle(item.getBounds());
351     } else {
352       for (TreeMapNode node : item.getChildren()) {
353         draw(gc, node);
354       }
355     }
356   }
357 
358   /**
359    * write the label in the middle of the item. <BR>
360    * You have first to define the font of the Graphics. <BR>
361    * You may override this method to change the position or the color of the
362    * label.
363    *
364    * @param gc Graphics where you have to draw
365    * @param item TreeMapNode to draw
366    */
367   protected void drawLabel(GC gc, TreeMapNode item) {
368     FontMetrics fm = gc.getFontMetrics();
369     // if the height of the item is high enough
370     if (fm.getHeight() < item.getHeight() - 2) {
371       String label = getTreeMapProvider().getLabel(item);
372 
373       int y = (item.getHeight() - fm.getAscent() - fm.getLeading() + fm
374           .getDescent()) / 2;
375       int stringWidth = fm.getAverageCharWidth() * label.length();
376       // the width of the label depends on the font :
377       // if the width of the label is larger than the item
378       if (item.getWidth() - 5 <= stringWidth) {
379         // We have to truncate the label
380         // number of chars who can be writen in the item
381         int nbChar = (label.length() * item.getWidth()) / stringWidth;
382         if (nbChar > 3) {
383           // and add "..." at the end
384           label = label.substring(0, nbChar - 3) + "...";
385           stringWidth = (nbChar - 1) * fm.getAverageCharWidth();
386         } else {
387           // if it is not enough large, we display nothing
388           return;
389         }
390       }
391       int x = (item.getWidth() - stringWidth) / 2;
392 
393       // background in black
394       gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
395       gc.drawString(label, item.getX() + x + 1, item.getY() + y + 1, true);
396       gc.drawString(label, item.getX() + x - 1, item.getY() + y + 1, true);
397       gc.drawString(label, item.getX() + x + 1, item.getY() + y - 1, true);
398       gc.drawString(label, item.getX() + x - 1, item.getY() + y - 1, true);
399       gc.drawString(label, item.getX() + x + 1, item.getY() + y, true);
400       gc.drawString(label, item.getX() + x - 1, item.getY() + y, true);
401       gc.drawString(label, item.getX() + x, item.getY() + y + 1, true);
402       gc.drawString(label, item.getX() + x, item.getY() + y - 1, true);
403       // label in foreground color
404       gc.setForeground(getColorProvider().getForeground(item));
405       gc.drawString(label, item.getX() + x, item.getY() + y, true);
406     }
407   }
408 
409   /**
410    * Draw all the labels to draw. <BR>
411    * You may override this method to draw the labels you want. <BR>
412    * For exemples, all the leaves, or all the first level children, or all of
413    * them...
414    *
415    * @param gc Graphics where you have to draw
416    * @param item TreeMapNode to draw
417    */
418   protected void drawLabels(GC gc, TreeMapNode item) {
419     // add the labels (level -1)
420     gc.setFont(this.getFont());
421     if (this.displayedRoot.isLeaf()) {
422       drawLabel(gc, this.displayedRoot);
423     } else {
424       for (TreeMapNode node : this.displayedRoot.getChildren()) {
425         drawLabel(gc, node);
426       }
427     }
428   }
429 
430   /**
431    * reveal the item.
432    *
433    * @param gc Graphics where you have to draw
434    * @param item TreeMapNode to reveal
435    */
436   protected void reveal(GC gc, TreeMapNode item) {
437     if (item.isLeaf()) {
438       if (revealColor != null)
439         revealColor.dispose();
440 
441       Color itemColor = this.colorProvider.getBackground(item.getValue());
442       RGB rgb = itemColor.getRGB();
443       float[] fs = java.awt.Color.RGBtoHSB(rgb.red, rgb.green, rgb.blue, null);
444       java.awt.Color cc = new java.awt.Color(java.awt.Color.HSBtoRGB(fs[0],
445           fs[1] / 2, (fs[2] + 1) / 2));
446       revealColor = new Color(getDisplay(), cc.getRed(), cc.getGreen(), cc
447           .getBlue());
448 
449       gc.setBackground(revealColor);
450       gc.fillRectangle(item.getX(), item.getY(), item.getWidth(), item
451           .getHeight());
452     }
453   }
454 
455   /**
456    * Listener who define the active leaf and set the tooltip text.
457    *
458    * @author Laurent Dutheil
459    */
460   protected class HandleMouseMotion implements MouseMoveListener {
461     public void mouseMove(MouseEvent e) {
462       cursorPosition.x = e.x;
463       cursorPosition.y = e.y;
464       if (!getDisplayedRoot().getChildren().isEmpty()) {
465         TreeMapNode t = getDisplayedRoot().getActiveLeaf(e.x, e.y);
466         TreeMapNode oldActiveLeaf = getActiveLeaf();
467         if (oldActiveLeaf != null && !oldActiveLeaf.equals(t)) {
468           Rectangle bounds = oldActiveLeaf.getBounds();
469           redraw(bounds.x, bounds.y, bounds.width, bounds.height, false);
470         }
471         setActiveLeaf(t);
472         if (t != null && !t.equals(oldActiveLeaf)) {
473           Rectangle bounds = t.getBounds();
474           redraw(bounds.x, bounds.y, bounds.width, bounds.height, false);
475 
476         }
477         if (t != null) {
478           String label = KTreeMap.this.getTreeMapProvider().getLabel(t);
479           String valueLabel = KTreeMap.this.getTreeMapProvider().getValueLabel(
480               t.getValue());
481           setToolTipText(label + "\n" + valueLabel);
482         } else {
483           setToolTipText(null);
484         }
485       }
486     }
487   }
488 
489   /**
490    * Class who zoom and unzoom the JTreeMap.
491    *
492    * @author Laurent Dutheil
493    */
494   private class Zoom {
495     private boolean enable;
496     private boolean keepProportion = false;
497 
498     /**
499      * Constructor
500      */
501     public Zoom() {
502       this.enable = true;
503     }
504 
505     /**
506      * Execute the zoom.
507      *
508      * @param dest TreeMapNode where you want to zoom
509      */
510     public void execute(TreeMapNode dest) {
511       if (this.enable) {
512         KTreeMap.this.setActiveLeaf(null);
513 
514         setNewDimension(dest);
515 
516         KTreeMap.this.setDisplayedRoot(dest);
517 
518         this.enable = false;
519       }
520     }
521 
522     /**
523      * @return Returns the keepProportion.
524      */
525     public boolean isKeepProportion() {
526       return this.keepProportion;
527     }
528 
529     /**
530      * @param keepProportion The keepProportion to set.
531      */
532     public void setKeepProportion(boolean keepProportion) {
533       this.keepProportion = keepProportion;
534     }
535 
536     /**
537      * undo the zoom.
538      */
539     public void undo() {
540       if (!this.enable) {
541         KTreeMap.this.setDisplayedRoot(KTreeMap.this.getRoot());
542         this.enable = true;
543       }
544     }
545 
546     /**
547      * set the new dimensions of the dest root
548      *
549      * @param dest the root to dimension
550      */
551     protected void setNewDimension(TreeMapNode dest) {
552       dest.setX(KTreeMap.this.getRoot().getX());
553       dest.setY(KTreeMap.this.getRoot().getY());
554 
555       int rootWidth = KTreeMap.this.getRoot().getWidth();
556       int rootHeight = KTreeMap.this.getRoot().getHeight();
557 
558       if (isKeepProportion()) {
559         int destHeight = dest.getHeight();
560         int destWidth = dest.getWidth();
561         float divWidth = (float)destWidth / (float)rootWidth;
562         float divHeight = (float)destHeight / (float)rootHeight;
563 
564         if (divWidth >= divHeight) {
565           dest.setHeight(Math.round(destHeight / divWidth));
566           dest.setWidth(rootWidth);
567         } else {
568           dest.setHeight(rootHeight);
569           dest.setWidth(Math.round(destWidth / divHeight));
570         }
571 
572       } else {
573         dest.setHeight(rootHeight);
574         dest.setWidth(rootWidth);
575       }
576     }
577   }
578 }
579 /*
580  *                 ObjectLab is supporing JTreeMap
581  * 
582  * Based in London, we are world leaders in the design and development 
583  * of bespoke applications for the securities financing markets.
584  * 
585  * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a>
586  *           ___  _     _           _   _          _
587  *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
588  *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
589  *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
590  *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
591  *                   |__/
592  *
593  *                     www.ObjectLab.co.uk
594  */