1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
71 private TreeMapNode activeLeaf = null;
72
73 private ITreeMapColorProvider colorProvider = null;
74
75 private ITreeMapProvider treeMapProvider = null;
76
77 private TreeMapNode displayedRoot = null;
78
79 private TreeMapNode root = null;
80
81 private SplitStrategy strategy = null;
82
83 private Zoom zoom;
84
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
138
139
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
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
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
331 draw(gc, this.displayedRoot);
332
333 if (this.activeLeaf != null) {
334 reveal(gc, this.activeLeaf);
335 }
336
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
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
377
378 if (item.getWidth() - 5 <= stringWidth) {
379
380
381 int nbChar = (label.length() * item.getWidth()) / stringWidth;
382 if (nbChar > 3) {
383
384 label = label.substring(0, nbChar - 3) + "...";
385 stringWidth = (nbChar - 1) * fm.getAverageCharWidth();
386 } else {
387
388 return;
389 }
390 }
391 int x = (item.getWidth() - stringWidth) / 2;
392
393
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
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
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
581
582
583
584
585
586
587
588
589
590
591
592
593
594