changeset 877:ec6d1e7c6fed

Move some swing classes to more appropriate packages Move some classes in the com.thermostat.client.swing package (and subpackages) to more appropriate locations. Main window should not be public api: c.r.t.c.s.MainWindow -> c.r.t.c.s.internal.MainWindow c.r.t.c.s.MainWindowTest -> c.r.t.c.s.internal.MainWindowTest These classes are not components; move them: c.r.t.c.s.components.EdtHelper -> c.r.t.c.s.EdtHelper c.r.t.c.s.components.EdtHelperTest -> c.r.t.c.s.EdtHelperTest c.r.t.c.s.components.GraphicsUtils -> c.r.t.c.s.GraphicUtils c.r.t.c.s.components.HtmlTextBuilder -> c.r.t.c.s.HtmlTextBuilder Also remove public static void main(String[] args) methods from ActionButton and StatusBar. The code from ActionButton is now a unit test. Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004854.html
author Omair Majid <omajid@redhat.com>
date Tue, 18 Dec 2012 16:39:38 -0500
parents 948273e69e5b
children e5cda64075ed
files client/swing/src/main/java/com/redhat/thermostat/client/swing/EdtHelper.java client/swing/src/main/java/com/redhat/thermostat/client/swing/GraphicsUtils.java client/swing/src/main/java/com/redhat/thermostat/client/swing/HtmlTextBuilder.java client/swing/src/main/java/com/redhat/thermostat/client/swing/MainWindow.java client/swing/src/main/java/com/redhat/thermostat/client/swing/MenuHelper.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButton.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButtonUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EdtHelper.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GradientPanel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GradientRoundBorder.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GraphicsUtils.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HtmlTextBuilder.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/SectionHeader.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ShadowLabel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/StatusBar.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ToolbarButtonBorder.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java client/swing/src/main/java/com/redhat/thermostat/client/swing/views/ClientConfigurationSwing.java client/swing/src/main/java/com/redhat/thermostat/client/swing/views/SearchFieldSwingView.java client/swing/src/test/java/com/redhat/thermostat/client/swing/EdtHelperTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/MainWindowTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/EdtHelperTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowTest.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadTimelineLegendPanel.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectDetailsPanel.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectRootsFrame.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapDetailsSwingTest.java
diffstat 30 files changed, 1833 insertions(+), 1753 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/EdtHelper.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 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;
+
+import java.awt.EventQueue;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.Callable;
+
+import javax.swing.SwingUtilities;
+
+/**
+ * Allows operations to be performed consistently on the Swing EDT
+ * irrespective of whether the caller is running on the EDT or not.
+ * 
+ * @see SwingUtilities#invokeAndWait(Runnable)
+ * @see SwingUtilities#invokeLater(Runnable)
+ */
+public class EdtHelper {
+
+    @SuppressWarnings("serial")
+    private static class CallableException extends RuntimeException {
+
+        private CallableException(Exception ex) {
+            super(ex);
+        }
+        
+    }
+
+    private static class CallableWrapper<T> implements Runnable {
+
+        private Callable<T> callable;
+        private T result;
+
+        private CallableWrapper(Callable<T> c) {
+            callable = c;
+        }
+
+        @Override
+        public void run() {
+            try {
+                result = callable.call();
+            } catch (Exception ex) {
+                throw new CallableException(ex);
+            }
+        }
+
+        private T getResult() {
+            return result;
+        }
+    }
+
+    /**
+     * Invoke the supplied {@link Runnable} on the EDT.
+     * @param r encapsulates the code to run
+     * @throws InvocationTargetException encapsulates the actual exception
+     * that occurs when executing this code.
+     * @throws InterruptedException
+     */
+    public void callAndWait(Runnable r) throws InvocationTargetException, InterruptedException {
+        if (EventQueue.isDispatchThread()) {
+            try {
+                r.run();
+            } catch (Exception ex) {
+                throw new InvocationTargetException(ex);
+            }
+        } else {
+            EventQueue.invokeAndWait(r);
+        }
+    }
+
+    /**
+     * Invokes the supplied {@link Callable} on the EDT, waits until it is
+     * finished execution and returns the result of invoking {@link Callable#call()}.
+     * @param c encapsulates the code to execute
+     * @return the result produce by c
+     * @throws InvocationTargetException indicates an exception occurred when executing the callable
+     * @throws InterruptedException
+     */
+    public <T> T callAndWait(Callable<T> c) throws InvocationTargetException, InterruptedException {
+        CallableWrapper<T> w = new CallableWrapper<>(c);
+        callAndWait(w);
+        return w.getResult();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/GraphicsUtils.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 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;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.RoundRectangle2D;
+
+import javax.swing.JComponent;
+
+import sun.swing.SwingUtilities2;
+
+@SuppressWarnings("restriction")
+public class GraphicsUtils {
+
+    private static GraphicsUtils instance = new GraphicsUtils();
+    public static GraphicsUtils getInstance() {
+        return instance;
+    }
+    
+    public Graphics2D createAAGraphics(Graphics g) {
+        Graphics2D graphics = (Graphics2D) g.create();
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        return graphics;
+    }
+    
+    public void drawStringWithShadow(JComponent component, Graphics2D graphics, String string, Color foreground, int x, int y) {
+        // paint it twice to give a subtle drop shadow effect
+        
+        graphics.setColor(new Color(0f, 0f, 0f, 0.1f));
+        SwingUtilities2.drawString(component, graphics, string, x, y + 1);
+        
+        graphics.setColor(foreground);
+        SwingUtilities2.drawString(component, graphics, string, x, y);
+    }
+    
+    public void drawString(JComponent component, Graphics2D graphics, String string, Color foreground, int x, int y) {
+        graphics.setColor(foreground);
+        SwingUtilities2.drawString(component, graphics, string, x, y);
+    }
+    
+    public FontMetrics getFontMetrics(JComponent component, Font font) {
+        return SwingUtilities2.getFontMetrics(component, font);
+    }
+    
+    public Shape getRoundShape(int width, int height) {
+        return new RoundRectangle2D.Double(0, 0, width, height, 4, 4);
+    }
+    
+    public void setGradientPaint(Graphics2D g, int x, int height, Color start, Color stop) {
+        Paint paint = new GradientPaint(x, 0, start, 0, height, stop);
+        g.setPaint(paint);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/HtmlTextBuilder.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 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;
+
+public class HtmlTextBuilder {
+
+    /*
+     * The api provided by this class needs to be cleaned up.
+     */
+
+    private final StringBuilder text = new StringBuilder();
+
+    public HtmlTextBuilder() {
+        // do nothing
+    }
+
+    public HtmlTextBuilder(String text) {
+        text = escape(text);
+        this.text.append(text);
+    }
+
+    public HtmlTextBuilder bold(boolean on) {
+        if (on) {
+            this.text.append("<b>");
+        } else {
+            this.text.append("</b>");
+        }
+        return this;
+    }
+
+    public HtmlTextBuilder bold(String toBold) {
+        text.append("<b>").append(toBold).append("</b>");
+        return this;
+    }
+
+    public HtmlTextBuilder larger(String toAppend) {
+        text.append("<font size='+2'>").append(escape(toAppend)).append("</font>");
+        return this;
+    }
+
+    public HtmlTextBuilder huge(String toAppend) {
+        text.append("<font size='+6'>").append(escape(toAppend)).append("</font>");
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        // FIXME
+        return null;
+    }
+
+    public String toHtml() {
+        return "<html>" + text.toString() + "</html>";
+    }
+
+    public String toPartialHtml() {
+        return text.toString();
+    }
+
+    private static String escape(String toEscape) {
+        // FIXME implement this
+        return toEscape;
+    }
+
+    public HtmlTextBuilder append(String toAppend) {
+        text.append(escape(toAppend));
+        return this;
+    }
+
+    public HtmlTextBuilder appendRaw(String toAppend) {
+        text.append(toAppend);
+        return this;
+    }
+
+    public static String boldHtml(String toBold) {
+        return new HtmlTextBuilder().bold(toBold).toHtml();
+    }
+
+    public HtmlTextBuilder newLine() {
+        text.append("<br>");
+        return this;
+    }
+
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/MainWindow.java	Tue Dec 18 22:02:42 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,804 +0,0 @@
-/*
- * Copyright 2012 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;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.InputEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.awt.image.BufferedImage;
-import java.io.PrintStream;
-import java.lang.reflect.InvocationTargetException;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
-import javax.swing.BorderFactory;
-import javax.swing.Icon;
-import javax.swing.ImageIcon;
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JSplitPane;
-import javax.swing.JTree;
-import javax.swing.KeyStroke;
-import javax.swing.SwingUtilities;
-import javax.swing.SwingWorker;
-import javax.swing.ToolTipManager;
-import javax.swing.event.TreeExpansionEvent;
-import javax.swing.event.TreeSelectionEvent;
-import javax.swing.event.TreeSelectionListener;
-import javax.swing.event.TreeWillExpandListener;
-import javax.swing.tree.DefaultMutableTreeNode;
-import javax.swing.tree.DefaultTreeCellRenderer;
-import javax.swing.tree.DefaultTreeModel;
-import javax.swing.tree.ExpandVetoException;
-import javax.swing.tree.TreeNode;
-import javax.swing.tree.TreePath;
-import javax.swing.tree.TreeSelectionModel;
-
-import sun.misc.Signal;
-
-import com.redhat.thermostat.client.core.Filter;
-import com.redhat.thermostat.client.core.views.BasicView;
-import com.redhat.thermostat.client.core.views.SearchFieldView.SearchAction;
-import com.redhat.thermostat.client.locale.LocaleResources;
-import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
-import com.redhat.thermostat.client.osgi.service.MenuAction;
-import com.redhat.thermostat.client.osgi.service.VMContextAction;
-import com.redhat.thermostat.client.swing.components.EdtHelper;
-import com.redhat.thermostat.client.swing.components.HtmlTextBuilder;
-import com.redhat.thermostat.client.swing.components.StatusBar;
-import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
-import com.redhat.thermostat.client.swing.internal.MainView;
-import com.redhat.thermostat.client.swing.internal.components.DecoratedDefaultMutableTreeNode;
-import com.redhat.thermostat.client.swing.views.SearchFieldSwingView;
-import com.redhat.thermostat.client.ui.Decorator;
-import com.redhat.thermostat.client.ui.IconDescriptor;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.common.HostsVMsLoader;
-import com.redhat.thermostat.common.dao.HostRef;
-import com.redhat.thermostat.common.dao.Ref;
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.utils.StringUtils;
-
-public class MainWindow extends JFrame implements MainView {
-    
-    public static final String MAIN_WINDOW_NAME = "Thermostat_mainWindo_JFrame_parent#1";
-
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    /**
-     * Updates a TreeModel in the background in an Swing EDT-safe manner.
-     */
-    private static class BackgroundTreeModelWorker extends SwingWorker<DefaultMutableTreeNode, Void> {
-
-        private JTree tree;
-
-        private final DefaultTreeModel treeModel;
-        private DefaultMutableTreeNode treeRoot;
-        
-        private List<Filter<HostRef>> hostFilters;
-        private List<Filter<VmRef>> vmFilters;
-        private List<DecoratorProvider<HostRef>> hostDecorators;
-        private List<DecoratorProvider<VmRef>> vmDecorators;
-        
-        private HostsVMsLoader hostsVMsLoader;
-
-        public BackgroundTreeModelWorker(DefaultTreeModel model, DefaultMutableTreeNode root,
-                                         List<Filter<HostRef>> hostFilters, List<Filter<VmRef>> vmFilters,
-                                         List<DecoratorProvider<HostRef>> hostDecorators,
-                                         List<DecoratorProvider<VmRef>> vmDecorators,
-                                         HostsVMsLoader hostsVMsLoader, JTree tree)
-        {
-            this.hostFilters = hostFilters;
-            this.vmFilters = vmFilters;
-
-            this.vmDecorators = vmDecorators;
-            this.hostDecorators = hostDecorators;
-
-            this.treeModel = model;
-            this.treeRoot = root;
-            this.hostsVMsLoader = hostsVMsLoader;
-            this.tree = tree;
-        }
-
-        @Override
-        protected DefaultMutableTreeNode doInBackground() throws Exception {
-            DefaultMutableTreeNode root = new DefaultMutableTreeNode();
-            
-            Collection<HostRef> hostsInRemoteModel = hostsVMsLoader.getHosts();
-            buildHostSubTree(root, hostsInRemoteModel);
-            return root;
-        }
-
-        private boolean buildHostSubTree(DefaultMutableTreeNode parent, Collection<HostRef> objectsInRemoteModel) {
-            boolean subTreeMatches = false;
-            for (HostRef inRemoteModel : objectsInRemoteModel) {
-                DecoratedDefaultMutableTreeNode inTreeNode =
-                        new DecoratedDefaultMutableTreeNode(inRemoteModel);
-
-                boolean shouldInsert = false;
-                if (hostFilters == null) {
-                    shouldInsert = true;
-                } else {
-                    shouldInsert = true;
-                    for (Filter<HostRef> filter : hostFilters) {
-                        if (!filter.matches(inRemoteModel)) {
-                            shouldInsert = false;
-                            break;
-                        }
-                    }
-                }
-                
-                Collection<VmRef> children = hostsVMsLoader.getVMs(inRemoteModel);
-                boolean subtreeResult = buildVmSubTree(inTreeNode, children);
-                if (subtreeResult) {
-                    shouldInsert = true;
-                }
-
-                if (shouldInsert) {
-                    for (DecoratorProvider<HostRef> decorator : hostDecorators) {
-                        Filter<HostRef> filter = decorator.getFilter();
-                        if (filter != null && filter.matches(inRemoteModel)) {
-                            inTreeNode.addDecorator(decorator.getDecorator());
-                        }
-                    }
-                    
-                    parent.add(inTreeNode);
-                    subTreeMatches = true;
-                }
-            }
-            
-            return subTreeMatches;
-        }
-
-        private boolean buildVmSubTree(DefaultMutableTreeNode parent, Collection<VmRef> objectsInRemoteModel) {
-            boolean subTreeMatches = false;
-            for (VmRef inRemoteModel : objectsInRemoteModel) {
-                DecoratedDefaultMutableTreeNode inTreeNode =
-                        new DecoratedDefaultMutableTreeNode(inRemoteModel);
-
-                boolean shouldInsert = false;
-                if (vmFilters == null) {
-                    shouldInsert = true;
-                } else {
-                    shouldInsert = true;
-                    for (Filter<VmRef> filter : vmFilters) {
-                        if (!filter.matches(inRemoteModel)) {
-                            shouldInsert = false;
-                            break;
-                        }
-                    }
-                }
-
-                if (shouldInsert) {
-                    for (DecoratorProvider<VmRef> decorator : vmDecorators) {
-                        Filter<VmRef> filter = decorator.getFilter();
-                        if (filter != null && filter.matches(inRemoteModel)) {
-                            inTreeNode.addDecorator(decorator.getDecorator());
-                        }
-                    }
-
-                    parent.add(inTreeNode);
-                    subTreeMatches = true;
-                }
-            }
-
-            return subTreeMatches;
-        }
-
-        @Override
-        protected void done() {
-            DefaultMutableTreeNode sourceRoot;
-            try {
-                sourceRoot = get();
-                syncTree(sourceRoot, treeModel, treeRoot);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            } catch (ExecutionException e) {
-                e.printStackTrace();
-            }
-        }
-
-        private void syncTree(DefaultMutableTreeNode sourceRoot, DefaultTreeModel targetModel, DefaultMutableTreeNode targetNode) {
-            
-            @SuppressWarnings("unchecked") // We know what we put into these trees.
-            List<DefaultMutableTreeNode> sourceChildren = Collections.list(sourceRoot.children());
-
-            @SuppressWarnings("unchecked")
-            List<DefaultMutableTreeNode> targetChildren = Collections.list(targetNode.children());
-            for (DefaultMutableTreeNode sourceChild : sourceChildren) {
-                Ref sourceRef = (Ref) sourceChild.getUserObject();
-                DefaultMutableTreeNode targetChild = null;
-                for (DefaultMutableTreeNode aChild : targetChildren) {
-                    Ref targetRef = (Ref) aChild.getUserObject();
-                    if (targetRef.equals(sourceRef)) {
-                        targetChild = aChild;
-                        if (sourceChild instanceof DecoratedDefaultMutableTreeNode) {
-                            DecoratedDefaultMutableTreeNode source = (DecoratedDefaultMutableTreeNode) sourceChild;
-                            ((DecoratedDefaultMutableTreeNode) targetChild).setDecorators(source.getDecorators());
-                        }
-                        break;
-                    }
-                }
-
-                if (targetChild == null) {
-                    targetChild = new DecoratedDefaultMutableTreeNode(sourceRef);
-                    if (sourceChild instanceof DecoratedDefaultMutableTreeNode) {
-                        DecoratedDefaultMutableTreeNode source = (DecoratedDefaultMutableTreeNode) sourceChild;
-                        ((DecoratedDefaultMutableTreeNode) targetChild).setDecorators(source.getDecorators());
-                    }
-                    targetModel.insertNodeInto(targetChild, targetNode, targetNode.getChildCount());
-                }
-
-                syncTree(sourceChild, targetModel, targetChild);
-            }
-
-            for (DefaultMutableTreeNode targetChild : targetChildren) {
-                Ref targetRef = (Ref) targetChild.getUserObject();
-                boolean matchFound = false;
-                for (DefaultMutableTreeNode sourceChild : sourceChildren) {
-                    Ref sourceRef = (Ref) sourceChild.getUserObject();
-                    if (targetRef.equals(sourceRef)) {
-                        matchFound = true;
-                        break;
-                    }
-                }
-
-                if (!matchFound) {
-                    targetModel.removeNodeFromParent(targetChild);
-                }
-            }
-            ensureRootIsExpanded(targetModel);
-        }
-
-        private void ensureRootIsExpanded(final DefaultTreeModel model) {
-            DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
-            tree.expandPath(new TreePath(root.getPath()));
-        }
-
-    }
-
-    private static final long serialVersionUID = 5608972421496808177L;
-
-    private final JMenuBar mainMenuBar = new JMenuBar();
-    private final MenuHelper mainMenuHelper = new MenuHelper(mainMenuBar);
-    private JPanel contentArea = null;
-
-    private SearchFieldSwingView searchField = new SearchFieldSwingView();
-    private JTree agentVmTree = null;
-
-    private final ShutdownClient shutdownAction;
-
-    private ActionNotifier<Action> actionNotifier = new ActionNotifier<>(this);
-
-    private ThermostatPopupMenu vmContextMenu;
-    private StatusBar statusBar;
-    
-    private final DefaultMutableTreeNode publishedRoot =
-            new DefaultMutableTreeNode(translator.localize(LocaleResources.MAIN_WINDOW_TREE_ROOT_NAME));
-    private final DefaultTreeModel publishedTreeModel = new DefaultTreeModel(publishedRoot);
-
-    @SuppressWarnings("restriction")
-    public MainWindow() {
-        super();
-
-        setName(MAIN_WINDOW_NAME);
-        
-        shutdownAction = new ShutdownClient();
-
-        searchField.addActionListener(new ActionListener<SearchAction>() {
-            @Override
-            public void actionPerformed(ActionEvent<SearchAction> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case TEXT_CHANGED:
-                    fireViewAction(Action.HOST_VM_TREE_FILTER);
-                    break;
-                }
-            }
-        });
-        agentVmTree = new JTree(publishedTreeModel);
-        agentVmTree.setName("agentVmTree");
-        agentVmTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
-        agentVmTree.setCellRenderer(new AgentVmTreeCellRenderer());
-        agentVmTree.addTreeWillExpandListener(new TreeWillExpandListener() {
-            @Override
-            public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
-                /* Yup, tree will expand */
-            }
-
-            @Override
-            public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
-                if (new TreePath(publishedRoot.getPath()).equals(event.getPath())) {
-                    throw new ExpandVetoException(event, "root cant be collapsed");
-                }
-            }
-        });
-        ToolTipManager.sharedInstance().registerComponent(agentVmTree);
-        contentArea = new JPanel(new BorderLayout());
-
-        setupMenus();
-        setupPanels();
-
-        this.setPreferredSize(new Dimension(800, 600));
-
-        agentVmTree.setSelectionPath(new TreePath(((DefaultMutableTreeNode) publishedTreeModel.getRoot()).getPath()));
-        
-        //agentVmTree.setLargeModel(true);
-        agentVmTree.setRowHeight(25);
-        
-        statusBar = new StatusBar();
-        getContentPane().add(statusBar, BorderLayout.SOUTH);
-        
-        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
-        addWindowListener(shutdownAction);
-
-        // Handle SIGTERM/SIGINT properly
-        Signal.handle(new Signal("TERM"), shutdownAction);
-        Signal.handle(new Signal("INT"), shutdownAction);
-        
-        addComponentListener(new ComponentAdapter() {
-
-            @Override
-            public void componentShown(ComponentEvent e) {
-                fireViewAction(Action.VISIBLE);
-            }
-
-            @Override
-            public void componentHidden(ComponentEvent e) {
-                fireViewAction(Action.HIDDEN);
-            }
-        });
-
-    }
-
-    private void setupMenus() {
-
-        JMenu fileMenu = new JMenu(translator.localize(LocaleResources.MENU_FILE));
-        fileMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
-        mainMenuBar.add(fileMenu);
-
-        JMenuItem fileExitMenu = new JMenuItem(translator.localize(LocaleResources.MENU_FILE_EXIT));
-        fileExitMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK));
-        fileExitMenu.addActionListener(shutdownAction);
-        fileMenu.add(fileExitMenu);
-
-        JMenu editMenu = new JMenu(translator.localize(LocaleResources.MENU_EDIT));
-        mainMenuBar.add(editMenu);
-
-        JMenuItem configureClientMenuItem = new JMenuItem(translator.localize(LocaleResources.MENU_EDIT_CONFIGURE_CLIENT));
-        configureClientMenuItem.setName("showClientConfig");
-        configureClientMenuItem.addActionListener(new java.awt.event.ActionListener() {
-            @Override
-            public void actionPerformed(java.awt.event.ActionEvent e) {
-                fireViewAction(Action.SHOW_CLIENT_CONFIG);
-            }
-        });
-        editMenu.add(configureClientMenuItem);
-
-        editMenu.addSeparator();
-        JMenuItem historyModeMenuItem = new JCheckBoxMenuItem(translator.localize(LocaleResources.MENU_EDIT_ENABLE_HISTORY_MODE));
-        historyModeMenuItem.setName("historyModeSwitch");
-        historyModeMenuItem.setSelected(false);
-        historyModeMenuItem.addActionListener(new java.awt.event.ActionListener() {
-            @Override
-            public void actionPerformed(java.awt.event.ActionEvent e) {
-                fireViewAction(Action.SWITCH_HISTORY_MODE);
-            }
-        });
-        editMenu.add(historyModeMenuItem);
-
-        JMenu viewMenu = new JMenu(translator.localize(LocaleResources.MENU_VIEW));
-        mainMenuBar.add(viewMenu);
-        JMenuItem configureAgentMenuItem = new JMenuItem(translator.localize(LocaleResources.MENU_VIEW_AGENTS));
-        configureAgentMenuItem.setName("showAgentConfig");
-        configureAgentMenuItem.addActionListener(new java.awt.event.ActionListener() {
-            @Override
-            public void actionPerformed(java.awt.event.ActionEvent e) {
-                fireViewAction(Action.SHOW_AGENT_CONFIG);
-            }
-        });
-        viewMenu.add(configureAgentMenuItem);
-
-        JMenu helpMenu = new JMenu(translator.localize(LocaleResources.MENU_HELP));
-        helpMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
-        mainMenuBar.add(helpMenu);
-
-        JMenuItem helpAboutMenu = new JMenuItem(translator.localize(LocaleResources.MENU_HELP_ABOUT));
-        helpAboutMenu.addActionListener(new java.awt.event.ActionListener() {
-            @Override
-            public void actionPerformed(java.awt.event.ActionEvent e) {
-                fireViewAction(Action.SHOW_ABOUT_DIALOG);
-            }
-        });
-        helpMenu.add(helpAboutMenu);
-        setJMenuBar(mainMenuBar);
-    }
-
-    private void setupPanels() {
-        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
-        splitPane.setOneTouchExpandable(true);
-        
-        JPanel navigationPanel = new JPanel(new BorderLayout());
-
-        navigationPanel.add(searchField, BorderLayout.PAGE_START);
-
-        agentVmTree.addTreeSelectionListener(new TreeSelectionListener() {
-            @Override
-            public void valueChanged(TreeSelectionEvent e) {
-                if (e.isAddedPath()) {
-                    fireViewAction(Action.HOST_VM_SELECTION_CHANGED);
-                }
-            }
-        });
-        registerContextActionListener(agentVmTree);
-        
-        JScrollPane treeScrollPane = new JScrollPane(agentVmTree);
-        
-        navigationPanel.add(treeScrollPane);
-
-        JPanel detailsPanel = createDetailsPanel();
-
-        navigationPanel.setMinimumSize(new Dimension(200,500));
-        detailsPanel.setMinimumSize(new Dimension(500, 500));
-
-        splitPane.add(navigationPanel);
-        splitPane.add(detailsPanel);
-
-        getContentPane().add(splitPane);
-    }
-
-    private void registerContextActionListener(JTree agentVmTree2) {
-        vmContextMenu = new ThermostatPopupMenu();
-        agentVmTree2.addMouseListener(new MouseAdapter() {
-            @Override
-            public void mousePressed(MouseEvent e) {
-                if (e.isPopupTrigger()) {
-                    Ref ref = getSelectedHostOrVm();
-                    if (ref instanceof VmRef) {
-                        fireViewAction(Action.SHOW_VM_CONTEXT_MENU, e);
-                    }
-                }
-            }
-        });
-    }
-
-    @Override
-    public void showVMContextActions(final List<VMContextAction> actions, final MouseEvent e) {
-        SwingUtilities.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                vmContextMenu.removeAll();
-
-                for (final VMContextAction action: actions) {
-                    JMenuItem contextAction = new JMenuItem();
-                    contextAction.setText(action.getName());
-                    contextAction.setToolTipText(action.getDescription());
-
-                    contextAction.addActionListener(new java.awt.event.ActionListener() {
-                        @Override
-                        public void actionPerformed(java.awt.event.ActionEvent e) {
-                            fireViewAction(Action.VM_CONTEXT_ACTION, action);
-                        }
-                    });
-                    vmContextMenu.add(contextAction);
-                }
-
-                vmContextMenu.show((Component)e.getSource(), e.getX(), e.getY());
-            }
-
-        });
-    }
-    
-    private JPanel createDetailsPanel() {
-        JPanel result = new JPanel(new BorderLayout());
-        result.add(contentArea, BorderLayout.CENTER);
-        return result;
-    }
-
-    @SuppressWarnings("restriction")
-    public class ShutdownClient extends WindowAdapter implements java.awt.event.ActionListener, sun.misc.SignalHandler {
-
-        @Override
-        public void windowClosing(WindowEvent e) {
-            shutdown();
-        }
-
-        @Override
-        public void actionPerformed(java.awt.event.ActionEvent e) {
-            shutdown();
-        }
-        
-        @Override
-        public void handle(Signal arg0) {
-            shutdown();
-        }
-
-        private void shutdown() {
-            dispose();
-            fireViewAction(Action.SHUTDOWN);
-        }
-
-    }
-
-    private static class AgentVmTreeCellRenderer extends DefaultTreeCellRenderer {
-        private static final long serialVersionUID = 4444642511815252481L;
-
-        @Override
-        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
-            
-            Object node = ((DefaultMutableTreeNode) value).getUserObject();
-            setToolTipText(createToolTipText(node));
-            
-            Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
-            if (value instanceof DecoratedDefaultMutableTreeNode) {
-                DecoratedDefaultMutableTreeNode treeNode = (DecoratedDefaultMutableTreeNode) value;
-                setAnnotation(treeNode, node, component);
-            }
-
-            return component;
-        }
-        
-        // TODO: we can cache more, for example the full icon, not just the decoration
-        private Map<Decorator, ImageIcon> decoratorsCache = new HashMap<>();
-        private void setAnnotation(DecoratedDefaultMutableTreeNode treeNode, Object value, Component component) {
-
-            List<Decorator> decorators = treeNode.getDecorators();
-            for (Decorator dec : decorators) {
-                String newText = dec.getLabel(getText());
-                setText(newText);
-                setLabelFor(component);
-                
-                ImageIcon icon = decoratorsCache.get(dec);
-                if (icon == null) {
-                    //System.err.println("cache miss: " + dec);
-                    IconDescriptor iconDescriptor = dec.getIconDescriptor();
-                    if (iconDescriptor != null) {
-                        ByteBuffer data = iconDescriptor.getData();
-                        icon = new ImageIcon(data.array());
-                        decoratorsCache.put(dec, icon);
-                    }
-                }
-                
-                if (icon == null) {
-                    return;
-                }
-                
-                Icon currentIcon = getIcon();
-                switch (dec.getQuadrant()) {
-                case BOTTOM_LEFT:
-                    int y = currentIcon.getIconHeight() - icon.getIconHeight();
-                    paintCustomIcon(currentIcon, icon, y);
-                    break;
-                    
-                case TOP_LEFT:
-                    paintCustomIcon(currentIcon, icon, 0);
-                    break;
-                    
-                case MAIN:
-                default:
-                    setIcon(icon);
-                    break;
-                }
-            }
-        }
-        
-        private void paintCustomIcon(Icon currentIcon, ImageIcon icon, int y) {
-            BufferedImage image = new BufferedImage(currentIcon.getIconWidth(),
-                                                    currentIcon.getIconHeight(),
-                                                    BufferedImage.TYPE_INT_ARGB);
-            Graphics2D graphics = (Graphics2D) image.getGraphics();
-            
-            currentIcon.paintIcon(null, graphics, 0, 0);
-            graphics.drawImage(icon.getImage(), 0, y, null);
-            
-            setIcon(new ImageIcon(image));
-        }
-        
-        private String createToolTipText(Object value) {
-            if (value instanceof HostRef) {
-                HostRef hostRef = (HostRef) value;
-                String hostNameHtml = new HtmlTextBuilder().bold(hostRef.getHostName()).toPartialHtml();
-                String agentIdHtml = new HtmlTextBuilder().bold(hostRef.getAgentId()).toPartialHtml();
-                HtmlTextBuilder builder = new HtmlTextBuilder()
-                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_HOST_NAME, hostNameHtml))
-                    .newLine()
-                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_AGENT_ID, agentIdHtml));
-                return builder.toHtml();
-            } else if (value instanceof VmRef) {
-                VmRef vmRef = (VmRef) value;
-                String vmNameHtml= new HtmlTextBuilder().bold(vmRef.getName()).toPartialHtml();
-                String vmIdHtml = new HtmlTextBuilder().bold(vmRef.getIdString()).toPartialHtml();
-                HtmlTextBuilder builder = new HtmlTextBuilder()
-                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_VM_NAME, vmNameHtml))
-                    .newLine()
-                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_VM_ID, vmIdHtml));
-                return builder.toHtml();
-            } else {
-                return null;
-            }
-        }
-    }
-
-    @Override
-    public JFrame getTopFrame() {
-        return this;
-    }
-    
-    @Override
-    public void addActionListener(ActionListener<Action> l) {
-        actionNotifier.addActionListener(l);
-    }
-
-    public void removeViewActionListener(ActionListener<Action> l) {
-        actionNotifier.removeActionListener(l);
-    }
-
-    private void fireViewAction(Action action) {
-        actionNotifier.fireAction(action);
-    }
-    
-    private void fireViewAction(Action action, Object payload) {
-        actionNotifier.fireAction(action, payload);
-    }
-    
-    @Override
-    public void updateTree(List<Filter<HostRef>> hostFilters, List<Filter<VmRef>> vmFilters,
-            List<DecoratorProvider<HostRef>> hostDecorators,
-            List<DecoratorProvider<VmRef>> vmDecorators,
-            HostsVMsLoader hostsVMsLoader)
-    {
-        BackgroundTreeModelWorker worker =
-                new BackgroundTreeModelWorker(publishedTreeModel, publishedRoot,
-                                              hostFilters, vmFilters, hostDecorators, vmDecorators, hostsVMsLoader, agentVmTree);
-        worker.execute();
-    }
-
-    @SuppressWarnings("unused") // Used for debugging but not in production code.
-    private static void printTree(PrintStream out, TreeNode node, int depth) {
-        out.println(StringUtils.repeat("  ", depth) + node.toString());
-        @SuppressWarnings("unchecked")
-        List<TreeNode> children = Collections.list(node.children());
-        for (TreeNode child : children) {
-            printTree(out, child, depth + 1);
-        }
-    }
-
-    @Override
-    public void setWindowTitle(String title) {
-        setTitle(title);
-    }
-
-    @Override
-    public void showMainWindow() {
-        try {
-            new EdtHelper().callAndWait(new Runnable() {
-
-                @Override
-                public void run() {
-                    pack();
-                    setVisible(true);
-                }
-            });
-        } catch (InvocationTargetException e) {
-            throw new RuntimeException(e);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-        }
-    }
-
-    @Override
-    public void hideMainWindow() {
-        setVisible(false);
-        dispose();
-    }
-
-    @Override
-    public void setStatusBarPrimaryStatus(final String primaryStatus) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                statusBar.setPrimaryStatus(primaryStatus);
-            }
-        });
-    }
-    
-    @Override
-    public void setSubView(final BasicView view) {
-        if (view instanceof SwingComponent) {
-            final SwingComponent swingComp = (SwingComponent)view;
-            SwingUtilities.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    contentArea.removeAll();
-                    Component toAdd = swingComp.getUiComponent();
-                    contentArea.add(toAdd);
-                    contentArea.revalidate();
-                }
-            });
-        }
-    }
-
-    @Override
-    public void addMenu(MenuAction action) {
-        mainMenuHelper.addMenuAction(action);
-    }
-
-    @Override
-    public void removeMenu(MenuAction action) {
-        mainMenuHelper.removeMenuAction(action);
-    }
-
-    /**
-     * Returns null to indicate no Ref is selected
-     */
-    @Override
-    public Ref getSelectedHostOrVm() {
-        TreePath path = agentVmTree.getSelectionPath();
-        if (path == null || path.getPathCount() == 1) {
-            return null;
-        }
-        return (Ref) ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
-    }
-
-    @Override
-    public String getHostVmTreeFilterText() {
-        return searchField.getSearchText();
-    }
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/MenuHelper.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/MenuHelper.java	Tue Dec 18 16:39:38 2012 -0500
@@ -50,7 +50,6 @@
 import javax.swing.MenuElement;
 
 import com.redhat.thermostat.client.osgi.service.MenuAction;
-import com.redhat.thermostat.client.swing.components.EdtHelper;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.common.utils.StringUtils;
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButton.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButton.java	Tue Dec 18 16:39:38 2012 -0500
@@ -99,45 +99,5 @@
         }
     }
     
-    public static void main(String[] args) throws InvocationTargetException, InterruptedException {
-        SwingUtilities.invokeAndWait(new Runnable() {
-            
-            @Override
-            public void run() {
-               JFrame frame = new JFrame();
-               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-               
-               HeaderPanel header = new HeaderPanel();
-               header.setHeader("Test");
-               
-               Icon icon = new Icon() {
-                
-                @Override
-                public void paintIcon(Component c, Graphics g, int x, int y) {
-                    g.setColor(Color.CYAN);
-                    g.fillRect(x, y, 16, 16);
 
-                }
-                
-                @Override
-                public int getIconWidth() {
-                    // TODO Auto-generated method stub
-                    return 16;
-                }
-                
-                @Override
-                public int getIconHeight() {
-                    // TODO Auto-generated method stub
-                    return 16;
-                }
-            }; 
-               
-               header.addToolBarButton(new ActionButton(icon, "Fluff"));
-               
-               frame.getContentPane().add(header);
-               frame.setSize(500, 500);
-               frame.setVisible(true);
-            }
-        });
-    }      
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButtonUI.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButtonUI.java	Tue Dec 18 16:39:38 2012 -0500
@@ -52,6 +52,7 @@
 import javax.swing.JComponent;
 import javax.swing.plaf.metal.MetalButtonUI;
 
+import com.redhat.thermostat.client.swing.GraphicsUtils;
 import com.redhat.thermostat.client.ui.Palette;
 
 class ActionButtonUI extends MetalButtonUI {
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EdtHelper.java	Tue Dec 18 22:02:42 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/*
- * Copyright 2012 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.EventQueue;
-import java.lang.reflect.InvocationTargetException;
-import java.util.concurrent.Callable;
-
-import javax.swing.SwingUtilities;
-
-/**
- * Allows operations to be performed consistently on the Swing EDT
- * irrespective of whether the caller is running on the EDT or not.
- * 
- * @see SwingUtilities#invokeAndWait(Runnable)
- * @see SwingUtilities#invokeLater(Runnable)
- */
-public class EdtHelper {
-
-    @SuppressWarnings("serial")
-    private static class CallableException extends RuntimeException {
-
-        private CallableException(Exception ex) {
-            super(ex);
-        }
-        
-    }
-
-    private static class CallableWrapper<T> implements Runnable {
-
-        private Callable<T> callable;
-        private T result;
-
-        private CallableWrapper(Callable<T> c) {
-            callable = c;
-        }
-
-        @Override
-        public void run() {
-            try {
-                result = callable.call();
-            } catch (Exception ex) {
-                throw new CallableException(ex);
-            }
-        }
-
-        private T getResult() {
-            return result;
-        }
-    }
-
-    /**
-     * Invoke the supplied {@link Runnable} on the EDT.
-     * @param r encapsulates the code to run
-     * @throws InvocationTargetException encapsulates the actual exception
-     * that occurs when executing this code.
-     * @throws InterruptedException
-     */
-    public void callAndWait(Runnable r) throws InvocationTargetException, InterruptedException {
-        if (EventQueue.isDispatchThread()) {
-            try {
-                r.run();
-            } catch (Exception ex) {
-                throw new InvocationTargetException(ex);
-            }
-        } else {
-            EventQueue.invokeAndWait(r);
-        }
-    }
-
-    /**
-     * Invokes the supplied {@link Callable} on the EDT, waits until it is
-     * finished execution and returns the result of invoking {@link Callable#call()}.
-     * @param c encapsulates the code to execute
-     * @return the result produce by c
-     * @throws InvocationTargetException indicates an exception occurred when executing the callable
-     * @throws InterruptedException
-     */
-    public <T> T callAndWait(Callable<T> c) throws InvocationTargetException, InterruptedException {
-        CallableWrapper<T> w = new CallableWrapper<>(c);
-        callAndWait(w);
-        return w.getResult();
-    }
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GradientPanel.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GradientPanel.java	Tue Dec 18 16:39:38 2012 -0500
@@ -45,6 +45,8 @@
 
 import javax.swing.JPanel;
 
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+
 /**
  * A {@link JPanel} filled with a gradient color as the background.
  */
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GradientRoundBorder.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GradientRoundBorder.java	Tue Dec 18 16:39:38 2012 -0500
@@ -50,6 +50,7 @@
 import javax.swing.border.Border;
 import javax.swing.plaf.UIResource;
 
+import com.redhat.thermostat.client.swing.GraphicsUtils;
 import com.redhat.thermostat.client.ui.Palette;
 
 /**
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/GraphicsUtils.java	Tue Dec 18 22:02:42 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/*
- * Copyright 2012 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.Color;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.GradientPaint;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Paint;
-import java.awt.RenderingHints;
-import java.awt.Shape;
-import java.awt.geom.RoundRectangle2D;
-
-import javax.swing.JComponent;
-
-import sun.swing.SwingUtilities2;
-
-@SuppressWarnings("restriction")
-public class GraphicsUtils {
-
-    private static GraphicsUtils instance = new GraphicsUtils();
-    public static GraphicsUtils getInstance() {
-        return instance;
-    }
-    
-    public Graphics2D createAAGraphics(Graphics g) {
-        Graphics2D graphics = (Graphics2D) g.create();
-        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        return graphics;
-    }
-    
-    public void drawStringWithShadow(JComponent component, Graphics2D graphics, String string, Color foreground, int x, int y) {
-        // paint it twice to give a subtle drop shadow effect
-        
-        graphics.setColor(new Color(0f, 0f, 0f, 0.1f));
-        SwingUtilities2.drawString(component, graphics, string, x, y + 1);
-        
-        graphics.setColor(foreground);
-        SwingUtilities2.drawString(component, graphics, string, x, y);
-    }
-    
-    public void drawString(JComponent component, Graphics2D graphics, String string, Color foreground, int x, int y) {
-        graphics.setColor(foreground);
-        SwingUtilities2.drawString(component, graphics, string, x, y);
-    }
-    
-    public FontMetrics getFontMetrics(JComponent component, Font font) {
-        return SwingUtilities2.getFontMetrics(component, font);
-    }
-    
-    public Shape getRoundShape(int width, int height) {
-        return new RoundRectangle2D.Double(0, 0, width, height, 4, 4);
-    }
-    
-    public void setGradientPaint(Graphics2D g, int x, int height, Color start, Color stop) {
-        Paint paint = new GradientPaint(x, 0, start, 0, height, stop);
-        g.setPaint(paint);
-    }
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HtmlTextBuilder.java	Tue Dec 18 22:02:42 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/*
- * Copyright 2012 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;
-
-public class HtmlTextBuilder {
-
-    /*
-     * The api provided by this class needs to be cleaned up.
-     */
-
-    private final StringBuilder text = new StringBuilder();
-
-    public HtmlTextBuilder() {
-        // do nothing
-    }
-
-    public HtmlTextBuilder(String text) {
-        text = escape(text);
-        this.text.append(text);
-    }
-
-    public HtmlTextBuilder bold(boolean on) {
-        if (on) {
-            this.text.append("<b>");
-        } else {
-            this.text.append("</b>");
-        }
-        return this;
-    }
-
-    public HtmlTextBuilder bold(String toBold) {
-        text.append("<b>").append(toBold).append("</b>");
-        return this;
-    }
-
-    public HtmlTextBuilder larger(String toAppend) {
-        text.append("<font size='+2'>").append(escape(toAppend)).append("</font>");
-        return this;
-    }
-
-    public HtmlTextBuilder huge(String toAppend) {
-        text.append("<font size='+6'>").append(escape(toAppend)).append("</font>");
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        // FIXME
-        return null;
-    }
-
-    public String toHtml() {
-        return "<html>" + text.toString() + "</html>";
-    }
-
-    public String toPartialHtml() {
-        return text.toString();
-    }
-
-    private static String escape(String toEscape) {
-        // FIXME implement this
-        return toEscape;
-    }
-
-    public HtmlTextBuilder append(String toAppend) {
-        text.append(escape(toAppend));
-        return this;
-    }
-
-    public HtmlTextBuilder appendRaw(String toAppend) {
-        text.append(toAppend);
-        return this;
-    }
-
-    public static String boldHtml(String toBold) {
-        return new HtmlTextBuilder().bold(toBold).toHtml();
-    }
-
-    public HtmlTextBuilder newLine() {
-        text.append("<br>");
-        return this;
-    }
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/SectionHeader.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/SectionHeader.java	Tue Dec 18 16:39:38 2012 -0500
@@ -39,6 +39,8 @@
 import javax.swing.JLabel;
 import javax.swing.SwingConstants;
 
+import com.redhat.thermostat.client.swing.HtmlTextBuilder;
+
 /**
  * A {@link JLabel} that is appropriate to use as a label for grouping
  * the following information together.
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ShadowLabel.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ShadowLabel.java	Tue Dec 18 16:39:38 2012 -0500
@@ -43,6 +43,8 @@
 import javax.swing.JLabel;
 import javax.swing.plaf.metal.MetalLabelUI;
 
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+
 /**
  * A {@link JLabel} that has a shadow.
  */
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/StatusBar.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/StatusBar.java	Tue Dec 18 16:39:38 2012 -0500
@@ -125,22 +125,5 @@
     public String getPrimaryStatus() {
         return primaryStatus;
     }
-        
-    public static void main(String[] args) {
-        SwingUtilities.invokeLater(new Runnable() {
-            
-            @Override
-            public void run() {
-                JFrame frame = new JFrame();
-                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-                frame.getContentPane().setLayout(new BorderLayout());
-                
-                StatusBar statusBar = new StatusBar();
-                frame.getContentPane().add(statusBar, BorderLayout.SOUTH);
-                
-                frame.setSize(500, 500);
-                frame.setVisible(true);
-            }
-        });
-    }
+
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ToolbarButtonBorder.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ToolbarButtonBorder.java	Tue Dec 18 16:39:38 2012 -0500
@@ -47,6 +47,7 @@
 import javax.swing.ButtonModel;
 import javax.swing.plaf.UIResource;
 
+import com.redhat.thermostat.client.swing.GraphicsUtils;
 import com.redhat.thermostat.client.ui.Palette;
 
 @SuppressWarnings("serial")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,805 @@
+/*
+ * Copyright 2012 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.internal;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+import javax.swing.ToolTipManager;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.event.TreeWillExpandListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.ExpandVetoException;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+import sun.misc.Signal;
+
+import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.core.views.SearchFieldView.SearchAction;
+import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.osgi.service.VMContextAction;
+import com.redhat.thermostat.client.swing.EdtHelper;
+import com.redhat.thermostat.client.swing.HtmlTextBuilder;
+import com.redhat.thermostat.client.swing.MenuHelper;
+import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.StatusBar;
+import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
+import com.redhat.thermostat.client.swing.internal.components.DecoratedDefaultMutableTreeNode;
+import com.redhat.thermostat.client.swing.views.SearchFieldSwingView;
+import com.redhat.thermostat.client.ui.Decorator;
+import com.redhat.thermostat.client.ui.IconDescriptor;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.HostsVMsLoader;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.Ref;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.utils.StringUtils;
+
+public class MainWindow extends JFrame implements MainView {
+    
+    public static final String MAIN_WINDOW_NAME = "Thermostat_mainWindo_JFrame_parent#1";
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    /**
+     * Updates a TreeModel in the background in an Swing EDT-safe manner.
+     */
+    private static class BackgroundTreeModelWorker extends SwingWorker<DefaultMutableTreeNode, Void> {
+
+        private JTree tree;
+
+        private final DefaultTreeModel treeModel;
+        private DefaultMutableTreeNode treeRoot;
+        
+        private List<Filter<HostRef>> hostFilters;
+        private List<Filter<VmRef>> vmFilters;
+        private List<DecoratorProvider<HostRef>> hostDecorators;
+        private List<DecoratorProvider<VmRef>> vmDecorators;
+        
+        private HostsVMsLoader hostsVMsLoader;
+
+        public BackgroundTreeModelWorker(DefaultTreeModel model, DefaultMutableTreeNode root,
+                                         List<Filter<HostRef>> hostFilters, List<Filter<VmRef>> vmFilters,
+                                         List<DecoratorProvider<HostRef>> hostDecorators,
+                                         List<DecoratorProvider<VmRef>> vmDecorators,
+                                         HostsVMsLoader hostsVMsLoader, JTree tree)
+        {
+            this.hostFilters = hostFilters;
+            this.vmFilters = vmFilters;
+
+            this.vmDecorators = vmDecorators;
+            this.hostDecorators = hostDecorators;
+
+            this.treeModel = model;
+            this.treeRoot = root;
+            this.hostsVMsLoader = hostsVMsLoader;
+            this.tree = tree;
+        }
+
+        @Override
+        protected DefaultMutableTreeNode doInBackground() throws Exception {
+            DefaultMutableTreeNode root = new DefaultMutableTreeNode();
+            
+            Collection<HostRef> hostsInRemoteModel = hostsVMsLoader.getHosts();
+            buildHostSubTree(root, hostsInRemoteModel);
+            return root;
+        }
+
+        private boolean buildHostSubTree(DefaultMutableTreeNode parent, Collection<HostRef> objectsInRemoteModel) {
+            boolean subTreeMatches = false;
+            for (HostRef inRemoteModel : objectsInRemoteModel) {
+                DecoratedDefaultMutableTreeNode inTreeNode =
+                        new DecoratedDefaultMutableTreeNode(inRemoteModel);
+
+                boolean shouldInsert = false;
+                if (hostFilters == null) {
+                    shouldInsert = true;
+                } else {
+                    shouldInsert = true;
+                    for (Filter<HostRef> filter : hostFilters) {
+                        if (!filter.matches(inRemoteModel)) {
+                            shouldInsert = false;
+                            break;
+                        }
+                    }
+                }
+                
+                Collection<VmRef> children = hostsVMsLoader.getVMs(inRemoteModel);
+                boolean subtreeResult = buildVmSubTree(inTreeNode, children);
+                if (subtreeResult) {
+                    shouldInsert = true;
+                }
+
+                if (shouldInsert) {
+                    for (DecoratorProvider<HostRef> decorator : hostDecorators) {
+                        Filter<HostRef> filter = decorator.getFilter();
+                        if (filter != null && filter.matches(inRemoteModel)) {
+                            inTreeNode.addDecorator(decorator.getDecorator());
+                        }
+                    }
+                    
+                    parent.add(inTreeNode);
+                    subTreeMatches = true;
+                }
+            }
+            
+            return subTreeMatches;
+        }
+
+        private boolean buildVmSubTree(DefaultMutableTreeNode parent, Collection<VmRef> objectsInRemoteModel) {
+            boolean subTreeMatches = false;
+            for (VmRef inRemoteModel : objectsInRemoteModel) {
+                DecoratedDefaultMutableTreeNode inTreeNode =
+                        new DecoratedDefaultMutableTreeNode(inRemoteModel);
+
+                boolean shouldInsert = false;
+                if (vmFilters == null) {
+                    shouldInsert = true;
+                } else {
+                    shouldInsert = true;
+                    for (Filter<VmRef> filter : vmFilters) {
+                        if (!filter.matches(inRemoteModel)) {
+                            shouldInsert = false;
+                            break;
+                        }
+                    }
+                }
+
+                if (shouldInsert) {
+                    for (DecoratorProvider<VmRef> decorator : vmDecorators) {
+                        Filter<VmRef> filter = decorator.getFilter();
+                        if (filter != null && filter.matches(inRemoteModel)) {
+                            inTreeNode.addDecorator(decorator.getDecorator());
+                        }
+                    }
+
+                    parent.add(inTreeNode);
+                    subTreeMatches = true;
+                }
+            }
+
+            return subTreeMatches;
+        }
+
+        @Override
+        protected void done() {
+            DefaultMutableTreeNode sourceRoot;
+            try {
+                sourceRoot = get();
+                syncTree(sourceRoot, treeModel, treeRoot);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            } catch (ExecutionException e) {
+                e.printStackTrace();
+            }
+        }
+
+        private void syncTree(DefaultMutableTreeNode sourceRoot, DefaultTreeModel targetModel, DefaultMutableTreeNode targetNode) {
+            
+            @SuppressWarnings("unchecked") // We know what we put into these trees.
+            List<DefaultMutableTreeNode> sourceChildren = Collections.list(sourceRoot.children());
+
+            @SuppressWarnings("unchecked")
+            List<DefaultMutableTreeNode> targetChildren = Collections.list(targetNode.children());
+            for (DefaultMutableTreeNode sourceChild : sourceChildren) {
+                Ref sourceRef = (Ref) sourceChild.getUserObject();
+                DefaultMutableTreeNode targetChild = null;
+                for (DefaultMutableTreeNode aChild : targetChildren) {
+                    Ref targetRef = (Ref) aChild.getUserObject();
+                    if (targetRef.equals(sourceRef)) {
+                        targetChild = aChild;
+                        if (sourceChild instanceof DecoratedDefaultMutableTreeNode) {
+                            DecoratedDefaultMutableTreeNode source = (DecoratedDefaultMutableTreeNode) sourceChild;
+                            ((DecoratedDefaultMutableTreeNode) targetChild).setDecorators(source.getDecorators());
+                        }
+                        break;
+                    }
+                }
+
+                if (targetChild == null) {
+                    targetChild = new DecoratedDefaultMutableTreeNode(sourceRef);
+                    if (sourceChild instanceof DecoratedDefaultMutableTreeNode) {
+                        DecoratedDefaultMutableTreeNode source = (DecoratedDefaultMutableTreeNode) sourceChild;
+                        ((DecoratedDefaultMutableTreeNode) targetChild).setDecorators(source.getDecorators());
+                    }
+                    targetModel.insertNodeInto(targetChild, targetNode, targetNode.getChildCount());
+                }
+
+                syncTree(sourceChild, targetModel, targetChild);
+            }
+
+            for (DefaultMutableTreeNode targetChild : targetChildren) {
+                Ref targetRef = (Ref) targetChild.getUserObject();
+                boolean matchFound = false;
+                for (DefaultMutableTreeNode sourceChild : sourceChildren) {
+                    Ref sourceRef = (Ref) sourceChild.getUserObject();
+                    if (targetRef.equals(sourceRef)) {
+                        matchFound = true;
+                        break;
+                    }
+                }
+
+                if (!matchFound) {
+                    targetModel.removeNodeFromParent(targetChild);
+                }
+            }
+            ensureRootIsExpanded(targetModel);
+        }
+
+        private void ensureRootIsExpanded(final DefaultTreeModel model) {
+            DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
+            tree.expandPath(new TreePath(root.getPath()));
+        }
+
+    }
+
+    private static final long serialVersionUID = 5608972421496808177L;
+
+    private final JMenuBar mainMenuBar = new JMenuBar();
+    private final MenuHelper mainMenuHelper = new MenuHelper(mainMenuBar);
+    private JPanel contentArea = null;
+
+    private SearchFieldSwingView searchField = new SearchFieldSwingView();
+    private JTree agentVmTree = null;
+
+    private final ShutdownClient shutdownAction;
+
+    private ActionNotifier<Action> actionNotifier = new ActionNotifier<>(this);
+
+    private ThermostatPopupMenu vmContextMenu;
+    private StatusBar statusBar;
+    
+    private final DefaultMutableTreeNode publishedRoot =
+            new DefaultMutableTreeNode(translator.localize(LocaleResources.MAIN_WINDOW_TREE_ROOT_NAME));
+    private final DefaultTreeModel publishedTreeModel = new DefaultTreeModel(publishedRoot);
+
+    @SuppressWarnings("restriction")
+    public MainWindow() {
+        super();
+
+        setName(MAIN_WINDOW_NAME);
+        
+        shutdownAction = new ShutdownClient();
+
+        searchField.addActionListener(new ActionListener<SearchAction>() {
+            @Override
+            public void actionPerformed(ActionEvent<SearchAction> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case TEXT_CHANGED:
+                    fireViewAction(Action.HOST_VM_TREE_FILTER);
+                    break;
+                }
+            }
+        });
+        agentVmTree = new JTree(publishedTreeModel);
+        agentVmTree.setName("agentVmTree");
+        agentVmTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+        agentVmTree.setCellRenderer(new AgentVmTreeCellRenderer());
+        agentVmTree.addTreeWillExpandListener(new TreeWillExpandListener() {
+            @Override
+            public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
+                /* Yup, tree will expand */
+            }
+
+            @Override
+            public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
+                if (new TreePath(publishedRoot.getPath()).equals(event.getPath())) {
+                    throw new ExpandVetoException(event, "root cant be collapsed");
+                }
+            }
+        });
+        ToolTipManager.sharedInstance().registerComponent(agentVmTree);
+        contentArea = new JPanel(new BorderLayout());
+
+        setupMenus();
+        setupPanels();
+
+        this.setPreferredSize(new Dimension(800, 600));
+
+        agentVmTree.setSelectionPath(new TreePath(((DefaultMutableTreeNode) publishedTreeModel.getRoot()).getPath()));
+        
+        //agentVmTree.setLargeModel(true);
+        agentVmTree.setRowHeight(25);
+        
+        statusBar = new StatusBar();
+        getContentPane().add(statusBar, BorderLayout.SOUTH);
+        
+        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+        addWindowListener(shutdownAction);
+
+        // Handle SIGTERM/SIGINT properly
+        Signal.handle(new Signal("TERM"), shutdownAction);
+        Signal.handle(new Signal("INT"), shutdownAction);
+        
+        addComponentListener(new ComponentAdapter() {
+
+            @Override
+            public void componentShown(ComponentEvent e) {
+                fireViewAction(Action.VISIBLE);
+            }
+
+            @Override
+            public void componentHidden(ComponentEvent e) {
+                fireViewAction(Action.HIDDEN);
+            }
+        });
+
+    }
+
+    private void setupMenus() {
+
+        JMenu fileMenu = new JMenu(translator.localize(LocaleResources.MENU_FILE));
+        fileMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
+        mainMenuBar.add(fileMenu);
+
+        JMenuItem fileExitMenu = new JMenuItem(translator.localize(LocaleResources.MENU_FILE_EXIT));
+        fileExitMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK));
+        fileExitMenu.addActionListener(shutdownAction);
+        fileMenu.add(fileExitMenu);
+
+        JMenu editMenu = new JMenu(translator.localize(LocaleResources.MENU_EDIT));
+        mainMenuBar.add(editMenu);
+
+        JMenuItem configureClientMenuItem = new JMenuItem(translator.localize(LocaleResources.MENU_EDIT_CONFIGURE_CLIENT));
+        configureClientMenuItem.setName("showClientConfig");
+        configureClientMenuItem.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                fireViewAction(Action.SHOW_CLIENT_CONFIG);
+            }
+        });
+        editMenu.add(configureClientMenuItem);
+
+        editMenu.addSeparator();
+        JMenuItem historyModeMenuItem = new JCheckBoxMenuItem(translator.localize(LocaleResources.MENU_EDIT_ENABLE_HISTORY_MODE));
+        historyModeMenuItem.setName("historyModeSwitch");
+        historyModeMenuItem.setSelected(false);
+        historyModeMenuItem.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                fireViewAction(Action.SWITCH_HISTORY_MODE);
+            }
+        });
+        editMenu.add(historyModeMenuItem);
+
+        JMenu viewMenu = new JMenu(translator.localize(LocaleResources.MENU_VIEW));
+        mainMenuBar.add(viewMenu);
+        JMenuItem configureAgentMenuItem = new JMenuItem(translator.localize(LocaleResources.MENU_VIEW_AGENTS));
+        configureAgentMenuItem.setName("showAgentConfig");
+        configureAgentMenuItem.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                fireViewAction(Action.SHOW_AGENT_CONFIG);
+            }
+        });
+        viewMenu.add(configureAgentMenuItem);
+
+        JMenu helpMenu = new JMenu(translator.localize(LocaleResources.MENU_HELP));
+        helpMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
+        mainMenuBar.add(helpMenu);
+
+        JMenuItem helpAboutMenu = new JMenuItem(translator.localize(LocaleResources.MENU_HELP_ABOUT));
+        helpAboutMenu.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                fireViewAction(Action.SHOW_ABOUT_DIALOG);
+            }
+        });
+        helpMenu.add(helpAboutMenu);
+        setJMenuBar(mainMenuBar);
+    }
+
+    private void setupPanels() {
+        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
+        splitPane.setOneTouchExpandable(true);
+        
+        JPanel navigationPanel = new JPanel(new BorderLayout());
+
+        navigationPanel.add(searchField, BorderLayout.PAGE_START);
+
+        agentVmTree.addTreeSelectionListener(new TreeSelectionListener() {
+            @Override
+            public void valueChanged(TreeSelectionEvent e) {
+                if (e.isAddedPath()) {
+                    fireViewAction(Action.HOST_VM_SELECTION_CHANGED);
+                }
+            }
+        });
+        registerContextActionListener(agentVmTree);
+        
+        JScrollPane treeScrollPane = new JScrollPane(agentVmTree);
+        
+        navigationPanel.add(treeScrollPane);
+
+        JPanel detailsPanel = createDetailsPanel();
+
+        navigationPanel.setMinimumSize(new Dimension(200,500));
+        detailsPanel.setMinimumSize(new Dimension(500, 500));
+
+        splitPane.add(navigationPanel);
+        splitPane.add(detailsPanel);
+
+        getContentPane().add(splitPane);
+    }
+
+    private void registerContextActionListener(JTree agentVmTree2) {
+        vmContextMenu = new ThermostatPopupMenu();
+        agentVmTree2.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mousePressed(MouseEvent e) {
+                if (e.isPopupTrigger()) {
+                    Ref ref = getSelectedHostOrVm();
+                    if (ref instanceof VmRef) {
+                        fireViewAction(Action.SHOW_VM_CONTEXT_MENU, e);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void showVMContextActions(final List<VMContextAction> actions, final MouseEvent e) {
+        SwingUtilities.invokeLater(new Runnable() {
+
+            @Override
+            public void run() {
+                vmContextMenu.removeAll();
+
+                for (final VMContextAction action: actions) {
+                    JMenuItem contextAction = new JMenuItem();
+                    contextAction.setText(action.getName());
+                    contextAction.setToolTipText(action.getDescription());
+
+                    contextAction.addActionListener(new java.awt.event.ActionListener() {
+                        @Override
+                        public void actionPerformed(java.awt.event.ActionEvent e) {
+                            fireViewAction(Action.VM_CONTEXT_ACTION, action);
+                        }
+                    });
+                    vmContextMenu.add(contextAction);
+                }
+
+                vmContextMenu.show((Component)e.getSource(), e.getX(), e.getY());
+            }
+
+        });
+    }
+    
+    private JPanel createDetailsPanel() {
+        JPanel result = new JPanel(new BorderLayout());
+        result.add(contentArea, BorderLayout.CENTER);
+        return result;
+    }
+
+    @SuppressWarnings("restriction")
+    public class ShutdownClient extends WindowAdapter implements java.awt.event.ActionListener, sun.misc.SignalHandler {
+
+        @Override
+        public void windowClosing(WindowEvent e) {
+            shutdown();
+        }
+
+        @Override
+        public void actionPerformed(java.awt.event.ActionEvent e) {
+            shutdown();
+        }
+        
+        @Override
+        public void handle(Signal arg0) {
+            shutdown();
+        }
+
+        private void shutdown() {
+            dispose();
+            fireViewAction(Action.SHUTDOWN);
+        }
+
+    }
+
+    private static class AgentVmTreeCellRenderer extends DefaultTreeCellRenderer {
+        private static final long serialVersionUID = 4444642511815252481L;
+
+        @Override
+        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+            
+            Object node = ((DefaultMutableTreeNode) value).getUserObject();
+            setToolTipText(createToolTipText(node));
+            
+            Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
+            if (value instanceof DecoratedDefaultMutableTreeNode) {
+                DecoratedDefaultMutableTreeNode treeNode = (DecoratedDefaultMutableTreeNode) value;
+                setAnnotation(treeNode, node, component);
+            }
+
+            return component;
+        }
+        
+        // TODO: we can cache more, for example the full icon, not just the decoration
+        private Map<Decorator, ImageIcon> decoratorsCache = new HashMap<>();
+        private void setAnnotation(DecoratedDefaultMutableTreeNode treeNode, Object value, Component component) {
+
+            List<Decorator> decorators = treeNode.getDecorators();
+            for (Decorator dec : decorators) {
+                String newText = dec.getLabel(getText());
+                setText(newText);
+                setLabelFor(component);
+                
+                ImageIcon icon = decoratorsCache.get(dec);
+                if (icon == null) {
+                    //System.err.println("cache miss: " + dec);
+                    IconDescriptor iconDescriptor = dec.getIconDescriptor();
+                    if (iconDescriptor != null) {
+                        ByteBuffer data = iconDescriptor.getData();
+                        icon = new ImageIcon(data.array());
+                        decoratorsCache.put(dec, icon);
+                    }
+                }
+                
+                if (icon == null) {
+                    return;
+                }
+                
+                Icon currentIcon = getIcon();
+                switch (dec.getQuadrant()) {
+                case BOTTOM_LEFT:
+                    int y = currentIcon.getIconHeight() - icon.getIconHeight();
+                    paintCustomIcon(currentIcon, icon, y);
+                    break;
+                    
+                case TOP_LEFT:
+                    paintCustomIcon(currentIcon, icon, 0);
+                    break;
+                    
+                case MAIN:
+                default:
+                    setIcon(icon);
+                    break;
+                }
+            }
+        }
+        
+        private void paintCustomIcon(Icon currentIcon, ImageIcon icon, int y) {
+            BufferedImage image = new BufferedImage(currentIcon.getIconWidth(),
+                                                    currentIcon.getIconHeight(),
+                                                    BufferedImage.TYPE_INT_ARGB);
+            Graphics2D graphics = (Graphics2D) image.getGraphics();
+            
+            currentIcon.paintIcon(null, graphics, 0, 0);
+            graphics.drawImage(icon.getImage(), 0, y, null);
+            
+            setIcon(new ImageIcon(image));
+        }
+        
+        private String createToolTipText(Object value) {
+            if (value instanceof HostRef) {
+                HostRef hostRef = (HostRef) value;
+                String hostNameHtml = new HtmlTextBuilder().bold(hostRef.getHostName()).toPartialHtml();
+                String agentIdHtml = new HtmlTextBuilder().bold(hostRef.getAgentId()).toPartialHtml();
+                HtmlTextBuilder builder = new HtmlTextBuilder()
+                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_HOST_NAME, hostNameHtml))
+                    .newLine()
+                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_AGENT_ID, agentIdHtml));
+                return builder.toHtml();
+            } else if (value instanceof VmRef) {
+                VmRef vmRef = (VmRef) value;
+                String vmNameHtml= new HtmlTextBuilder().bold(vmRef.getName()).toPartialHtml();
+                String vmIdHtml = new HtmlTextBuilder().bold(vmRef.getIdString()).toPartialHtml();
+                HtmlTextBuilder builder = new HtmlTextBuilder()
+                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_VM_NAME, vmNameHtml))
+                    .newLine()
+                    .appendRaw(translator.localize(LocaleResources.TREE_HOST_TOOLTIP_VM_ID, vmIdHtml));
+                return builder.toHtml();
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Override
+    public JFrame getTopFrame() {
+        return this;
+    }
+    
+    @Override
+    public void addActionListener(ActionListener<Action> l) {
+        actionNotifier.addActionListener(l);
+    }
+
+    public void removeViewActionListener(ActionListener<Action> l) {
+        actionNotifier.removeActionListener(l);
+    }
+
+    private void fireViewAction(Action action) {
+        actionNotifier.fireAction(action);
+    }
+    
+    private void fireViewAction(Action action, Object payload) {
+        actionNotifier.fireAction(action, payload);
+    }
+    
+    @Override
+    public void updateTree(List<Filter<HostRef>> hostFilters, List<Filter<VmRef>> vmFilters,
+            List<DecoratorProvider<HostRef>> hostDecorators,
+            List<DecoratorProvider<VmRef>> vmDecorators,
+            HostsVMsLoader hostsVMsLoader)
+    {
+        BackgroundTreeModelWorker worker =
+                new BackgroundTreeModelWorker(publishedTreeModel, publishedRoot,
+                                              hostFilters, vmFilters, hostDecorators, vmDecorators, hostsVMsLoader, agentVmTree);
+        worker.execute();
+    }
+
+    @SuppressWarnings("unused") // Used for debugging but not in production code.
+    private static void printTree(PrintStream out, TreeNode node, int depth) {
+        out.println(StringUtils.repeat("  ", depth) + node.toString());
+        @SuppressWarnings("unchecked")
+        List<TreeNode> children = Collections.list(node.children());
+        for (TreeNode child : children) {
+            printTree(out, child, depth + 1);
+        }
+    }
+
+    @Override
+    public void setWindowTitle(String title) {
+        setTitle(title);
+    }
+
+    @Override
+    public void showMainWindow() {
+        try {
+            new EdtHelper().callAndWait(new Runnable() {
+
+                @Override
+                public void run() {
+                    pack();
+                    setVisible(true);
+                }
+            });
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(e);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    @Override
+    public void hideMainWindow() {
+        setVisible(false);
+        dispose();
+    }
+
+    @Override
+    public void setStatusBarPrimaryStatus(final String primaryStatus) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                statusBar.setPrimaryStatus(primaryStatus);
+            }
+        });
+    }
+    
+    @Override
+    public void setSubView(final BasicView view) {
+        if (view instanceof SwingComponent) {
+            final SwingComponent swingComp = (SwingComponent)view;
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    contentArea.removeAll();
+                    Component toAdd = swingComp.getUiComponent();
+                    contentArea.add(toAdd);
+                    contentArea.revalidate();
+                }
+            });
+        }
+    }
+
+    @Override
+    public void addMenu(MenuAction action) {
+        mainMenuHelper.addMenuAction(action);
+    }
+
+    @Override
+    public void removeMenu(MenuAction action) {
+        mainMenuHelper.removeMenuAction(action);
+    }
+
+    /**
+     * Returns null to indicate no Ref is selected
+     */
+    @Override
+    public Ref getSelectedHostOrVm() {
+        TreePath path = agentVmTree.getSelectionPath();
+        if (path == null || path.getPathCount() == 1) {
+            return null;
+        }
+        return (Ref) ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
+    }
+
+    @Override
+    public String getHostVmTreeFilterText() {
+        return searchField.getSearchText();
+    }
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Tue Dec 18 16:39:38 2012 -0500
@@ -48,7 +48,6 @@
 import com.redhat.thermostat.client.core.views.SummaryViewProvider;
 import com.redhat.thermostat.client.core.views.VmInformationViewProvider;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
-import com.redhat.thermostat.client.swing.MainWindow;
 import com.redhat.thermostat.client.ui.HostInformationController;
 import com.redhat.thermostat.client.ui.MainWindowController;
 import com.redhat.thermostat.client.ui.SummaryController;
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/views/ClientConfigurationSwing.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/views/ClientConfigurationSwing.java	Tue Dec 18 16:39:38 2012 -0500
@@ -52,7 +52,7 @@
 
 import com.redhat.thermostat.client.core.views.ClientConfigurationView;
 import com.redhat.thermostat.client.locale.LocaleResources;
-import com.redhat.thermostat.client.swing.components.EdtHelper;
+import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.locale.Translate;
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/views/SearchFieldSwingView.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/views/SearchFieldSwingView.java	Tue Dec 18 16:39:38 2012 -0500
@@ -59,8 +59,8 @@
 
 import com.redhat.thermostat.client.core.views.SearchFieldView;
 import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.client.swing.IconResource;
-import com.redhat.thermostat.client.swing.components.EdtHelper;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.locale.Translate;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/EdtHelperTest.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2012 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;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.EventQueue;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.Callable;
+
+import javax.swing.SwingUtilities;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.client.swing.EdtHelper;
+
+public class EdtHelperTest {
+
+    private class ExceptionCallable implements Callable<Object>  {
+
+        @Override
+        public Object call() throws Exception {
+            throw new Exception("fluff");
+        }
+        
+    }
+
+    private class ResultCallable implements Callable<Object> {
+    
+        private Object result;
+        private ResultCallable(Object r) {
+            result = r;
+        }
+        @Override
+        public Object call() throws Exception {
+            // By waiting here, we make sure the EDTHelper actually waits for the call.
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            if (EventQueue.isDispatchThread()) {
+                calledOnEDT = true;
+            }
+            return result;
+        }
+        
+    }
+
+    private class TestRunnable implements Runnable {
+
+        @Override
+        public void run() {
+            // By waiting here, we make sure the EDTHelper actually waits for the call.
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            if (EventQueue.isDispatchThread()) {
+                calledOnEDT = true;
+            }
+        }
+        
+    }
+
+    private volatile boolean calledOnEDT;
+
+    @Before
+    public void setUp() {
+        calledOnEDT = false;
+    }
+
+    @After
+    public void tearDown() {
+        calledOnEDT = false;
+    }
+
+    @Test
+    public void testCallRunnableFromNonEDT() throws InvocationTargetException, InterruptedException {
+        Runnable r = new TestRunnable();
+        new EdtHelper().callAndWait(r);
+        assertTrue(calledOnEDT);
+    }
+
+    @Test
+    public void testCallRunnableFromEDT() throws InvocationTargetException, InterruptedException {
+        final Runnable r = new TestRunnable();
+        SwingUtilities.invokeAndWait(new Runnable() {
+            
+            @Override
+            public void run() {
+                try {
+                    new EdtHelper().callAndWait(r);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        });
+        assertTrue(calledOnEDT);
+    }
+
+    @Test
+    public void testCallCallableFromNoEDT() throws InvocationTargetException, InterruptedException {
+        final Object expected = new Object();
+        Callable<Object> c = new ResultCallable(expected);
+        Object result = new EdtHelper().callAndWait(c);
+        assertTrue(calledOnEDT);
+        assertSame(expected, result);
+    }
+
+    @Test
+    public void testCallCallableFromEDT() throws InvocationTargetException, InterruptedException {
+        final Object expected = new Object();
+        final Callable<Object> c = new ResultCallable(expected);
+        final Object[] result = new Object[1];
+        SwingUtilities.invokeAndWait(new Runnable() {
+            
+            @Override
+            public void run() {
+                try {
+                    result[0] = new EdtHelper().callAndWait(c);
+                } catch (InvocationTargetException | InterruptedException e) {
+                    throw new RuntimeException();
+                }
+            }
+        });
+        assertTrue(calledOnEDT);
+        assertSame(expected, result[0]);
+    }
+
+    @Test(expected=InvocationTargetException.class)
+    public void testCallCallableFromNoEDTThrowingException() throws InvocationTargetException, InterruptedException {
+        Callable<Object> c = new ExceptionCallable();
+        new EdtHelper().callAndWait(c);
+    }
+
+    @Test
+    public void testCallCallableFromEDTThrowingException() throws InvocationTargetException, InterruptedException {
+        final boolean[] exceptionThrown = new boolean[1];
+        final Callable<Object> c = new ExceptionCallable();
+        SwingUtilities.invokeAndWait(new Runnable() {
+            
+            @Override
+            public void run() {
+                try {
+                    new EdtHelper().callAndWait(c);
+                } catch (InvocationTargetException e) {
+                    exceptionThrown[0] = true;
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        });
+        assertTrue(exceptionThrown[0]);
+    }
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/MainWindowTest.java	Tue Dec 18 22:02:42 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,356 +0,0 @@
-/*
- * Copyright 2012 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;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JRadioButtonMenuItem;
-
-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.exception.ComponentLookupException;
-import org.fest.swing.fixture.FrameFixture;
-import org.fest.swing.fixture.JMenuItemFixture;
-import org.fest.swing.fixture.JTextComponentFixture;
-import org.fest.swing.fixture.JTreeFixture;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-
-import com.redhat.thermostat.client.core.Filter;
-import com.redhat.thermostat.client.core.views.SearchFieldView;
-import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
-import com.redhat.thermostat.client.osgi.service.MenuAction;
-import com.redhat.thermostat.client.swing.internal.MainView;
-import com.redhat.thermostat.client.ui.Decorator;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.HostsVMsLoader;
-import com.redhat.thermostat.common.dao.HostRef;
-
-@RunWith(CacioFESTRunner.class)
-public class MainWindowTest {
-
-    private FrameFixture frameFixture;
-    private MainWindow window;
-    private ActionListener<MainView.Action> l;
-
-    @BeforeClass
-    public static void setUpOnce() {
-        FailOnThreadViolationRepaintManager.install();
-    }
-
-    @SuppressWarnings("unchecked") // mock(ActionListener.class)
-    @Before
-    public void setUp() {
-
-        GuiActionRunner.execute(new GuiTask() {
-            
-            @Override
-            protected void executeInEDT() throws Throwable {
-                window = new MainWindow();
-                l = mock(ActionListener.class);
-                window.addActionListener(l);
-            }
-        });
-
-        frameFixture = new FrameFixture(window);
-    }
-
-    @After
-    public void tearDown() {
-        frameFixture.cleanUp();
-        frameFixture = null;
-        window = null;
-        l = null;
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void testHostVmSelectionChangedSupport() {
-        frameFixture.show();
-        JTreeFixture hostVMTree = frameFixture.tree("agentVmTree");
-        hostVMTree.selectRows(0);
-
-        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.HOST_VM_SELECTION_CHANGED));
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void testHostVmDecoratorsAdded() throws InterruptedException {
-        
-        List<DecoratorProvider<HostRef>> decorators = new ArrayList<>();
-        @SuppressWarnings("unchecked")
-        DecoratorProvider<HostRef> refDecorator = mock(DecoratorProvider.class);
-        final Decorator decorator = mock(Decorator.class);
-        when(decorator.getLabel(anyString())).thenReturn("fluff");
-        
-        when(refDecorator.getDecorator()).thenReturn(decorator);
-        
-        @SuppressWarnings("unchecked")
-        Filter<HostRef> filter = mock(Filter.class);
-        when(filter.matches(isA(HostRef.class))).thenReturn(false).thenReturn(true);
-
-        when(refDecorator.getFilter()).thenReturn(filter);
-        
-        decorators.add(refDecorator);
-        
-        HostsVMsLoader hostsVMsLoader = mock(HostsVMsLoader.class);
-        Collection<HostRef> expectedHosts = new ArrayList<>();
-        expectedHosts.add(new HostRef("123", "fluffhost1"));
-        expectedHosts.add(new HostRef("456", "fluffhost2"));
-        
-        when(hostsVMsLoader.getHosts()).thenReturn(expectedHosts);
-        
-        window.updateTree(null, null, decorators, null, hostsVMsLoader);
-
-        Thread.sleep(50);
-        
-        frameFixture.show();
-        frameFixture.requireVisible();
-        
-        verify(decorator, times(0)).getLabel("fluffhost1");
-        verify(decorator, atLeastOnce()).getLabel("fluffhost2");
-    }
-    
-    @Category(GUITest.class)
-    @Test
-    public void testHostVMTreeFilterPropertySupport() {
-        String SEARCH_TEXT = "test";
-        frameFixture.show();
-        JTextComponentFixture hostVMTreeFilterField = frameFixture.textBox(SearchFieldView.VIEW_NAME);
-        hostVMTreeFilterField.enterText(SEARCH_TEXT);
-
-        verify(l, times(SEARCH_TEXT.length())).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.HOST_VM_TREE_FILTER));
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void verifyThatCloseFiresShutdownEvent() {
-
-        frameFixture.show();
-
-        frameFixture.close();
-        frameFixture.requireNotVisible();
-        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHUTDOWN));
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void verifyShowMainWindowShowsWindow() {
-        window.showMainWindow();
-        frameFixture.requireVisible();
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void verifyHideMainWindowHidesWindow() {
-        GuiActionRunner.execute(new GuiTask() {
-            @Override
-            protected void executeInEDT() throws Throwable {
-                window.showMainWindow();
-            }
-        });
-        frameFixture.requireVisible();
-        GuiActionRunner.execute(new GuiTask() {
-            @Override
-            protected void executeInEDT() throws Throwable {
-                window.hideMainWindow();
-            }
-        });
-        frameFixture.requireNotVisible();
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void verifyThatClientPreferencesMenuItemTriggersEvent() {
-        frameFixture.show();
-        JMenuItemFixture menuItem = frameFixture.menuItem("showClientConfig");
-        menuItem.click();
-        frameFixture.close();
-        frameFixture.requireNotVisible();
-
-        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHOW_CLIENT_CONFIG));
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void verifyThatAgentPreferencesMenuItemTriggersEvent() {
-        frameFixture.show();
-        JMenuItemFixture menuItem = frameFixture.menuItem("showAgentConfig");
-        menuItem.click();
-        frameFixture.close();
-        frameFixture.requireNotVisible();
-
-        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHOW_AGENT_CONFIG));
-    }
-
-
-    @Category(GUITest.class)
-    @Test
-    public void verifyThatHistorySwitchTriggersEvent() {
-        frameFixture.show();
-        JMenuItemFixture menuItem = frameFixture.menuItem("historyModeSwitch");
-        menuItem.click();
-        frameFixture.close();
-        frameFixture.requireNotVisible();
-
-        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SWITCH_HISTORY_MODE));
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void addRemoveMenu() {
-    	final String PARENT_NAME = "File";
-        final String MENU_NAME = "Test2";
-        MenuAction action = mock(MenuAction.class);
-        when(action.getName()).thenReturn(MENU_NAME);
-        when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME});
-        when(action.getType()).thenReturn(MenuAction.Type.STANDARD);
-
-        JMenuItemFixture menuItem;
-
-        frameFixture.show();
-
-        window.addMenu(action);
-
-        menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
-        assertNotNull(menuItem);
-        menuItem.click();
-
-        verify(action).execute();
-
-        window.removeMenu(action);
-
-        try {
-            menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
-            // should not reach here
-            assertTrue(false);
-        } catch (ComponentLookupException cle) {
-            // expected
-        }
-    }
-    
-    @Category(GUITest.class)
-    @Test
-    public void addRadioMenu() {
-    	final String PARENT_NAME = "File";
-        final String MENU_NAME = "Test";
-        MenuAction action = mock(MenuAction.class);
-        when(action.getName()).thenReturn(MENU_NAME);
-        when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME});
-
-
-        when(action.getType()).thenReturn(MenuAction.Type.RADIO);
-
-        JMenuItemFixture menuItem;
-
-        frameFixture.show();
-
-        window.addMenu(action);
-
-        menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
-        assertNotNull(menuItem);
-
-        assertTrue(menuItem.target instanceof JRadioButtonMenuItem);
-    }
-    
-    @Category(GUITest.class)
-    @Test
-    public void addCheckBoxMenu() {
-    	final String PARENT_NAME = "File";
-        final String MENU_NAME = "Test";
-        MenuAction action = mock(MenuAction.class);
-        when(action.getName()).thenReturn(MENU_NAME);
-        when(action.getType()).thenReturn(MenuAction.Type.CHECK);
-        when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME});
-
-
-        JMenuItemFixture menuItem;
-
-        frameFixture.show();
-
-        window.addMenu(action);
-
-        menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
-        assertNotNull(menuItem);
-
-        assertTrue(menuItem.target instanceof JCheckBoxMenuItem);
-    }
-    
-    @Category(GUITest.class)
-    @Test
-    public void testGetHostVMTreeFilter() {
-        frameFixture.show();
-        JTextComponentFixture hostVMTreeFilterField = frameFixture.textBox(SearchFieldView.VIEW_NAME);
-        hostVMTreeFilterField.enterText("test");
-        String actual = window.getHostVmTreeFilterText();
-        assertEquals("test", actual);
-    }
-
-    @Category(GUITest.class)
-    @Test
-    public void testGetSelectedHostOrVm() {
-        frameFixture.show();
-        JTreeFixture hostVMTree = frameFixture.tree("agentVmTree");
-        hostVMTree.selectRow(0);
-
-        assertEquals(null, window.getSelectedHostOrVm());
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012 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.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.Icon;
+import javax.swing.JFrame;
+
+import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+
+import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JButtonFixture;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(CacioFESTRunner.class)
+public class ActionButtonTest {
+
+    private FrameFixture frameFixture;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+    @After
+    public void tearDown() {
+        if (frameFixture != null) {
+            frameFixture.cleanUp();
+        }
+    }
+
+    @Test
+    public void testActionButton() {
+        JFrame frame = GuiActionRunner.execute(new GuiQuery<JFrame>() {
+            @Override
+            protected JFrame executeInEDT() throws Throwable {
+
+                JFrame frame = new JFrame();
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+                HeaderPanel header = new HeaderPanel();
+                header.setHeader("Test");
+
+                Icon icon = new Icon() {
+
+                    @Override
+                    public void paintIcon(Component c, Graphics g, int x, int y) {
+                        g.setColor(Color.CYAN);
+                        g.fillRect(x, y, 16, 16);
+
+                    }
+
+                    @Override
+                    public int getIconWidth() {
+                        // TODO Auto-generated method stub
+                        return 16;
+                    }
+
+                    @Override
+                    public int getIconHeight() {
+                        // TODO Auto-generated method stub
+                        return 16;
+                    }
+                };
+
+                ActionButton button = new ActionButton(icon, "Fluff");
+                button.setName("button");
+                header.addToolBarButton(button);
+
+                frame.getContentPane().add(header);
+                frame.setSize(500, 500);
+                frame.setVisible(true);
+                return frame;
+            }
+        });
+        frameFixture = new FrameFixture(frame);
+        JButtonFixture actionButton = frameFixture.button("button");
+        actionButton.requireText("");
+        actionButton.requireEnabled();
+
+    }
+
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/EdtHelperTest.java	Tue Dec 18 22:02:42 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- * Copyright 2012 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 static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.awt.EventQueue;
-import java.lang.reflect.InvocationTargetException;
-import java.util.concurrent.Callable;
-
-import javax.swing.SwingUtilities;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class EdtHelperTest {
-
-    private class ExceptionCallable implements Callable<Object>  {
-
-        @Override
-        public Object call() throws Exception {
-            throw new Exception("fluff");
-        }
-        
-    }
-
-    private class ResultCallable implements Callable<Object> {
-    
-        private Object result;
-        private ResultCallable(Object r) {
-            result = r;
-        }
-        @Override
-        public Object call() throws Exception {
-            // By waiting here, we make sure the EDTHelper actually waits for the call.
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-            if (EventQueue.isDispatchThread()) {
-                calledOnEDT = true;
-            }
-            return result;
-        }
-        
-    }
-
-    private class TestRunnable implements Runnable {
-
-        @Override
-        public void run() {
-            // By waiting here, we make sure the EDTHelper actually waits for the call.
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
-            if (EventQueue.isDispatchThread()) {
-                calledOnEDT = true;
-            }
-        }
-        
-    }
-
-    private volatile boolean calledOnEDT;
-
-    @Before
-    public void setUp() {
-        calledOnEDT = false;
-    }
-
-    @After
-    public void tearDown() {
-        calledOnEDT = false;
-    }
-
-    @Test
-    public void testCallRunnableFromNonEDT() throws InvocationTargetException, InterruptedException {
-        Runnable r = new TestRunnable();
-        new EdtHelper().callAndWait(r);
-        assertTrue(calledOnEDT);
-    }
-
-    @Test
-    public void testCallRunnableFromEDT() throws InvocationTargetException, InterruptedException {
-        final Runnable r = new TestRunnable();
-        SwingUtilities.invokeAndWait(new Runnable() {
-            
-            @Override
-            public void run() {
-                try {
-                    new EdtHelper().callAndWait(r);
-                } catch (InvocationTargetException e) {
-                    throw new RuntimeException(e);
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                }
-            }
-        });
-        assertTrue(calledOnEDT);
-    }
-
-    @Test
-    public void testCallCallableFromNoEDT() throws InvocationTargetException, InterruptedException {
-        final Object expected = new Object();
-        Callable<Object> c = new ResultCallable(expected);
-        Object result = new EdtHelper().callAndWait(c);
-        assertTrue(calledOnEDT);
-        assertSame(expected, result);
-    }
-
-    @Test
-    public void testCallCallableFromEDT() throws InvocationTargetException, InterruptedException {
-        final Object expected = new Object();
-        final Callable<Object> c = new ResultCallable(expected);
-        final Object[] result = new Object[1];
-        SwingUtilities.invokeAndWait(new Runnable() {
-            
-            @Override
-            public void run() {
-                try {
-                    result[0] = new EdtHelper().callAndWait(c);
-                } catch (InvocationTargetException | InterruptedException e) {
-                    throw new RuntimeException();
-                }
-            }
-        });
-        assertTrue(calledOnEDT);
-        assertSame(expected, result[0]);
-    }
-
-    @Test(expected=InvocationTargetException.class)
-    public void testCallCallableFromNoEDTThrowingException() throws InvocationTargetException, InterruptedException {
-        Callable<Object> c = new ExceptionCallable();
-        new EdtHelper().callAndWait(c);
-    }
-
-    @Test
-    public void testCallCallableFromEDTThrowingException() throws InvocationTargetException, InterruptedException {
-        final boolean[] exceptionThrown = new boolean[1];
-        final Callable<Object> c = new ExceptionCallable();
-        SwingUtilities.invokeAndWait(new Runnable() {
-            
-            @Override
-            public void run() {
-                try {
-                    new EdtHelper().callAndWait(c);
-                } catch (InvocationTargetException e) {
-                    exceptionThrown[0] = true;
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                }
-            }
-        });
-        assertTrue(exceptionThrown[0]);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowTest.java	Tue Dec 18 16:39:38 2012 -0500
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2012 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.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JRadioButtonMenuItem;
+
+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.exception.ComponentLookupException;
+import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JMenuItemFixture;
+import org.fest.swing.fixture.JTextComponentFixture;
+import org.fest.swing.fixture.JTreeFixture;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.client.core.views.SearchFieldView;
+import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.swing.internal.MainView;
+import com.redhat.thermostat.client.swing.internal.MainWindow;
+import com.redhat.thermostat.client.ui.Decorator;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.HostsVMsLoader;
+import com.redhat.thermostat.common.dao.HostRef;
+
+@RunWith(CacioFESTRunner.class)
+public class MainWindowTest {
+
+    private FrameFixture frameFixture;
+    private MainWindow window;
+    private ActionListener<MainView.Action> l;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+    @SuppressWarnings("unchecked") // mock(ActionListener.class)
+    @Before
+    public void setUp() {
+
+        GuiActionRunner.execute(new GuiTask() {
+            
+            @Override
+            protected void executeInEDT() throws Throwable {
+                window = new MainWindow();
+                l = mock(ActionListener.class);
+                window.addActionListener(l);
+            }
+        });
+
+        frameFixture = new FrameFixture(window);
+    }
+
+    @After
+    public void tearDown() {
+        frameFixture.cleanUp();
+        frameFixture = null;
+        window = null;
+        l = null;
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void testHostVmSelectionChangedSupport() {
+        frameFixture.show();
+        JTreeFixture hostVMTree = frameFixture.tree("agentVmTree");
+        hostVMTree.selectRows(0);
+
+        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.HOST_VM_SELECTION_CHANGED));
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void testHostVmDecoratorsAdded() throws InterruptedException {
+        
+        List<DecoratorProvider<HostRef>> decorators = new ArrayList<>();
+        @SuppressWarnings("unchecked")
+        DecoratorProvider<HostRef> refDecorator = mock(DecoratorProvider.class);
+        final Decorator decorator = mock(Decorator.class);
+        when(decorator.getLabel(anyString())).thenReturn("fluff");
+        
+        when(refDecorator.getDecorator()).thenReturn(decorator);
+        
+        @SuppressWarnings("unchecked")
+        Filter<HostRef> filter = mock(Filter.class);
+        when(filter.matches(isA(HostRef.class))).thenReturn(false).thenReturn(true);
+
+        when(refDecorator.getFilter()).thenReturn(filter);
+        
+        decorators.add(refDecorator);
+        
+        HostsVMsLoader hostsVMsLoader = mock(HostsVMsLoader.class);
+        Collection<HostRef> expectedHosts = new ArrayList<>();
+        expectedHosts.add(new HostRef("123", "fluffhost1"));
+        expectedHosts.add(new HostRef("456", "fluffhost2"));
+        
+        when(hostsVMsLoader.getHosts()).thenReturn(expectedHosts);
+        
+        window.updateTree(null, null, decorators, null, hostsVMsLoader);
+
+        Thread.sleep(50);
+        
+        frameFixture.show();
+        frameFixture.requireVisible();
+        
+        verify(decorator, times(0)).getLabel("fluffhost1");
+        verify(decorator, atLeastOnce()).getLabel("fluffhost2");
+    }
+    
+    @Category(GUITest.class)
+    @Test
+    public void testHostVMTreeFilterPropertySupport() {
+        String SEARCH_TEXT = "test";
+        frameFixture.show();
+        JTextComponentFixture hostVMTreeFilterField = frameFixture.textBox(SearchFieldView.VIEW_NAME);
+        hostVMTreeFilterField.enterText(SEARCH_TEXT);
+
+        verify(l, times(SEARCH_TEXT.length())).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.HOST_VM_TREE_FILTER));
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void verifyThatCloseFiresShutdownEvent() {
+
+        frameFixture.show();
+
+        frameFixture.close();
+        frameFixture.requireNotVisible();
+        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHUTDOWN));
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void verifyShowMainWindowShowsWindow() {
+        window.showMainWindow();
+        frameFixture.requireVisible();
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void verifyHideMainWindowHidesWindow() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                window.showMainWindow();
+            }
+        });
+        frameFixture.requireVisible();
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                window.hideMainWindow();
+            }
+        });
+        frameFixture.requireNotVisible();
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void verifyThatClientPreferencesMenuItemTriggersEvent() {
+        frameFixture.show();
+        JMenuItemFixture menuItem = frameFixture.menuItem("showClientConfig");
+        menuItem.click();
+        frameFixture.close();
+        frameFixture.requireNotVisible();
+
+        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHOW_CLIENT_CONFIG));
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void verifyThatAgentPreferencesMenuItemTriggersEvent() {
+        frameFixture.show();
+        JMenuItemFixture menuItem = frameFixture.menuItem("showAgentConfig");
+        menuItem.click();
+        frameFixture.close();
+        frameFixture.requireNotVisible();
+
+        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHOW_AGENT_CONFIG));
+    }
+
+
+    @Category(GUITest.class)
+    @Test
+    public void verifyThatHistorySwitchTriggersEvent() {
+        frameFixture.show();
+        JMenuItemFixture menuItem = frameFixture.menuItem("historyModeSwitch");
+        menuItem.click();
+        frameFixture.close();
+        frameFixture.requireNotVisible();
+
+        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SWITCH_HISTORY_MODE));
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void addRemoveMenu() {
+    	final String PARENT_NAME = "File";
+        final String MENU_NAME = "Test2";
+        MenuAction action = mock(MenuAction.class);
+        when(action.getName()).thenReturn(MENU_NAME);
+        when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME});
+        when(action.getType()).thenReturn(MenuAction.Type.STANDARD);
+
+        JMenuItemFixture menuItem;
+
+        frameFixture.show();
+
+        window.addMenu(action);
+
+        menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
+        assertNotNull(menuItem);
+        menuItem.click();
+
+        verify(action).execute();
+
+        window.removeMenu(action);
+
+        try {
+            menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
+            // should not reach here
+            assertTrue(false);
+        } catch (ComponentLookupException cle) {
+            // expected
+        }
+    }
+    
+    @Category(GUITest.class)
+    @Test
+    public void addRadioMenu() {
+    	final String PARENT_NAME = "File";
+        final String MENU_NAME = "Test";
+        MenuAction action = mock(MenuAction.class);
+        when(action.getName()).thenReturn(MENU_NAME);
+        when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME});
+
+
+        when(action.getType()).thenReturn(MenuAction.Type.RADIO);
+
+        JMenuItemFixture menuItem;
+
+        frameFixture.show();
+
+        window.addMenu(action);
+
+        menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
+        assertNotNull(menuItem);
+
+        assertTrue(menuItem.target instanceof JRadioButtonMenuItem);
+    }
+    
+    @Category(GUITest.class)
+    @Test
+    public void addCheckBoxMenu() {
+    	final String PARENT_NAME = "File";
+        final String MENU_NAME = "Test";
+        MenuAction action = mock(MenuAction.class);
+        when(action.getName()).thenReturn(MENU_NAME);
+        when(action.getType()).thenReturn(MenuAction.Type.CHECK);
+        when(action.getPath()).thenReturn(new String[] {PARENT_NAME, MENU_NAME});
+
+
+        JMenuItemFixture menuItem;
+
+        frameFixture.show();
+
+        window.addMenu(action);
+
+        menuItem = frameFixture.menuItemWithPath(PARENT_NAME, MENU_NAME);
+        assertNotNull(menuItem);
+
+        assertTrue(menuItem.target instanceof JCheckBoxMenuItem);
+    }
+    
+    @Category(GUITest.class)
+    @Test
+    public void testGetHostVMTreeFilter() {
+        frameFixture.show();
+        JTextComponentFixture hostVMTreeFilterField = frameFixture.textBox(SearchFieldView.VIEW_NAME);
+        hostVMTreeFilterField.enterText("test");
+        String actual = window.getHostVmTreeFilterText();
+        assertEquals("test", actual);
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void testGetSelectedHostOrVm() {
+        frameFixture.show();
+        JTreeFixture hostVMTree = frameFixture.tree("agentVmTree");
+        hostVMTree.selectRow(0);
+
+        assertEquals(null, window.getSelectedHostOrVm());
+    }
+
+}
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java	Tue Dec 18 16:39:38 2012 -0500
@@ -57,8 +57,8 @@
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
 
+import com.redhat.thermostat.client.swing.GraphicsUtils;
 import com.redhat.thermostat.client.swing.components.GradientRoundBorder;
-import com.redhat.thermostat.client.swing.components.GraphicsUtils;
 import com.redhat.thermostat.client.ui.Palette;
 import com.redhat.thermostat.common.model.LongRange;
 import com.redhat.thermostat.common.model.LongRangeNormalizer;
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadTimelineLegendPanel.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadTimelineLegendPanel.java	Tue Dec 18 16:39:38 2012 -0500
@@ -50,7 +50,7 @@
 import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
 
-import com.redhat.thermostat.client.swing.components.GraphicsUtils;
+import com.redhat.thermostat.client.swing.GraphicsUtils;
 import com.redhat.thermostat.thread.client.common.chart.ChartColors;
 
 @SuppressWarnings("serial")
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectDetailsPanel.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectDetailsPanel.java	Tue Dec 18 16:39:38 2012 -0500
@@ -55,8 +55,8 @@
 import javax.swing.JLabel;
 
 import com.redhat.thermostat.client.core.views.SearchFieldView.SearchAction;
+import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.client.swing.SwingComponent;
-import com.redhat.thermostat.client.swing.components.EdtHelper;
 import com.redhat.thermostat.client.swing.views.SearchFieldSwingView;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionEvent;
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectRootsFrame.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectRootsFrame.java	Tue Dec 18 16:39:38 2012 -0500
@@ -58,7 +58,7 @@
 import javax.swing.tree.MutableTreeNode;
 import javax.swing.tree.TreePath;
 
-import com.redhat.thermostat.client.swing.components.EdtHelper;
+import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.locale.Translate;
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapDetailsSwingTest.java	Tue Dec 18 22:02:42 2012 +0100
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapDetailsSwingTest.java	Tue Dec 18 16:39:38 2012 -0500
@@ -60,7 +60,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import com.redhat.thermostat.client.swing.components.EdtHelper;
+import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.HeapDetailsSwing;
 import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.HistogramPanel;
 import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.ObjectDetailsPanel;