changeset 1085:6c9d61f870b3

Refactor Heap dumper, first part review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-May/006577.html reviewed-by: jerboaa
author Mario Torre <neugens.limasoftware@gmail.com>
date Tue, 14 May 2013 21:14:43 +0200
parents bf4f8d4e38a9
children 75a475d10f7e
files client/swing/src/main/java/com/redhat/thermostat/client/swing/components/CompositeIcon.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/Icon.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/CompositeIconTest.java distribution/config/osgi-export.properties 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/core/HeapView.java vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/chart/OverviewChart.java vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/core/pin_mask.png vm-heap-analysis/client-swing/pom.xml vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingView.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/StatsPanel.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/HeapChartPanel.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/HeapChartPanelLayout.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/HeapDumpListener.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/HeapDumperPopup.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/HeapSelectionEvent.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/OverlayComponent.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/StatsPanel.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingViewTest.java
diffstat 20 files changed, 1335 insertions(+), 309 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/CompositeIcon.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012, 2013 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.client.swing.components;
+
+import java.awt.AlphaComposite;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.ui.IconDescriptor;
+
+/**
+ * 
+ */
+@SuppressWarnings("serial")
+public class CompositeIcon extends Icon {
+
+    private Icon mask;
+    
+    public CompositeIcon(IconDescriptor mask, IconDescriptor thisIcon) {
+        this(mask, new Icon(thisIcon));
+    }
+
+    public CompositeIcon(IconDescriptor mask, Icon thisIcon) {
+        this(new Icon(mask), thisIcon);
+    }
+    
+    public CompositeIcon(Icon mask, Icon thisIcon) {
+        super(thisIcon.getImage());
+        this.mask = mask;
+    }
+    
+    @Override
+    public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
+        GraphicsUtils utils = GraphicsUtils.getInstance();
+      
+        Graphics2D graphics = utils.createAAGraphics(g);
+        
+        int iconW = getIconWidth();
+        int iconH = getIconHeight();
+      
+        BufferedImage imageBuffer = new BufferedImage(iconW, iconH, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D buffer = imageBuffer.createGraphics();
+
+        mask.paintIcon(null, buffer, 0, 0);
+        
+        AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_IN);
+        buffer.setComposite(ac);
+
+        super.paintIcon(null, buffer, 0, 0);
+        
+        buffer.dispose();
+
+        graphics.drawImage(imageBuffer, x, y, null);
+        graphics.dispose();
+    }
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/Icon.java	Mon May 13 12:39:57 2013 -0400
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/Icon.java	Tue May 14 21:14:43 2013 +0200
@@ -38,6 +38,8 @@
 
 import com.redhat.thermostat.client.ui.IconDescriptor;
 import javax.swing.ImageIcon;
+import java.awt.Dimension;
+import java.awt.Image;
 
 /**
  */
@@ -47,4 +49,24 @@
     public Icon(IconDescriptor descriptor) {
         super(descriptor.getData().array());
     }
+
+    public Icon() {
+        super();
+    }
+    
+    public Icon(byte[] data) {
+        super(data);
+    }
+    
+    public Icon(Image image) {
+        super(image);
+    }
+    
+    /**
+     * Returns a {@link Dimension} object with this {@link Icon}
+     * {@code width} and {@code height}.
+     */
+    public Dimension getDimension() {
+        return new Dimension(getIconWidth(), getIconHeight());
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/CompositeIconTest.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,152 @@
+package com.redhat.thermostat.client.swing.components;
+
+import static org.junit.Assert.assertEquals;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.beans.Transient;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.ui.Palette;
+
+public class CompositeIconTest {
+    
+    @Test
+    public void testDisplayIcon() {
+
+        CompositeIcon icon = new CompositeIcon(new MaskIcon(), new SourceIcon());
+        BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+        Graphics2D graphics = (Graphics2D) image.getGraphics();
+        icon.paintIcon(null, graphics, 0, 0);
+        graphics.dispose();
+        
+        int pixel = image.getRGB(0, 0);
+        
+        int a = (pixel >> 24) & 0xFF;
+        int r = (pixel >> 16) & 0xFF;
+        int g = (pixel >>  8) & 0xFF;
+        int b = pixel & 0xFF;
+        
+        assertEquals(0, a);
+        assertEquals(0, r);
+        assertEquals(0, g);
+        assertEquals(0, b);
+        
+        pixel = image.getRGB(image.getWidth() - 1, 0);
+        
+        a = (pixel >> 24) & 0xFF;
+        r = (pixel >> 16) & 0xFF;
+        g = (pixel >>  8) & 0xFF;
+        b = pixel & 0xFF;
+        
+        assertEquals(0, a);
+        assertEquals(0, r);
+        assertEquals(0, g);
+        assertEquals(0, b);
+        
+        pixel = image.getRGB(image.getWidth() - 1, image.getHeight() - 1);
+        
+        a = (pixel >> 24) & 0xFF;
+        r = (pixel >> 16) & 0xFF;
+        g = (pixel >>  8) & 0xFF;
+        b = pixel & 0xFF;
+        
+        assertEquals(0, a);
+        assertEquals(0, r);
+        assertEquals(0, g);
+        assertEquals(0, b);
+        
+        pixel = image.getRGB(0, image.getHeight()/2);
+        
+        a = (pixel >> 24) & 0xFF;
+        r = (pixel >> 16) & 0xFF;
+        g = (pixel >>  8) & 0xFF;
+        b = pixel & 0xFF;
+        
+        // alpha of the source image is 200
+        assertEquals(200, a);
+        assertEquals(0, r);
+        assertEquals(0, g);
+        assertEquals(0, b);
+        
+        pixel = image.getRGB(image.getWidth() - 1, image.getHeight()/2);
+        
+        a = (pixel >> 24) & 0xFF;
+        r = (pixel >> 16) & 0xFF;
+        g = (pixel >>  8) & 0xFF;
+        b = pixel & 0xFF;
+        
+        // alpha of the source image is 200
+        assertEquals(200, a);
+        assertEquals(255, r);
+        assertEquals(255, g);
+        assertEquals(255, b);
+    }
+    
+    private static class SourceIcon extends Icon {
+        @Override
+        public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
+            
+            GraphicsUtils utils = GraphicsUtils.getInstance();
+            
+            Color left = utils.deriveWithAlpha(Palette.BLACK.getColor(), 200);
+            Color right = utils.deriveWithAlpha(Palette.WHITE.getColor(), 200);
+            
+            g.setColor(left);
+            g.fillRect(x, y, getIconWidth()/2, getIconHeight());
+            
+            g.setColor(right);
+            g.fillRect(x + getIconWidth()/2, y, getIconWidth()/2, getIconHeight());
+        }
+        
+        @Override
+        public int getIconHeight() {
+            return 48;
+        }
+        
+        @Override
+        public int getIconWidth() {
+            return 48;
+        }
+        
+        @Override
+        @Transient
+        public Image getImage() {
+            BufferedImage image = new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+            Graphics2D g = (Graphics2D) image.getGraphics();
+            paintIcon(null, g, 0, 0);
+            g.dispose();
+            return image;
+        }
+    }
+    
+    private static class MaskIcon extends Icon {
+        @Override
+        public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
+
+            // draw a black line in the middle
+            int w = getIconWidth()/2;
+            int h = getIconHeight()/2;
+            y = y + h;
+            
+            g.setColor(Color.BLACK);
+            g.drawLine(x, y, x + getIconWidth() - 1, y);
+        }
+        
+        @Override
+        public int getIconHeight() {
+            return 48;
+        }
+        
+        @Override
+        public int getIconWidth() {
+            return 48;
+        }
+    }
+}
--- a/distribution/config/osgi-export.properties	Mon May 13 12:39:57 2013 -0400
+++ b/distribution/config/osgi-export.properties	Tue May 14 21:14:43 2013 +0200
@@ -38,6 +38,7 @@
 org.jfree.chart.event
 org.jfree.chart.labels
 org.jfree.chart.plot
+org.jfree.chart.panel
 org.jfree.chart.renderer.xy
 org.jfree.data
 org.jfree.data.general
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapIconResources.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012, 2013 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.core;
+
+import com.redhat.thermostat.client.ui.IconDescriptor;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class HeapIconResources {
+
+    public static final String PIN_MASK = "com/redhat/thermostat/vm/heap/analysis/client/core/pin_mask.png";
+
+    private static Map<String, IconDescriptor> icons = new HashMap<>();
+    
+    public synchronized static IconDescriptor getIcon(String path) {
+        if (!icons.containsKey(path)) {
+            try {
+                IconDescriptor icon = IconDescriptor.loadIcon(HeapIconResources.class.getClassLoader(), path);
+                icons.put(path, icon);
+                
+            } catch (IOException ex) {
+                Logger.getLogger(HeapIconResources.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
+            }
+        }
+        return icons.get(path);
+    }
+}
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapView.java	Mon May 13 12:39:57 2013 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapView.java	Tue May 14 21:14:43 2013 +0200
@@ -46,7 +46,7 @@
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
 
 public abstract class HeapView extends BasicView implements UIComponent {
-    
+
     public enum HeapDumperAction {
         DUMP_REQUESTED,
         ANALYSE,
@@ -84,6 +84,7 @@
 
     public abstract void openDumpView();
     public abstract void setChildView(BasicView childView);
+    public abstract void setActiveDump(HeapDump dump);
     public abstract void notifyHeapDumpComplete();
 
     public abstract void displayWarning(String string);
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/chart/OverviewChart.java	Mon May 13 12:39:57 2013 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/chart/OverviewChart.java	Tue May 14 21:14:43 2013 +0200
@@ -69,6 +69,8 @@
     private String title;
     private String xAxis;
     private String yAxis;
+
+    private JFreeChart chart;
     
     public OverviewChart(String title, String xAxis, String yAxis, String mainSeries, String secondarySeries) {
         
@@ -83,7 +85,11 @@
         used.setDescription(secondarySeries);
     }
     
-    public JFreeChart createChart(int height, Color bgColor) {
+    public JFreeChart getChart() {
+        return chart;
+    }
+    
+    public void createChart(int height, Color bgColor) {
 
         TimeSeriesCollection dataset = new TimeSeriesCollection();
         
@@ -92,7 +98,7 @@
             dataset.addSeries(used);
         }
         
-        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+        chart = ChartFactory.createTimeSeriesChart(
                 title,
                 xAxis,
                 yAxis,
@@ -139,7 +145,6 @@
         yAxis.setRangeType(RangeType.POSITIVE);
         yAxis.setAutoRangeMinimumSize(10);
         yAxis.setAutoRange(true);
-        return chart;
     }
 
     public void addData(long timeStamp, long used, long total) {
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java	Mon May 13 12:39:57 2013 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java	Tue May 14 21:14:43 2013 +0200
@@ -94,27 +94,30 @@
 
 
     public HeapDumpController(final VmMemoryStatDAO vmMemoryStatDao,
-            final VmInfoDAO vmInfoDao,
-            final HeapDAO heapDao, final VmRef ref,
-            final ApplicationService appService, HeapViewProvider viewProvider,
-            HeapDumpDetailsViewProvider detailsViewProvider,
-            HeapHistogramViewProvider histogramProvider,
-            ObjectDetailsViewProvider objectDetailsProvider,
-            ObjectRootsViewProvider objectRootsProvider) {
+                              final VmInfoDAO vmInfoDao,
+                              final HeapDAO heapDao, final VmRef ref,
+                              final ApplicationService appService, HeapViewProvider viewProvider,
+                              HeapDumpDetailsViewProvider detailsViewProvider,
+                              HeapHistogramViewProvider histogramProvider,
+                              ObjectDetailsViewProvider objectDetailsProvider,
+                              ObjectRootsViewProvider objectRootsProvider)
+    {
         this(vmMemoryStatDao, vmInfoDao, heapDao, ref, appService, viewProvider,
-                detailsViewProvider, histogramProvider, objectDetailsProvider,
-                objectRootsProvider, new HeapDumper(ref));
+             detailsViewProvider, histogramProvider, objectDetailsProvider,
+             objectRootsProvider, new HeapDumper(ref));
     }
 
     HeapDumpController(final VmMemoryStatDAO vmMemoryStatDao,
-            final VmInfoDAO vmInfoDao,
-            final HeapDAO heapDao, final VmRef ref,
-            final ApplicationService appService, HeapViewProvider viewProvider,
-            HeapDumpDetailsViewProvider detailsViewProvider,
-            HeapHistogramViewProvider histogramProvider,
-            ObjectDetailsViewProvider objectDetailsProvider,
-            ObjectRootsViewProvider objectRootsProvider,
-            final HeapDumper heapDumper) {
+                       final VmInfoDAO vmInfoDao,
+                       final HeapDAO heapDao, final VmRef ref,
+                       final ApplicationService appService,
+                       HeapViewProvider viewProvider,
+                       HeapDumpDetailsViewProvider detailsViewProvider,
+                       HeapHistogramViewProvider histogramProvider,
+                       ObjectDetailsViewProvider objectDetailsProvider,
+                       ObjectRootsViewProvider objectRootsProvider,
+                       final HeapDumper heapDumper)
+    {
         this.objectDetailsViewProvider = objectDetailsProvider;
         this.objectRootsViewProvider = objectRootsProvider;
         this.histogramViewProvider = histogramProvider;
@@ -136,7 +139,7 @@
         
         timer.setInitialDelay(0);
         timer.setDelay(1000);
-        model.setRange(3600);
+        model.setRange(1800);
         timer.setTimeUnit(TimeUnit.MILLISECONDS);
         timer.setSchedulingType(SchedulingType.FIXED_RATE);
         
@@ -228,17 +231,20 @@
     }
 
     private void showHeapDumpDetails(HeapDump dump) {
-        HeapDumpDetailsController controller = new HeapDumpDetailsController(
-                appService, detailsViewProvider, histogramViewProvider,
-                objectDetailsViewProvider, objectRootsViewProvider);
+        HeapDumpDetailsController controller =
+                new HeapDumpDetailsController(appService, detailsViewProvider,
+                                              histogramViewProvider,
+                                              objectDetailsViewProvider,
+                                              objectRootsViewProvider);
         controller.setDump(dump);
+        view.setActiveDump(dump);
         view.setChildView(controller.getView());
         view.openDumpView();
     }
 
     @Override
     public UIComponent getView() {
-        return (UIComponent) view;
+        return view;
     }
 
     @Override
@@ -297,8 +303,6 @@
 
             model.notifyListenersOfModelChange();
         }
-
     }
-    
 }
 
Binary file vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/core/pin_mask.png has changed
--- a/vm-heap-analysis/client-swing/pom.xml	Mon May 13 12:39:57 2013 -0400
+++ b/vm-heap-analysis/client-swing/pom.xml	Tue May 14 21:14:43 2013 +0200
@@ -58,7 +58,8 @@
             <Bundle-Activator>com.redhat.thermostat.vm.heap.analysis.client.swing.internal.Activator</Bundle-Activator>
             <Bundle-SymbolicName>com.redhat.thermostat.vm.heap.analysis.client.swing</Bundle-SymbolicName>
             <Private-Package>
-               com.redhat.thermostat.vm.heap.analysis.client.swing.internal
+               com.redhat.thermostat.vm.heap.analysis.client.swing.internal,
+               com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingView.java	Mon May 13 12:39:57 2013 -0400
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingView.java	Tue May 14 21:14:43 2013 +0200
@@ -46,10 +46,14 @@
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
+
+import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats.HeapDumpListener;
+import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats.HeapSelectionEvent;
 
-import org.jfree.chart.ChartPanel;
+import org.jfree.chart.axis.DateAxis;
+import org.jfree.chart.event.AxisChangeEvent;
+import org.jfree.chart.event.AxisChangeListener;
+import org.jfree.chart.plot.XYPlot;
 
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.swing.ComponentVisibleListener;
@@ -59,6 +63,8 @@
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView;
 import com.redhat.thermostat.vm.heap.analysis.client.core.chart.OverviewChart;
 import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
+import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats.HeapChartPanel;
+import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats.StatsPanel;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
 
 public class HeapSwingView extends HeapView implements SwingComponent {
@@ -80,11 +86,11 @@
                 heapDumperNotifier.fireAction(HeapDumperAction.DUMP_REQUESTED);
             }
         });
-        
-        stats.addDumpListListener(new ListSelectionListener() {
+
+        stats.addDumpListListener(new HeapDumpListener() {
             @Override
-            public void valueChanged(ListSelectionEvent arg0) {
-                HeapDump dump = stats.getSelectedHeapDump();
+            public void actionPerformed(HeapSelectionEvent e) {
+                HeapDump dump = e.getSource().getHeapDump();
                 heapDumperNotifier.fireAction(HeapDumperAction.ANALYSE, dump);
             }
         });
@@ -119,10 +125,25 @@
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                ChartPanel charts = new ChartPanel(model.createChart(stats.getWidth(), stats.getBackground()));
+
+                // TODO, move to controller
+                model.createChart(stats.getWidth(), stats.getBackground());
+                
+                final HeapChartPanel charts = new HeapChartPanel(model.getChart());
+                
+                XYPlot plot = model.getChart().getXYPlot();
+                DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
+                domainAxis.addChangeListener(new AxisChangeListener() {
+                    @Override
+                    public void axisChanged(AxisChangeEvent event) {
+                        // somehow the chart panel doesn't see this
+                        charts.revalidate();
+                    }
+                });
+                
                 /*
                  * By default, ChartPanel scales itself instead of redrawing things when
-                 * it's resized. To have it resize automatically, we need to set minimum
+                 * it's re-sized. To have it resize automatically, we need to set minimum
                  * and maximum sizes. Lets constrain the minimum, but not the maximum
                  * size.
                  */
@@ -220,7 +241,17 @@
             });
         }
     }
-    
+
+    @Override
+    public void setActiveDump(final HeapDump dump) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                stats.selectOverlay(dump);
+            }
+        });
+    }
+
     @Override
     public Component getUiComponent() {
         return visiblePane;
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/StatsPanel.java	Mon May 13 12:39:57 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,225 +0,0 @@
-/*
- * Copyright 2012, 2013 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.event.ActionListener;
-import java.util.List;
-
-import javax.swing.BoxLayout;
-import javax.swing.DefaultListModel;
-import javax.swing.GroupLayout;
-import javax.swing.GroupLayout.Alignment;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.LayoutStyle.ComponentPlacement;
-import javax.swing.ListSelectionModel;
-import javax.swing.SwingConstants;
-import javax.swing.event.ListSelectionListener;
-
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason;
-import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
-import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
-
-@SuppressWarnings("serial")
-public class StatsPanel extends JPanel {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private JPanel leftPanel;
-    
-    private JButton heapDumpButton;
-    private JList<HeapDump> dumpList;
-    private DefaultListModel<HeapDump> listModel;
-    
-    private JLabel max;
-    private JLabel current;
-    
-    public StatsPanel() {
-        
-        leftPanel = new JPanel();
-        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.X_AXIS));
-        
-        JPanel rightPanel = new JPanel();
-        GroupLayout groupLayout = new GroupLayout(this);
-        groupLayout.setHorizontalGroup(
-            groupLayout.createParallelGroup(Alignment.LEADING)
-                .addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
-                    .addComponent(leftPanel, GroupLayout.DEFAULT_SIZE, 343, Short.MAX_VALUE)
-                    .addPreferredGap(ComponentPlacement.UNRELATED)
-                    .addComponent(rightPanel, GroupLayout.PREFERRED_SIZE, 252, GroupLayout.PREFERRED_SIZE))
-        );
-        groupLayout.setVerticalGroup(
-            groupLayout.createParallelGroup(Alignment.LEADING)
-                .addComponent(rightPanel, GroupLayout.DEFAULT_SIZE, 293, Short.MAX_VALUE)
-                .addComponent(leftPanel, GroupLayout.DEFAULT_SIZE, 293, Short.MAX_VALUE)
-        );
-        
-        JLabel currentLabel = new JLabel("used:");
-        currentLabel.setHorizontalAlignment(SwingConstants.LEFT);
-        
-        JLabel maxLabel = new JLabel("capacity:");
-        maxLabel.setHorizontalAlignment(SwingConstants.LEFT);
-        
-        current = new JLabel("-");
-        current.setHorizontalAlignment(SwingConstants.RIGHT);
-        
-        max = new JLabel("-");
-        max.setHorizontalAlignment(SwingConstants.RIGHT);
-        
-        heapDumpButton = new JButton(translator.localize(LocaleResources.TRIGGER_HEAP_DUMP));
-        heapDumpButton.setName("heapDumpButton");
-
-        JScrollPane dumpListScrollPane = new JScrollPane();
-        dumpList = new JList<>();
-        dumpList.setName("heapDumpList");
-        listModel = new DefaultListModel<>();
-        dumpList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-        dumpList.setModel(listModel);
-        
-        GroupLayout gl_rightPanel = new GroupLayout(rightPanel);
-        gl_rightPanel.setHorizontalGroup(
-            gl_rightPanel.createParallelGroup(Alignment.TRAILING)
-                .addGroup(gl_rightPanel.createSequentialGroup()
-                    .addContainerGap()
-                    .addGroup(gl_rightPanel.createParallelGroup(Alignment.TRAILING)
-                        .addComponent(dumpListScrollPane, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE)
-                        .addComponent(heapDumpButton, GroupLayout.DEFAULT_SIZE, 173, Short.MAX_VALUE)
-                        .addGroup(gl_rightPanel.createSequentialGroup()
-                            .addGroup(gl_rightPanel.createParallelGroup(Alignment.TRAILING, false)
-                                .addComponent(maxLabel, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                                .addComponent(currentLabel, Alignment.LEADING, GroupLayout.DEFAULT_SIZE, 48, Short.MAX_VALUE))
-                            .addPreferredGap(ComponentPlacement.RELATED)
-                            .addGroup(gl_rightPanel.createParallelGroup(Alignment.TRAILING)
-                                .addComponent(current, GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)
-                                .addComponent(max, GroupLayout.PREFERRED_SIZE, 119, GroupLayout.PREFERRED_SIZE))))
-                    .addContainerGap())
-        );
-        gl_rightPanel.setVerticalGroup(
-            gl_rightPanel.createParallelGroup(Alignment.LEADING)
-                .addGroup(gl_rightPanel.createSequentialGroup()
-                    .addContainerGap()
-                    .addGroup(gl_rightPanel.createParallelGroup(Alignment.BASELINE)
-                        .addComponent(currentLabel, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE)
-                        .addComponent(current))
-                    .addPreferredGap(ComponentPlacement.RELATED)
-                    .addGroup(gl_rightPanel.createParallelGroup(Alignment.BASELINE)
-                        .addComponent(maxLabel)
-                        .addComponent(max))
-                    .addGap(18)
-                    .addComponent(heapDumpButton)
-                    .addGap(18)
-                    .addComponent(dumpListScrollPane, GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE)
-                    .addContainerGap())
-        );
-        rightPanel.setLayout(gl_rightPanel);
-        setLayout(groupLayout);
-
-        dumpListScrollPane.setViewportView(dumpList);
-    }
-    
-    void setChartPanel(JPanel panel) {
-        leftPanel.removeAll();
-        leftPanel.add(panel);
-        leftPanel.revalidate();
-        repaint();
-    }
-
-    public void setMax(String capacity) {
-        max.setText(capacity);
-    }
-
-    public void setUsed(String used) {
-        current.setText(used);
-    }
-    
-    void addHeapDumperListener(ActionListener listener) {
-        heapDumpButton.addActionListener(listener);
-    }
-
-    void addDumpListListener(ListSelectionListener listener) {
-        dumpList.addListSelectionListener(listener);
-    }
-    
-    public void disableHeapDumperControl(DumpDisabledReason reason) {
-        String text = null;
-        switch (reason) {
-        case DUMP_IN_PROGRESS:
-            text = translator.localize(LocaleResources.HEAP_DUMP_IN_PROGRESS);
-            break;
-        case PROCESS_DEAD:
-            text = translator.localize(LocaleResources.PROCESS_EXITED);
-            break;
-        }
-        heapDumpButton.setText(text);
-        heapDumpButton.setEnabled(false);
-    }
-
-    public void enableHeapDumperControl() {
-        heapDumpButton.setText(translator.localize(LocaleResources.TRIGGER_HEAP_DUMP));
-        heapDumpButton.setEnabled(true);
-    }
-
-    public void addDump(HeapDump dump) {
-        
-        listModel.addElement(dump);
-    }
-
-    public void clearDumpList() {
-        listModel.clear();
-    }
-    
-    public HeapDump getSelectedHeapDump() {
-        return dumpList.getSelectedValue();
-    }
-
-    public void updateHeapDumpList(List<HeapDump> heapDumps) {
-        int numItemsBefore = listModel.getSize();
-        for (HeapDump heapDump : heapDumps) {
-            if (! listModel.contains(heapDump)) {
-                listModel.addElement(heapDump);
-            }
-        }
-        if (numItemsBefore == 0 && listModel.getSize() > 0) {
-            dumpList.repaint();
-        }
-    }
-}
-
--- /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/stats/HeapChartPanel.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.event.ChartChangeEvent;
+import org.jfree.chart.event.ChartChangeListener;
+
+@SuppressWarnings("serial")
+public class HeapChartPanel extends ChartPanel {
+            
+    public HeapChartPanel(JFreeChart chart) {
+   
+        super(chart);
+        
+        setName(HeapChartPanel.class.getName());
+        
+        setLayout(new HeapChartPanelLayout());
+        chart.addChangeListener(new ChartChangeListener() {
+            @Override
+            public void chartChanged(ChartChangeEvent event) {
+                doLayout();
+            }
+        });
+    }
+
+    @Override
+    protected void paintChildren(Graphics g) {
+        Rectangle2D area = getScreenDataArea();
+        Graphics2D graphics = (Graphics2D) g.create();
+        graphics.setClip(area);
+        
+        super.paintChildren(graphics);
+        
+        graphics.dispose();
+    }
+}
--- /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/stats/HeapChartPanelLayout.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.LayoutManager2;
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+
+import org.jfree.chart.axis.DateAxis;
+import org.jfree.chart.plot.XYPlot;
+
+import com.redhat.thermostat.common.model.LongRange;
+import com.redhat.thermostat.common.model.LongRangeNormalizer;
+
+/**
+ *
+ */
+public class HeapChartPanelLayout implements LayoutManager2 {
+
+    @Override
+    public void addLayoutComponent(String name, Component comp) {        
+    }
+
+    @Override
+    public void removeLayoutComponent(Component comp) {
+    }
+
+    @Override
+    public Dimension preferredLayoutSize(Container parent) {
+        return new Dimension(5, 5);
+    }
+
+    @Override
+    public Dimension minimumLayoutSize(Container parent) {
+        return new Dimension(5, 5);
+    }
+
+    @Override
+    public void layoutContainer(Container parent) {
+        
+        HeapChartPanel chartPanel = (HeapChartPanel) parent;
+        synchronized (chartPanel.getTreeLock()) {
+
+            Rectangle2D area = chartPanel.getScreenDataArea();
+            
+            XYPlot plot = (XYPlot) chartPanel.getChart().getPlot();
+            DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
+
+            // need first and last value
+            
+            long max = domainAxis.getMaximumDate().getTime();
+            long min = domainAxis.getMinimumDate().getTime();
+
+            LongRange offset = new LongRange(min, max); 
+            
+            LongRangeNormalizer normaliser = new LongRangeNormalizer(offset);
+            chartPanel.getScreenDataArea();
+            
+            normaliser.setMaxNormalized((int) (area.getX() + area.getWidth()));
+            normaliser.setMinNormalized((int) area.getX());
+            
+            int y = (int) (area.getHeight()/2);
+            int bound = y;
+            
+            boolean moveUp = false;
+            int delta = 0;
+            int x = 0;
+            
+            Component[] children = chartPanel.getComponents();
+            for (Component _child : children) {
+                
+                if (!(_child instanceof OverlayComponent)) {
+                    continue;
+                }
+                
+                OverlayComponent child = (OverlayComponent) _child;
+                
+                if (!child.isVisible()) {
+                    continue;
+                }
+                
+                Dimension preferredSize = child.getIconCenter();
+
+                normaliser.setValue(child.getTimestamp());
+                x = (int) normaliser.getValueNormalized() - preferredSize.width;
+
+                preferredSize = child.getPreferredSize();
+                Rectangle bounds = new Rectangle(x, y, preferredSize.width, preferredSize.height);
+
+                if (delta > bound) {
+                    delta = 0;
+                }
+
+                if (moveUp) {
+                    bounds.y = bounds.y - delta;
+                } else {
+                    bounds.y = bounds.y + delta;
+                    delta += bounds.height;
+                }
+                moveUp = !moveUp;
+
+                child.setSize(preferredSize);
+                child.setBounds(bounds);
+            }
+        }
+    }
+
+    @Override
+    public void addLayoutComponent(Component comp, Object constraints) {
+        
+    }
+
+    @Override
+    public Dimension maximumLayoutSize(Container target) {
+        return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public float getLayoutAlignmentX(Container target) {
+        return 0.5f;
+    }
+
+    @Override
+    public float getLayoutAlignmentY(Container target) {
+        return 0.5f;
+    }
+
+    @Override
+    public void invalidateLayout(Container target) {        
+    }
+
+}
--- /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/stats/HeapDumpListener.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import java.util.EventListener;
+
+public interface HeapDumpListener extends EventListener {
+
+    public void actionPerformed(HeapSelectionEvent e);
+}
--- /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/stats/HeapDumperPopup.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import java.awt.event.ActionListener;
+
+import javax.swing.JMenuItem;
+
+import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
+
+@SuppressWarnings("serial")
+public class HeapDumperPopup extends ThermostatPopupMenu {
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    private JMenuItem dumper;
+    
+    public HeapDumperPopup() {
+        dumper = new JMenuItem(translator.localize(LocaleResources.TRIGGER_HEAP_DUMP));
+        add(dumper);
+    }
+    
+    public void addDumperListener(ActionListener listener) {
+        dumper.addActionListener(listener);
+    }
+}
--- /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/stats/HeapSelectionEvent.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import java.util.EventObject;
+
+public class HeapSelectionEvent extends EventObject {
+
+    public HeapSelectionEvent(OverlayComponent source) {
+        super(source);
+    }
+
+    @Override
+    public OverlayComponent getSource() {
+        return (OverlayComponent) super.getSource();
+    }
+}
--- /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/stats/OverlayComponent.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.swing.components.CompositeIcon;
+import com.redhat.thermostat.client.swing.components.Icon;
+import com.redhat.thermostat.client.swing.components.ShadowLabel;
+import com.redhat.thermostat.client.swing.components.timeline.TimelineUtils;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapIconResources;
+import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.beans.Transient;
+import java.util.Date;
+import java.util.Objects;
+
+import javax.swing.ButtonModel;
+
+@SuppressWarnings("serial")
+public class OverlayComponent extends ShadowLabel {
+
+    private HeapDump dump;
+    
+    private boolean selected;
+    private String _text;
+    
+    public class NiceIcon extends Icon {
+        
+        private Icon src;
+        
+        public NiceIcon(Icon src) {
+            this.src = src;
+        }
+        
+        @Override
+        public int getIconHeight() {
+            return src.getIconHeight();
+        }
+        
+        @Override
+        public int getIconWidth() {
+            return src.getIconWidth();
+        }
+        
+        @Override
+        public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
+
+            GraphicsUtils utils = GraphicsUtils.getInstance();
+            Graphics2D graphics = utils.createAAGraphics(g);
+            
+            Color up = utils.deriveWithAlpha(Palette.DARK_GRAY.getColor(), 200);
+            Color bottom = utils.deriveWithAlpha(Palette.BLACK.getColor(), 200);
+
+            Paint gradient = new GradientPaint(0, 0, up, 0, getIconHeight(), bottom);
+            graphics.setPaint(gradient);
+            
+            graphics.fillRect(x, y, getIconWidth(), getIconHeight());
+            
+            graphics.dispose();
+        }
+        
+        @Override
+        @Transient
+        public Image getImage() {
+            BufferedImage image = new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+            Graphics2D g = (Graphics2D) image.getGraphics();
+            paintIcon(null, g, 0, 0);
+            g.dispose();
+            return image;
+        }
+    }
+    
+    public OverlayComponent(HeapDump dump) {
+        
+        super("");
+        
+        setName(OverlayComponent.class.getName());
+        
+        Icon mask = new Icon(HeapIconResources.getIcon(HeapIconResources.PIN_MASK));
+        Icon source = new NiceIcon(mask);
+        
+        setIcon(new CompositeIcon(mask, source));
+        
+        this.dump = dump;
+
+        this._text = new Date(dump.getTimestamp()).toString();
+        
+        setFont(TimelineUtils.FONT);
+
+        setOpaque(false);
+
+        setToolTipText(_text);
+        
+        addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                if (!isSelected()) {
+                    setText(_text);
+                    repaint();
+                }
+            }
+
+            @Override
+            public void mouseExited(MouseEvent e) {
+                if (!isSelected()) {
+                    setText("");
+                    repaint();
+                }
+            }
+        });
+    }
+    
+    public HeapDump getHeapDump() {
+        return this.dump;
+    }
+    
+    public long getTimestamp() {
+        return dump.getTimestamp();
+    }
+
+    @Transient
+    public Dimension getIconCenter() {
+        Dimension preferredSize = ((Icon) getIcon()).getDimension();
+
+        preferredSize.width = (preferredSize.width / 2);
+        preferredSize.height = (preferredSize.height/2);
+
+        return preferredSize;
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(Long.valueOf(dump.getTimestamp()));
+    }
+
+    @Override
+    public boolean equals(Object obj) {        
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        OverlayComponent other = (OverlayComponent) obj;
+        if (getTimestamp() != other.getTimestamp())
+            return false;
+        return true;
+    }
+
+    public void setSelected(boolean selected) {
+        this.selected = selected;
+        if (selected) {
+            setText(_text);
+        } else {
+            setText("");
+        }
+    }
+
+    public boolean isSelected() {
+        return selected;
+    }
+}
--- /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/stats/StatsPanel.java	Tue May 14 21:14:43 2013 +0200
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2012, 2013 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.stats;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JPanel;
+
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason;
+import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
+import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
+
+@SuppressWarnings("serial")
+public class StatsPanel extends JPanel {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private HeapChartPanel heapPanel;
+    private HeapDumperPopup popup;
+
+    private List<OverlayComponent> overlays;
+
+    private boolean canDump;
+    
+    public StatsPanel() {
+        
+        setName(StatsPanel.class.getName());
+        
+        canDump = true;
+        
+        overlays = new ArrayList<>();
+        
+        popup = new HeapDumperPopup();
+        
+        setLayout(new BorderLayout());
+    }
+    
+    public void setChartPanel(HeapChartPanel panel) {
+        if (heapPanel != null) {
+            remove(heapPanel);
+        }
+        heapPanel = panel;
+        for (OverlayComponent overlay : overlays) {
+            heapPanel.add(overlay);
+        }
+        heapPanel.addMouseListener(new MouseAdapter() {
+            public void mousePressed(MouseEvent e)  {
+                checkPopup(e);
+            }  
+
+            public void mouseReleased(MouseEvent e) {
+                checkPopup(e);
+            }  
+
+            private void checkPopup(MouseEvent e) {
+                if (canDump && e.isPopupTrigger()) {
+                    popup.show(e.getComponent(), e.getX(), e.getY());
+                }
+            }
+        });
+        
+        add(heapPanel, BorderLayout.CENTER);
+    }
+
+    public void setMax(String capacity) {
+        //max.setText(capacity);
+    }
+
+    public void setUsed(String used) {
+        //current.setText(used);
+    }
+    
+    public void addHeapDumperListener(ActionListener listener) {
+        popup.addDumperListener(listener);
+    }
+
+    public void addDumpListListener(HeapDumpListener listener) {
+        listenerList.add(HeapDumpListener.class, listener);
+    }
+    
+    public void disableHeapDumperControl(DumpDisabledReason reason) {
+        canDump = false;
+    }
+
+    public void enableHeapDumperControl() {
+        canDump = true;
+    }
+
+    private void fireHeapDumpClicked(OverlayComponent component) {
+        Object[] listeners = listenerList.getListenerList();
+
+        HeapSelectionEvent event = new HeapSelectionEvent(component);
+        
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i] == HeapDumpListener.class) {
+                ((HeapDumpListener) listeners[i + 1]).actionPerformed(event);
+            }
+        }
+    }
+    
+    public void addDump(HeapDump dump) {
+        OverlayComponent dumpOverlay = new OverlayComponent(dump);
+        if (!overlays.contains(dumpOverlay)){
+            overlays.add(dumpOverlay);
+            dumpOverlay.addMouseListener(new MouseAdapter() {
+                @Override
+                public void mouseClicked(MouseEvent e) {
+                    if (e.getClickCount() > 1) {
+                        OverlayComponent sourceOverlay = (OverlayComponent) e.getSource();
+                        for (OverlayComponent overlay : overlays) {
+                            overlay.setSelected(false);
+                        }
+                        sourceOverlay.setSelected(true);
+                        fireHeapDumpClicked(sourceOverlay);
+                    }
+                }
+            });
+            if (heapPanel != null) {
+                heapPanel.add(dumpOverlay);
+            }
+        }    
+    }
+
+    public void clearDumpList() {
+        if (heapPanel != null) {
+            for (OverlayComponent overlay : overlays) {
+                heapPanel.remove(overlay);
+            }
+        }
+        overlays.clear();
+    }
+    
+    public void selectOverlay(HeapDump heapDump) {
+        OverlayComponent dumpOverlay = new OverlayComponent(heapDump);
+        
+        for (OverlayComponent overlay : overlays) {
+            if (overlay.equals(dumpOverlay)) {
+                overlay.setSelected(true);
+            } else {
+                overlay.setSelected(false);
+            }
+        }
+    }
+
+    public void updateHeapDumpList(List<HeapDump> heapDumps) {
+        for (HeapDump heapDump : heapDumps) {
+            addDump(heapDump);
+        }
+    }
+}
+
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingViewTest.java	Mon May 13 12:39:57 2013 -0400
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingViewTest.java	Tue May 14 21:14:43 2013 +0200
@@ -36,23 +36,23 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.swing.internal;
 
+import static org.junit.Assert.*;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.awt.Container;
-import java.util.Arrays;
-import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
 
-import org.fest.swing.annotation.GUITest;
 import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
 import org.fest.swing.edt.GuiActionRunner;
 import org.fest.swing.edt.GuiTask;
 import org.fest.swing.fixture.Containers;
 import org.fest.swing.fixture.FrameFixture;
-import org.fest.swing.fixture.JButtonFixture;
-import org.fest.swing.fixture.JListFixture;
+import org.fest.swing.fixture.JLabelFixture;
+import org.fest.swing.fixture.JPanelFixture;
+import org.fest.swing.fixture.JPopupMenuFixture;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -62,8 +62,11 @@
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.HeapDumperAction;
-import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.HeapSwingView;
+import com.redhat.thermostat.vm.heap.analysis.client.core.chart.OverviewChart;
+import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats.HeapChartPanel;
+import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.stats.OverlayComponent;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
+import com.redhat.thermostat.vm.heap.analysis.common.model.HeapInfo;
 
 @RunWith(CacioFESTRunner.class)
 public class HeapSwingViewTest {
@@ -72,19 +75,30 @@
 
     private FrameFixture frame;
 
+    private OverviewChart model;
+    
+    private long now;
+    
     @BeforeClass
     public static void setUpOnce() {
         FailOnThreadViolationRepaintManager.install();
     }
 
-
     @Before
     public void setUp() throws Exception {
+        
+        now = 1000;
+        
         GuiActionRunner.execute(new GuiTask() {
             
             @Override
             protected void executeInEDT() throws Throwable {
                 view = new HeapSwingView();
+                
+                model = new OverviewChart("fluff", "fluff", "fluff", "fluff", "fluff");                
+                model.addData(now, 1, 1000);
+                model.addData(now + 10000, 1, 1000);
+                view.setModel(model);
             }
         });
         frame = Containers.showInFrame((Container) view.getUiComponent());
@@ -98,51 +112,113 @@
     }
 
     @Test
-    @GUITest
-    public void testActivateHeapDump() {
-        @SuppressWarnings("unchecked")
-        ActionListener<HeapDumperAction> l = mock(ActionListener.class);
-        view.addDumperListener(l);
-        JButtonFixture heapDumpButton = frame.button("heapDumpButton");
-        heapDumpButton.click();
-        verify(l).actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED));
-    }
-
-    @Test
-    @GUITest
-    public void testNotifyHeapDumpComplete() {
-        final JButtonFixture heapDumpButton = frame.button("heapDumpButton");
+    public void testAddHeapDump() {
+        final boolean [] result = new boolean[1];
+        final int [] resultTimes = new int[1];
+        view.addDumperListener(new ActionListener<HeapDumperAction>() {
+            @Override
+            public void actionPerformed(ActionEvent<HeapDumperAction> actionEvent) {
+                if (actionEvent.getActionId() == HeapDumperAction.DUMP_REQUESTED) {
+                    result[0] = true;
+                    resultTimes[0]++;
+                }
+            }
+        });
+        
+        frame.show();
+        
         GuiActionRunner.execute(new GuiTask() {
-            
             @Override
             protected void executeInEDT() throws Throwable {
-                heapDumpButton.component().setEnabled(false);
+                model.addData(now + 20000, 1, 1000);
+            }
+        });
+        
+        assertFalse(result[0]);
+        
+        JPanelFixture panel = frame.panel(HeapChartPanel.class.getName());
+        JPopupMenuFixture popup = panel.showPopupMenu();
+        popup.click();
+        
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                model.addData(now + 30000, 1, 1000);
             }
         });
-
-        view.notifyHeapDumpComplete();
-        frame.robot.waitForIdle();
-
-        heapDumpButton.requireEnabled();
+        
+        assertTrue(result[0]);
+        assertEquals(1, resultTimes[0]);
     }
-
+    
     @Test
-    @GUITest
-    public void testUpdateHeapDumpList() {
-        JListFixture heapDumpList = frame.list("heapDumpList");
-        heapDumpList.requireItemCount(0);
-
-        HeapDump heapDump = mock(HeapDump.class);
-        List<HeapDump> heapDumps = Arrays.asList(heapDump);
+    public void testActivateHeapDump() throws InterruptedException {
+        final boolean [] result = new boolean[1];
+        final int [] resultTimes = new int[1];
+        
+        final CountDownLatch latch = new CountDownLatch(1);
+        
+        HeapInfo info = mock(HeapInfo.class);
+        when(info.getTimeStamp()).thenReturn(now + 10000);
+        final HeapDump dump = new HeapDump(info, null);
+        
+        view.addDumperListener(new ActionListener<HeapDumperAction>() {
+            @Override
+            public void actionPerformed(ActionEvent<HeapDumperAction> actionEvent) {
+                if (actionEvent.getActionId() == HeapDumperAction.ANALYSE) {
+                    result[0] = true;
+                    resultTimes[0]++;
+                } else {
+                    view.addHeapDump(dump);
+                    latch.countDown();
+                }
+            }
+        });
+        
+        frame.show();
 
-        view.updateHeapDumpList(heapDumps);
-        frame.robot.waitForIdle();
-        heapDumpList.requireItemCount(1);
-
-        view.updateHeapDumpList(heapDumps);
-        frame.robot.waitForIdle();
-        heapDumpList.requireItemCount(1);
-
+        // really same as previous test, this time we get the overlay component
+        // though
+        
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                model.addData(now + 20000, 1, 1000);
+            }
+        });
+        
+        assertFalse(result[0]);
+        
+        final JPanelFixture panel = frame.panel(HeapChartPanel.class.getName());
+        JPopupMenuFixture popup = panel.showPopupMenu();
+        popup.click();
+        
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                // needs this because OverlayComponent and HeapChartPanel are
+                // bound by a special layout manager
+                panel.component().doLayout();
+            }
+        });
+        
+        latch.await();
+        
+        JLabelFixture overlay =  panel.label(OverlayComponent.class.getName());
+        overlay.doubleClick();
+        
+        OverlayComponent overlayComponent = (OverlayComponent) overlay.component();
+        assertEquals(dump, overlayComponent.getHeapDump());
+        
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                panel.component().doLayout();
+            }
+        });
+        
+        assertTrue(result[0]);
+        assertEquals(1, resultTimes[0]);
     }
 }