changeset 1726:df0b23875ffb

Backport: Improve treemap zoom out review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-August/015378.html reviewed-by: jerboaa PR2586
author Mario Torre <neugens.limasoftware@gmail.com>
date Thu, 20 Aug 2015 14:12:33 +0200
parents 5bcf61ecacab
children a15b633b7335
files vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapIconResources.java vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_body.png vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_head.png vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_tail.png vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverter.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapBreadcrumb.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponent.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNode.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapObserver.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapPanel.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapToolbar.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapZoomBar.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverterTest.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponentTest.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNodeTest.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapToolbarTest.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapZoomBarTest.java
diffstat 19 files changed, 1229 insertions(+), 142 deletions(-) [+]
line wrap: on
line diff
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapIconResources.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapIconResources.java	Thu Aug 20 14:12:33 2015 +0200
@@ -49,6 +49,10 @@
     public static final String PIN_MASK = "com/redhat/thermostat/vm/heap/analysis/client/core/pin_mask.png";
     public static final String TRIGGER_HEAP_DUMP = "com/redhat/thermostat/vm/heap/analysis/client/core/take_dump.png";
 
+    public static final String BREADCRUMB_HEAD = "com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_head.png";
+    public static final String BREADCRUMB_BODY = "com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_body.png";
+    public static final String BREADCRUMB_TAIL = "com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_tail.png";
+
     private static Map<String, IconDescriptor> icons = new HashMap<>();
     
     public synchronized static IconDescriptor getIcon(String path) {
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java	Thu Aug 20 14:12:33 2015 +0200
@@ -80,6 +80,9 @@
     LIST_DUMPS_ACTION, 
     HEAP_DUMP_SECTION_TREEMAP,
 
+    ZOOM_IN,
+    ZOOM_OUT,
+    ZOOM_FULL,
     ;
 
     static final String RESOURCE_BUNDLE = "com.redhat.thermostat.vm.heap.analysis.client.locale.strings";
--- a/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties	Thu Aug 20 14:12:33 2015 +0200
@@ -36,4 +36,8 @@
 DUMPS_LIST = Available Dumps
 HEAP_DUMP_IN_PROGRESS = dumping...
 HEAP_DUMP_LOADING_IN_PROGRESS = loading...
-PROCESS_EXITED = Process exited.
\ No newline at end of file
+PROCESS_EXITED = Process exited.
+
+ZOOM_IN = Zoom in the selected item. Try also with a double click
+ZOOM_OUT = Zoom out the view. Try also with a left click
+ZOOM_FULL = Restore the original zoom level. Try also with a mouse wheel click
Binary file vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_body.png has changed
Binary file vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_head.png has changed
Binary file vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/swing/breadcrumb_tail.png has changed
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverter.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverter.java	Thu Aug 20 14:12:33 2015 +0200
@@ -67,7 +67,7 @@
      * @return the resulting tree
      */
     public static TreeMapNode convertToTreeMap(ObjectHistogram histrogram) {
-       TreeMapNode root = new TreeMapNode("", 0);
+        TreeMapNode root = new TreeMapNode("", 0);
         
         List<HistogramRecord> records = new ArrayList<>();
         records.addAll(histrogram.getHistogram());
@@ -104,8 +104,7 @@
                
                 String nodeId = className.split(SPLIT_REG_EXP)[0];
                 
-                TreeMapNode child = lastProcessed.searchNodeByLabel(nodeId);
-                
+                TreeMapNode child = searchNode(lastProcessed, nodeId);
                 if (child == null) {
                     child = new TreeMapNode(nodeId, 0);
                     lastProcessed.addChild(child);
@@ -130,6 +129,16 @@
         }
     }
 
+    private static TreeMapNode searchNode(TreeMapNode root, String nodeId) {
+        List<TreeMapNode> nodes = root.getChildren();
+        for (TreeMapNode node : nodes) {
+            if (node.getLabel().equals(nodeId)) {
+                return node;
+            }
+        }
+        return null;
+    }
+    
     /**
      * This method calcs the real weights using a bottom-up traversal. From leaves, 
      * weights are passed to parent nodem which will have as weight the sum of
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapBreadcrumb.java	Thu Aug 20 14:12:33 2015 +0200
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
+
+import java.awt.Font;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.font.FontRenderContext;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.Stack;
+
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.UIManager;
+
+import com.redhat.thermostat.client.swing.components.Icon;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapIconResources;
+
+/**
+ * This object creates a breadcrumb navigation bar used to trace 
+ * {@link TreeMapComponent} objects' state.
+ */
+public class TreeMapBreadcrumb extends JComponent implements TreeMapObserver {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Font used by bradcrumb items.
+     */
+    private Font FONT = (Font) UIManager.get("thermostat-default-font");
+
+    /**
+     * Stack containing all items part of the breadcrumb.
+     */
+    private Stack<BreadcrumbItem> items;
+
+    /**
+     * The TreeMap object to interact with.
+     */
+    private TreeMapComponent treemap;
+
+    /**
+     * Constructor. Creates a breadcumbs navigation bar with the starting 
+     * element and register itself as observer to the given treemap.
+     * 
+     * @param start the treemap's root.
+     */
+    public TreeMapBreadcrumb(TreeMapComponent treemap, TreeMapNode start) {
+        super();  
+        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+        this.items = new Stack<>();
+        this.treemap = Objects.requireNonNull(treemap);
+        this.treemap.register(this);
+        buildBreadcrumb(start);
+    }
+
+
+    /**
+     * Builds the breadcrumb using the nodes'ancestors.
+     * @param node the tree's branch to represent ad breadcrumb bar.
+     */
+    public void buildBreadcrumb(TreeMapNode node) {
+        LinkedList<TreeMapNode> nodes = node.getAncestors();
+
+        while (!nodes.isEmpty()) {
+            BreadcrumbItem item = new BreadcrumbItem(nodes.removeLast());
+            items.push(item);
+            add(item);
+        }
+        // the first element has no tail
+        items.get(0).setAsFirst();
+
+        //the last element has no head
+        items.peek().setAsLast();
+    }
+
+
+    @Override
+    public void notifySelection(TreeMapNode node) {
+        // do nothing
+    }
+
+    @Override
+    public void notifyZoomIn(TreeMapNode node) {
+        items.clear();
+        removeAll();
+        buildBreadcrumb(node);
+    }
+
+    @Override
+    public void notifyZoomOut() {
+        this.remove(items.pop());
+        items.peek().setAsLast();
+        items.peek().repaint();
+    }
+
+    @Override
+    public void notifyZoomFull() {
+        items.clear();
+        this.removeAll();
+        BreadcrumbItem item = new BreadcrumbItem(treemap.getTreeMapRoot());
+        item.setAsFirst();
+        item.setAsLast();
+        items.push(item);
+        this.add(item);
+    }
+
+
+
+    /**
+     *  This class allows to create a single item in a breadcrumb object.
+     *  This component has 3 {@link JLabel}s which contain the images needed
+     *  to draw an arrow.
+     *        _____
+     *    >  |_____|  >
+     *    |     |     |
+     *  tail   body  head
+     *  
+     */
+    class BreadcrumbItem extends JComponent {
+
+        private static final long serialVersionUID = 1L;
+
+        private final String ROOT_TEXT = "root";
+
+        private JLabel tail;
+        private JLabel body;
+        private JLabel head;
+
+        /**
+         * The node this items represents.
+         */
+        private TreeMapNode node;
+
+        /**
+         * The constructor creates a complete item, including both tail and head
+         * @param node
+         */
+        public BreadcrumbItem(final TreeMapNode node) {
+            super();
+            this.node = node;
+            this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+            initComponent();
+
+            /**
+             * Simulate the click effect increasing and reducing the font size
+             */
+            this.addMouseListener(new MouseAdapter() {
+
+                @Override
+                public void mouseReleased(MouseEvent arg0) {
+                    increaseFont(body, 2);
+                }
+
+                @Override
+                public void mousePressed(MouseEvent arg0) {
+                    increaseFont(body, -2);
+                }
+
+                @Override
+                public void mouseClicked(MouseEvent arg0) {
+                    treemap.zoomIn(node);
+                }
+            });
+        }
+
+
+        /**
+         * Increases the given component's font size.
+         * @param comp the component which edit the font size to.
+         * @param increment value of the increment. Negative values reduce the 
+         * font size.
+         */
+        private void increaseFont(JComponent comp, int increment) {
+            Font f = comp.getFont();
+            int newSize = f.getSize() + increment;
+            f = new Font(f.getName(), f.getStyle(), newSize);
+            comp.setFont(f);
+            comp.repaint();
+        }
+
+        private void initComponent() {
+            initTail();
+            initBody();
+            initHead();
+        }
+
+
+        private void initTail() {
+            tail = new JLabel();
+            tail.setIcon(new Icon(HeapIconResources.getIcon(HeapIconResources.BREADCRUMB_TAIL)));
+            this.add(tail);
+        }
+
+        private void initHead() {
+            head = new JLabel();
+            head.setIcon(new Icon(HeapIconResources.getIcon(HeapIconResources.BREADCRUMB_HEAD)));
+            this.add(head);
+        }
+
+        private void initBody() {
+            body = new JLabel();
+            body.setFont(FONT);
+            body.setHorizontalTextPosition(JLabel.CENTER);
+            body.setText(node.getLabel());
+            adaptIcon(body, new Icon(HeapIconResources.getIcon(HeapIconResources.BREADCRUMB_BODY)));
+            this.add(body);
+        }
+
+
+        public TreeMapNode getNode() {
+            return this.node;
+        }
+
+        /**
+         * Remove the tail of his breadcrumb item.
+         */
+        public void setAsFirst() {
+            this.remove(tail);
+            this.tail = null;
+            this.body.setText(ROOT_TEXT);
+            adaptIcon(body, new Icon(HeapIconResources.getIcon(HeapIconResources.BREADCRUMB_BODY)));
+        }
+
+        /**
+         * Remove the head of his breadcrumb item.
+         */
+        public void setAsLast() {
+            this.remove(head);
+            this.head = null;
+        }
+
+        /**
+         * Sets the text of this item, which is placed in the body.
+         * @param text
+         */
+        public void setText(String text) {
+            body.setText(text);
+        }
+
+        public int getHeight() {
+            return body.getPreferredSize().height;
+        }
+    }
+
+    /**
+     * Calculates the labels' text size in order to scale the given image
+     * and to apply to it.
+     */
+    private void adaptIcon(JLabel label, ImageIcon icon) {
+        Rectangle fontArea;
+        try {
+            fontArea = label.getFont().getStringBounds(label.getText(), 
+                    new FontRenderContext(label.getFont().getTransform(),
+                            false, false)).getBounds();
+
+        } catch (NullPointerException npe) {
+            fontArea = label.getBounds();
+        }
+        
+        Image img = icon.getImage();
+        Image newimg = img.getScaledInstance(fontArea.getBounds().width + 10, 
+                img.getHeight(null),  java.awt.Image.SCALE_SMOOTH);
+        icon = new ImageIcon(newimg);
+        label.setIcon(icon);
+    }
+}
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponent.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponent.java	Thu Aug 20 14:12:33 2015 +0200
@@ -53,6 +53,9 @@
 import java.awt.event.MouseListener;
 import java.awt.font.FontRenderContext;
 import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Objects;
 import java.util.Stack;
 
@@ -163,6 +166,11 @@
     private static Comp lastClicked;
     
     /**
+     * List of objects observing this.
+     */
+    private List<TreeMapObserver> observers;
+
+    /**
      * Constructor which creates a TreeMapComponent by an histogram object.
      * @param histogram the histogram to represent as tree map.
      */
@@ -187,6 +195,7 @@
         lastDim = getSize();
         this.zoomStack = new Stack<>();
         this.zoomStack.push(this.tree);
+        this.observers = new ArrayList<>();
 
         // assign a rectangle to the tree's root in order to process the tree.
         Rectangle2D.Double area = new Rectangle2D.Double(0, 0, d.width, d.height);
@@ -333,7 +342,7 @@
                     Dimension newDim = container.getSize();
 
                     if (isChangedSize(newDim)) {
-                        redrawTreeMap(); 
+                        redrawTreeMap(tree); 
                     }
                 } 
             }            
@@ -379,7 +388,8 @@
      * This method recalculates and redraws the TreeMap in according to the size
      * of this component and the actual {@link TreeMapNode} object.
      */
-    private void redrawTreeMap() {
+    private void redrawTreeMap(TreeMapNode newRoot) {
+        tree = newRoot;
         Rectangle2D.Double newArea = tree.getRectangle();
         // give to the root node the size of this object so it can be recalculated
         newArea.width = getSize().width;
@@ -392,27 +402,33 @@
         drawTreeMap(tree);        
     }
 
-    /**
-     * Zoom the TreeMap on the given node.
-     * @param node the new TreeMap's root.
-     */
+    boolean isZoomInEnabled(TreeMapNode node) {
+        return !(node == null
+                || node.equals(this.tree)
+                || node.isLeaf());
+    }
+
     public void zoomIn(TreeMapNode node) {
-        if (node != null && node != this.tree && !zoomStack.contains(node)) {
-            zoomStack.push(node);
-            tree = node;
-            redrawTreeMap();
+        if (isZoomInEnabled(node)) {
+            fillZoomStack(node.getAncestors());
+            redrawTreeMap(node);
+            notifyZoomInToObservers(zoomStack.peek());
         } 
     }
 
-    /**
-     * Zoom out the view to the last zoom level, until the original root is 
-     * reached.
-     */
+    private void fillZoomStack(LinkedList<TreeMapNode> ancestors) {
+        zoomStack.clear();
+        while (!ancestors.isEmpty()) {
+            zoomStack.push(ancestors.removeLast());
+        }
+    }
+
     public void zoomOut() {
+        // if the actual root element is not the tree's original root
         if (zoomStack.size() > 1) {
             zoomStack.pop();
-            tree = zoomStack.peek();
-            redrawTreeMap();
+            redrawTreeMap(zoomStack.peek());
+            notifyZoomOutToObservers();
         }
     }
 
@@ -422,12 +438,69 @@
     public void zoomFull() {
         if (zoomStack.size() > 1) {
             clearZoomCallsStack();
-            tree = zoomStack.peek();
-            redrawTreeMap();
+            redrawTreeMap(zoomStack.peek());
+            notifyZoomFullToObservers();
+        }
+    }
+    
+    /**
+     * Add the object in input to the list of registered objects to this TreeMap.
+     * @param observer the Notifiable object to register to this object.
+     */
+    public void register(TreeMapObserver observer) {
+        this.observers.add(observer);
+    }
+    
+    /**
+     * Remove the object in input from the list of registered objects to this TreeMap.
+     * @param observer the Notifiable object to unregister from this object.
+     */
+    public void unregister(TreeMapObserver observer) {
+        this.observers.remove(observer);
+    }
+    /**
+     * Notify observers that an object in the TreeMap has been selected.
+     * @param comp the selected component.
+     */
+    private void notifySelectionToObservers(TreeMapNode node) {
+        for (TreeMapObserver observer : observers) {
+            observer.notifySelection(node);
         }
     }
 
     /**
+     * Notify observers that  TreeMap has been zoomed.
+     * @param zoomedComponent 
+     */
+    private void notifyZoomInToObservers(TreeMapNode node) {
+        for (TreeMapObserver observer : observers) {
+            observer.notifyZoomIn(node);
+        }
+    }
+    
+    /**
+     * Notify observers that  TreeMap has been zoomed.
+     * @param zoomedComponent 
+     */
+    private void notifyZoomOutToObservers() {
+        for (TreeMapObserver observer : observers) {
+            observer.notifyZoomOut();
+        }
+    }
+    
+    /**
+     * Notify observers that  TreeMap has been zoomed.
+     * @param zoomedComponent 
+     */
+    private void notifyZoomFullToObservers() {
+        for (TreeMapObserver observer : observers) {
+            observer.notifyZoomFull();
+        }
+    }
+    
+    
+
+    /**
      * Returns the list of zoom operation calls.
      * @return the stack that holds the zoom calls.
      */
@@ -640,19 +713,17 @@
                     if (SwingUtilities.isLeftMouseButton(e)) {
                         selectComp();
                     }
-                    // two left click: zoom-in
-                    // two right click: zoom-out
-                    // two middle click: zoom full
-                    if (e.getClickCount() == 2) {
-                        if (SwingUtilities.isLeftMouseButton(e)) {
-                            if (!getNode().isLeaf()) {
-                                zoomIn(getNode());
-                            }
-                        } else if (SwingUtilities.isRightMouseButton(e)) {
-                            zoomOut();
-                        } else {
-                            zoomFull();
-                        }
+                    // double left click to zoom in (on non-leaf nodes only)
+                    if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) {
+                        zoomIn(getNode());
+                    }
+                    // one right click to zoom out
+                    if (SwingUtilities.isRightMouseButton(e)) {
+                        zoomOut();
+                    }
+                    // one middle click to reset zoom
+                    if (SwingUtilities.isMiddleMouseButton(e)) {
+                        zoomFull();
                     }
                 }
             };
@@ -712,6 +783,7 @@
                 setColor(getColor().darker());
             }
             repaint();
+            notifySelectionToObservers(node);
         }
     }
 }
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNode.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNode.java	Thu Aug 20 14:12:33 2015 +0200
@@ -42,6 +42,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -291,27 +292,6 @@
                 "; rectangle=" + rectangle.getBounds() + "]";
     }
 
-    
-    /**
-     * Search into the tree the node with id = key.
-     * @param key the id of the node to search.
-     * @return the node of exists, else null.
-     */
-    public TreeMapNode searchNodeByLabel(String key) {
-        if (this.getLabel().equals(key)) {
-            return this;
-        }
-        
-        TreeMapNode result = null;
-        for (TreeMapNode child : getChildren()) {
-            result = child.searchNodeByLabel(key) ;
-            if (result != null) {
-                return result;
-            }
-        }
-        return result;
-    }
-
     /**
      * Return the weight of this object. In case of allowNonPositiveWeight is 
      * set to false and the weight is 0, less than 0 or not a number 
@@ -451,4 +431,18 @@
       };
       Collections.sort(nodes, c);
     }
+    
+    /**
+     * Return the list of ancestors node of this object. The first one is this 
+     * node itself, the last one the root.
+     * @return a list of ancestors nodes.
+     */
+    public LinkedList<TreeMapNode> getAncestors() {
+        LinkedList<TreeMapNode> toReturn = new LinkedList<TreeMapNode>();
+        TreeMapNode tmp = this;
+        do {
+            toReturn.add(tmp);
+        } while ((tmp = tmp.getParent()) != null);
+        return toReturn;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapObserver.java	Thu Aug 20 14:12:33 2015 +0200
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
+
+/**
+ * This interface is used as part of the Observer Design Pattern developed
+ * for objects who want to be notified about TreeMap's events.
+ */
+public interface TreeMapObserver {
+    
+    /**
+     * This method inform the Observer object that the object passed as 
+     * argument has been selected.
+     * 
+     * @param selectedComp the selected component to communicate to 
+     * this Observer object.
+     */
+    public void notifySelection(TreeMapNode node);
+    
+    /**
+     * This method informs objects that a zoom in event has been performed on
+     * the given node.
+     * @param node the zoomed node.
+     */
+    public void notifyZoomIn(TreeMapNode node);
+    
+    /**
+     * This method informs objects that a zoom out event has been performed.
+     */
+    public void notifyZoomOut();
+    
+    /**
+     * This method informs objects that the zoom level has been resetted.
+     */
+    public void notifyZoomFull();
+}
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapPanel.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapPanel.java	Thu Aug 20 14:12:33 2015 +0200
@@ -36,40 +36,35 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
 
+import java.awt.BorderLayout;
 import java.awt.Component;
 
-import javax.swing.BoxLayout;
 import javax.swing.JPanel;
 
 import com.redhat.thermostat.client.swing.SwingComponent;
-import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView;
-import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
 import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
 
-@SuppressWarnings("serial")
 public class TreeMapPanel extends HeapTreeMapView implements SwingComponent {
     
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
     private final JPanel panel;
 
     private TreeMapComponent treeMap;
     
     public TreeMapPanel() {
         panel = new JPanel();
-        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+        panel.setLayout(new BorderLayout());
     }
 
     @Override
     public void display(ObjectHistogram histogram) {
         treeMap = new TreeMapComponent(histogram);
-        panel.add(treeMap);
+        panel.add(treeMap, BorderLayout.CENTER);
+        panel.add(new TreeMapToolbar(treeMap), BorderLayout.NORTH);
     }
 
     @Override
     public Component getUiComponent() {
         return panel;
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapToolbar.java	Thu Aug 20 14:12:33 2015 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.Objects;
+
+import javax.swing.Box;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ScrollPaneConstants;
+
+/**
+ * This class provides a tool bar containing a {@lnk TreeMapBreadcrumb} object 
+ * and a {@link TreeMapZoomBar} instance to control the state of 
+ * {@link TreeMapComponent} objects.
+ */
+@SuppressWarnings("serial")
+public class TreeMapToolbar extends JComponent {
+    
+    /**
+     * The panel in which objects are placed.
+     */
+    private JPanel contentPane;
+    
+    /**
+     * The scroll pane used to hide breadcrumb's first items.
+     */
+    private JScrollPane scrollPane;
+    
+    
+    public TreeMapToolbar(TreeMapComponent treemap) {
+        super();
+        initComponent(Objects.requireNonNull(treemap));
+    }
+
+
+    private void initComponent(TreeMapComponent treemap) {
+        this.setLayout(new BorderLayout());
+        
+        final JPanel breadcrumbPanel = new JPanel();
+        breadcrumbPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+        add(breadcrumbPanel, BorderLayout.CENTER);
+        
+        JPanel zoomPanel = new JPanel(new FlowLayout());
+        // add some empty space between the breadcrumb bar and buttons
+        zoomPanel.add(Box.createHorizontalStrut(20));
+        add(zoomPanel, BorderLayout.EAST);
+
+        TreeMapZoomBar zoomBar = new TreeMapZoomBar(treemap);
+        zoomPanel.add(zoomBar);
+        
+        TreeMapBreadcrumb bc = new TreeMapBreadcrumb(treemap, treemap.getTreeMapRoot());
+        
+        contentPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        contentPane.add(bc);
+
+        scrollPane = new JScrollPane(contentPane);
+        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
+        scrollPane.setBorder(null);
+        
+        // allows to see always the last elements of the breadcrumb.
+        scrollPane.getHorizontalScrollBar().addAdjustmentListener(new AdjustmentListener() {
+            public void adjustmentValueChanged(AdjustmentEvent e) {
+                e.getAdjustable().setValue(e.getAdjustable().getMaximum());
+            }
+        });
+        breadcrumbPanel.add(scrollPane);
+        
+        // when the component is resized the new dimension is used to arrange 
+        // the scrollpane, in order to use all available space.
+        breadcrumbPanel.addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent arg0) {
+                Dimension d = breadcrumbPanel.getSize();
+                d.height = 20;
+                scrollPane.setPreferredSize(d);
+            }
+        });
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapZoomBar.java	Thu Aug 20 14:12:33 2015 +0200
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
+
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Objects;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import com.redhat.thermostat.client.swing.components.FontAwesomeIcon;
+import com.redhat.thermostat.client.swing.components.Icon;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
+
+/**
+ * This class provides a component containing zoom in/out/full buttons which can
+ * control a {@link TreeMapComponent} object.
+ */
+public class TreeMapZoomBar extends JComponent implements TreeMapObserver {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private JLabel zoomOut;
+    private JLabel zoomFull;
+    private JLabel zoomIn;
+    
+    private Color defaultColor = Palette.BLACK.getColor();
+    private Color enterColor = Palette.THERMOSTAT_BLU.getColor();
+
+    /**
+     * The treemap object to interact with.
+     */
+    private TreeMapComponent treemap;
+
+    /**
+     * If an item is selected in the treemap, it is stored in order to zoom on 
+     * it if the ZoomIn button is pressed.
+     */
+    private TreeMapNode selectedItem;
+    
+    /**
+     * Constructor. It creates the zoom buttons and registers this object as 
+     * treemap observer.
+     */
+    public TreeMapZoomBar(TreeMapComponent treemap) {
+        super();
+        this.treemap = Objects.requireNonNull(treemap);
+        initComponent();
+        treemap.register(this);
+    }
+
+    private void initComponent() {
+        this.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));
+        createZoomInButton();
+        createZoomFullButton();
+        createZoomOutButton();
+
+        /*
+         * At the beginning no actions can be performed:
+         * Cannot zoom in because no item is selected;
+         * cannot zoom out because zoom in hasn't been performed;
+         * the same for zoom full.
+         */
+        zoomIn.setEnabled(false);
+        zoomFull.setEnabled(false);
+        zoomOut.setEnabled(false);
+    }
+
+
+
+    private void createZoomInButton() {
+        zoomIn = new JLabel();
+        final Icon baseIcon = new FontAwesomeIcon('\uf065', 15, defaultColor);
+        final Icon hoverIcon = new FontAwesomeIcon('\uf065', 15, enterColor);
+        
+        zoomIn.setIcon(baseIcon);
+        zoomIn.setToolTipText(t.localize(LocaleResources.ZOOM_IN).getContents());
+        
+        zoomIn.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseExited(MouseEvent e) {
+                zoomIn.setIcon(baseIcon);
+            }
+
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                zoomIn.setIcon(hoverIcon);
+            }
+
+            @Override
+            public void mouseClicked(MouseEvent arg0) {
+                if (selectedItem != null) {
+                    treemap.zoomIn(selectedItem);
+                }
+            }
+        });
+
+        this.add(zoomIn);
+    }
+
+    private void createZoomOutButton() {
+        zoomOut = new JLabel();
+        
+        final Icon baseIcon = new FontAwesomeIcon('\uf066', 15, defaultColor);
+        final Icon hoverIcon = new FontAwesomeIcon('\uf066', 15, enterColor);
+        
+        zoomOut.setIcon(baseIcon);
+        zoomOut.setToolTipText(t.localize(LocaleResources.ZOOM_OUT).getContents());
+        zoomOut.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseExited(MouseEvent e) {
+                zoomOut.setIcon(baseIcon);
+            }
+
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                zoomOut.setIcon(hoverIcon);
+            }
+
+            @Override
+            public void mouseClicked(MouseEvent arg0) {
+                treemap.zoomOut();
+            }
+        });
+
+        this.add(zoomOut);
+    }
+
+    private void createZoomFullButton() {
+        zoomFull = new JLabel();
+
+        final Icon baseIcon = new FontAwesomeIcon('\uf03b', 15, defaultColor);
+        final Icon hoverIcon = new FontAwesomeIcon('\uf03b', 15, enterColor);
+        
+        zoomFull.setIcon(baseIcon);
+        zoomFull.setToolTipText(t.localize(LocaleResources.ZOOM_FULL).getContents());
+        zoomFull.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseExited(MouseEvent e) {
+                zoomFull.setIcon(baseIcon);
+            }
+
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                zoomFull.setIcon(hoverIcon);
+            }
+
+            @Override
+            public void mouseClicked(MouseEvent arg0) {
+                treemap.zoomFull();
+            }
+        });
+
+        this.add(zoomFull);
+    }
+
+    /**
+     * Changes the buttons state in according to the treemap view.
+     */
+    private void changeState() {
+        selectedItem = null;
+        zoomIn.setEnabled(false);
+
+        if (!isRootShown()) {
+            zoomFull.setEnabled(true);
+            zoomOut.setEnabled(true);
+        } else {
+            zoomFull.setEnabled(false);
+            zoomOut.setEnabled(false);
+        }
+    }
+
+    @Override
+    public void notifySelection(TreeMapNode node) {
+        selectedItem = node;
+        zoomIn.setEnabled(treemap.isZoomInEnabled(node));
+    }
+
+    @Override
+    public void notifyZoomIn(TreeMapNode node) {
+        changeState();
+    }    
+
+    @Override
+    public void notifyZoomOut() {
+        changeState();
+    }
+
+    @Override
+    public void notifyZoomFull() {
+        // no actions can be performed
+        zoomFull.setEnabled(false);
+        zoomOut.setEnabled(false);
+        zoomIn.setEnabled(false);
+    }
+
+    private boolean isRootShown() {
+        return treemap.getTreeMapRoot() == treemap.getZoomCallsStack().firstElement();
+    }
+
+}
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverterTest.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverterTest.java	Thu Aug 20 14:12:33 2015 +0200
@@ -37,8 +37,10 @@
 package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+
+import java.util.List;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,15 +55,19 @@
 
     @Before
     public void setUp() throws Exception {
-        
-       
         /*
-         *  This is the classes structure used for the test and built using histogram
+         * This is the classes structure used for the test and built using
+         * histogram. Nodes are compacted at the point of branching
+         * so for example the node relative to "example2.package1.Class1" is
+         * still just one node "example2.package1.Class1" but the node relative
+         * to "com.example1.package1.Class1" branches at "com" so becomes the
+         * common parent node "com" with children "example1" and "example2",
+         * and so fort:
          * 
          *                ________com_______                    java
          *               /                  \                     |
          *         __example1__           example2              lang
-         *        /            \             |                    |
+         *       /            \             |                    |
          *    package1      package2      package1              Object
          *    /     \           |            |   
          * Class1  Class2     Class3       Class4         
@@ -82,13 +88,16 @@
          *  Class1  Class2   
          *      
          */
-
         final String[] classes = {
                     "com.example1.package1.Class1", 
                     "com.example1.package1.Class2",
                     "com.example1.package2.Class3",
                     "com.example2.package1.Class4",
-                    "java.lang.Object"
+                    "example2.package1.Class1",
+                    "java.lang.Object",
+                    "repeat1.repeat1.RepeatClass",
+                    "repeat2.repeat2A.RepeatClass",
+                    "repeat2.repeat2B.RepeatClass",
                 };
         
         histrogram = new ObjectHistogram();
@@ -112,58 +121,64 @@
         }
     }
     
+    private static TreeMapNode searchNode(List<TreeMapNode> nodes, String nodeId) {
+        for (TreeMapNode node : nodes) {
+            if (node.getLabel().equals(nodeId)) {
+                return node;
+            }
+        }
+        return null;
+    }
+    
     @Test
-    public final void testconvertToTreeMap() {
+    public void testconvertToTreeMap() {
         TreeMapNode tree = HistogramConverter.convertToTreeMap(histrogram);
 
-
-        //tree node is the root element, which has an empty label by default
-        assertEquals(tree.getLabel(), "");
-        assertTrue(tree.getChildren().size() == 2);
-
-
-        TreeMapNode com = tree.searchNodeByLabel("com");
-        assertNotNull(com);
-        assertEquals(com.getParent(), tree);
+        List<TreeMapNode> nodes = tree.getChildren();
+        assertEquals(5, nodes.size());
+        
+        TreeMapNode node = searchNode(nodes, "com");
+        assertNotNull(node);
 
-        TreeMapNode java = tree.searchNodeByLabel("java.lang.Object");
-        assertNotNull(java);
-        assertEquals(java.getParent(), tree);
+        List<TreeMapNode> nodesFromCom = node.getChildren();
+        
+        // example1 and example2
+        assertEquals(2, nodesFromCom.size());
+        node = searchNode(nodesFromCom, "example1");
+        assertNotNull(node);
         
-        // com node has 2 children
-        assertTrue(com.getChildren().size() == 2);
+        // package2.Class3 and package1
+        assertEquals(2, node.getChildren().size());
         
-        TreeMapNode example1 = com.searchNodeByLabel("example1");
-        assertNotNull(example1);
-        assertEquals(example1.getParent(), com);
+        node = searchNode(node.getChildren(), "package2.Class3");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
 
-        //example2 subtree has been collapsed in example2 node
-        TreeMapNode example2 = com.searchNodeByLabel("example2.package1.Class4"); 
-        assertNotNull(example2);
-        assertEquals(example2.getParent(), com);
+        node = searchNode(nodes, "java.lang.Object");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
 
+        node = searchNode(nodes, "example2.package1.Class1");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
         
-        assertTrue(example1.getChildren().size() == 2);
-
-        //class3 node has been collapsed in package2 node
-        TreeMapNode package2 = example1.searchNodeByLabel("package2.Class3");
-        assertNotNull(package2);
-        assertEquals(package2.getParent(), example1);
-        
+        // now on to the  "repeat1.repeat1.RepeatClass" bunch
+        node = searchNode(nodes, "repeat1.repeat1.RepeatClass");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
         
-        TreeMapNode package1 = example1.searchNodeByLabel("package1");
-        assertNotNull(package1);
-        assertEquals(package1.getParent(), example1);
-        
-        assertTrue(package1.getChildren().size() == 2);
+        node = searchNode(nodes, "repeat2");
+        assertNotNull(node);
+        assertEquals(2, node.getChildren().size());
         
-        TreeMapNode class1 = package1.searchNodeByLabel("Class1");
-        assertNotNull(class1);
-        assertTrue(class1.getChildren().isEmpty());
+        List<TreeMapNode> nodesFromRepeat2 = node.getChildren();
+        node = searchNode(nodesFromRepeat2, "repeat2A.RepeatClass");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
         
-        TreeMapNode class2 = package1.searchNodeByLabel("Class2");
-        assertNotNull(class2);
-        assertTrue(class2.getChildren().isEmpty());        
+        node = searchNode(nodesFromRepeat2, "repeat2B.RepeatClass");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
     }
 
 }
\ No newline at end of file
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponentTest.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponentTest.java	Thu Aug 20 14:12:33 2015 +0200
@@ -37,6 +37,7 @@
 package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.awt.Dimension;
@@ -71,7 +72,6 @@
 
     @Test
     public final void testTreeMapComponent() throws InvocationTargetException, InterruptedException {
-
         SwingUtilities.invokeAndWait(new Runnable() {
 
             @Override
@@ -124,6 +124,21 @@
     }
 
     @Test
+    public final void testIsZoomInEnabled() throws InvocationTargetException, InterruptedException {
+        SwingUtilities.invokeAndWait(new Runnable() {
+            @Override
+            public void run() {
+                TreeMapComponent treeMap = new TreeMapComponent(tree, dim);
+
+                assertFalse("Should not be able to zoom in on null", treeMap.isZoomInEnabled(null));
+                assertFalse("Should not be able to zoom in on root", treeMap.isZoomInEnabled(tree));
+                assertTrue("Should be able to zoom in on node 1", treeMap.isZoomInEnabled(node1));
+                assertFalse("Should not be able to zoom in on node 2", treeMap.isZoomInEnabled(node2));
+            }
+        });
+    }
+
+    @Test
     public final void testZoomIn() throws InvocationTargetException, InterruptedException {
         SwingUtilities.invokeAndWait(new Runnable() {
 
@@ -135,7 +150,7 @@
                 assertEquals(node1, treeMap.getTreeMapRoot());
 
                 treeMap.zoomIn(node2);
-                assertEquals(node2, treeMap.getTreeMapRoot());
+                assertEquals(node1, treeMap.getTreeMapRoot());
             }
         });
     }
@@ -152,9 +167,8 @@
                 assertEquals(tree, treeMap.getTreeMapRoot());
 
                 treeMap.zoomIn(node1); //if zoom out root is tree
-                treeMap.zoomIn(node2); //if zoom out root is node1
+                treeMap.zoomIn(node2); //no-op, cannot zoom on leaf
 
-                treeMap.zoomOut();
                 assertEquals(node1, treeMap.getTreeMapRoot());
 
                 treeMap.zoomOut();
@@ -221,4 +235,59 @@
             }
         });
     }
+    
+    @Test
+    public final void testObserver() throws InvocationTargetException, InterruptedException {
+        SwingUtilities.invokeAndWait(new Runnable() {
+            boolean zoomedIn = false;
+            boolean zoomedOut = false;
+            boolean zoomedFull = false;
+            
+            TreeMapObserver observer = new TreeMapObserver() {
+                @Override
+                public void notifyZoomOut() {
+                    zoomedOut = true;
+                }
+                
+                @Override
+                public void notifyZoomIn(TreeMapNode node) {
+                    zoomedIn = true;
+                }
+                
+                @Override
+                public void notifyZoomFull() {
+                    zoomedFull = true;
+                }
+                
+                @Override
+                public void notifySelection(TreeMapNode node) {
+                }
+            };
+
+            @Override
+            public void run() {
+                TreeMapNode child = new TreeMapNode(1);
+                tree.addChild(child);
+                TreeMapNode grandchild = new TreeMapNode(1);
+                child.addChild(grandchild);
+
+                treeMap = new TreeMapComponent(tree, dim);
+                treeMap.register(observer);
+                
+                treeMap.zoomIn(child);
+                assertTrue("Should have zoomed in on child", zoomedIn);
+                zoomedIn = false;
+
+                treeMap.zoomIn(grandchild);
+                assertFalse("Should not have zoomed in on grandchild", zoomedIn);
+                
+                treeMap.zoomOut();
+                assertTrue("Should have zoomed out", zoomedOut);
+                
+                treeMap.zoomIn(child);
+                treeMap.zoomFull();
+                assertTrue("Should have zoomed full", zoomedFull);
+            }
+        });
+    }
 }
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNodeTest.java	Wed Aug 19 11:41:51 2015 +0200
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNodeTest.java	Thu Aug 20 14:12:33 2015 +0200
@@ -46,6 +46,7 @@
 import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -205,32 +206,6 @@
     }
 
     @Test
-    public final void testSearchByLabel() {
-        TreeMapNode root = new TreeMapNode("root", 0);
-
-        TreeMapNode a = new TreeMapNode("a", 3);
-        TreeMapNode b = new TreeMapNode("b", 2);
-        TreeMapNode c = new TreeMapNode("c", 1);
-        root.addChild(a);
-        root.addChild(b);
-        root.addChild(c);
-
-        TreeMapNode aa = new TreeMapNode("aa", 3);
-        TreeMapNode ab = new TreeMapNode("ab", 3);
-        TreeMapNode ac = new TreeMapNode("ac", 3);
-        a.addChild(aa);
-        a.addChild(ab);
-        a.addChild(ac);        
-
-        assertEquals(aa, root.searchNodeByLabel("aa"));
-        assertEquals(b, root.searchNodeByLabel("b"));
-        assertEquals(ac, root.searchNodeByLabel("ac"));
-        assertEquals(c, root.searchNodeByLabel("c"));
-    }
-
-
-
-    @Test
     public final void testSort() {
 
         TreeMapNode n1 = new TreeMapNode(null, 5);
@@ -290,4 +265,25 @@
             node.setColor(node.getNextColor());
         }
     }
+    
+    
+    @Test
+    public final void testGetAncestors() {
+        TreeMapNode node1 = new TreeMapNode(0);
+        TreeMapNode node2 = new TreeMapNode(0);
+        TreeMapNode node3 = new TreeMapNode(0);
+        TreeMapNode node4 = new TreeMapNode(0);
+        
+        node1.addChild(node2);
+        node2.addChild(node3);
+        node3.addChild(node4);
+        
+        LinkedList<TreeMapNode> ancestors = node4.getAncestors();
+        
+        assertEquals(node1, ancestors.get(3));
+        assertEquals(node2, ancestors.get(2));
+        assertEquals(node3, ancestors.get(1));
+        assertEquals(node4, ancestors.get(0));        
+    }
+    
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapToolbarTest.java	Thu Aug 20 14:12:33 2015 +0200
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
+
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Dimension;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.SwingUtilities;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import com.redhat.thermostat.annotations.internal.CacioTest;
+
+@Category(CacioTest.class)
+public class TreeMapToolbarTest {
+
+    private TreeMapComponent treeMap;
+    @SuppressWarnings("unused")
+    private TreeMapToolbar toolbar;
+
+    private static TreeMapNode tree;
+    private static Dimension dim;
+
+    @Test
+    public final void testTreeMapToolbar() throws InvocationTargetException, InterruptedException {
+        SwingUtilities.invokeAndWait(new Runnable() {
+            @Override
+            public void run() {
+                
+                tree = new TreeMapNode(1);
+                dim = new Dimension(500, 500);
+                treeMap = new TreeMapComponent(tree, dim);
+                
+                boolean catched = false;
+                try {
+                    toolbar = new TreeMapToolbar(null);
+                } catch(NullPointerException e) {
+                    catched = true;
+                }
+                assertTrue(catched);
+                try {
+                    toolbar = new TreeMapToolbar(treeMap);
+                } catch (NullPointerException e) {
+                    Assert.fail("Should not throw any exception.");
+                }
+            }
+        });
+    }
+
+   
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapZoomBarTest.java	Thu Aug 20 14:12:33 2015 +0200
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
+
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Dimension;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.SwingUtilities;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import com.redhat.thermostat.annotations.internal.CacioTest;
+
+@Category(CacioTest.class)
+public class TreeMapZoomBarTest {
+
+    private TreeMapComponent treeMap;
+    @SuppressWarnings("unused")
+    private TreeMapZoomBar zoomBar;
+
+    private static TreeMapNode tree;
+    private static Dimension dim;
+
+    @Before
+    public void setUp() {
+        tree = new TreeMapNode(1);
+        dim = new Dimension(500, 500);
+    }
+
+    @Test
+    public final void testTreeMapZoomBar() throws InvocationTargetException, InterruptedException {
+        SwingUtilities.invokeAndWait(new Runnable() {
+
+            @Override
+            public void run() {
+                boolean catched = false;
+                try {
+                    zoomBar = new TreeMapZoomBar(null);
+                } catch(NullPointerException e) {
+                    catched = true;
+                }
+                assertTrue(catched);
+                try {
+                    treeMap = new TreeMapComponent(tree, dim);
+                    zoomBar = new TreeMapZoomBar(treeMap);
+                } catch (NullPointerException e) {
+                    Assert.fail("Should not throw any exception.");
+                }
+            }
+        });
+    }
+
+   
+}