changeset 1207:27c09390b3ce

Progress Notification (first part) review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007622.html reviwed-by: jerboaa
author Mario Torre <neugens.limasoftware@gmail.com>
date Mon, 05 Aug 2013 11:56:19 +0200
parents 804a5aa7d565
children 3d2faeefba5f
files client/core/pom.xml client/core/src/main/java/com/redhat/thermostat/client/core/progress/ProgressHandle.java client/core/src/main/java/com/redhat/thermostat/client/core/progress/ProgressNotifier.java client/core/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java client/core/src/main/resources/com/redhat/thermostat/client/locale/strings.properties client/core/src/test/java/com/redhat/thermostat/client/core/progress/ProgressHandleTest.java client/swing/pom.xml client/swing/src/main/java/com/redhat/thermostat/client/swing/components/OverlayPanel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ShadowLabel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.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/MainWindowControllerImpl.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/AggregateNotificationPanel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/AggregateProgressBarOverlayLayout.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/AggregateProgressComponent.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/ProgressNotificationArea.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/SwingProgressNotifier.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/ThermostatGlassPane.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/progress/SwingProgressNotifierTest.java
diffstat 20 files changed, 1080 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/client/core/pom.xml	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/core/pom.xml	Mon Aug 05 11:56:19 2013 +0200
@@ -123,6 +123,7 @@
               com.redhat.thermostat.client.locale,
               <!-- Interfaces for views, controllers, etc. -->
               com.redhat.thermostat.client.core,
+              com.redhat.thermostat.client.core.progress,
               com.redhat.thermostat.client.core.controllers,
               com.redhat.thermostat.client.core.views
             </Export-Package>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/progress/ProgressHandle.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.core.progress;
+
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ */
+public class ProgressHandle {
+
+    public enum Status {
+        STARTED,
+        STOPPED,
+    }
+    
+    private UUID id;
+    
+    private final ActionNotifier<ProgressHandle.Status> notifier;
+    
+    private String name;
+    private boolean indeterminate;
+    
+    public ProgressHandle(String name) {
+        id = UUID.randomUUID();
+        this.name = name;
+        notifier = new ActionNotifier<>(this);
+    }
+
+    public void setIndeterminate(boolean indeterminate) {
+        this.indeterminate = indeterminate;
+    }
+
+    public boolean isIndeterminate() {
+        return indeterminate;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void start() {
+        notifier.fireAction(Status.STARTED);
+    }
+    
+    public void stop() {
+        notifier.fireAction(Status.STOPPED);
+    }
+        
+    public void addProgressListener(ActionListener<ProgressHandle.Status> listener) {
+        notifier.addActionListener(listener);
+    }
+    
+    public void removeProgressListener(ActionListener<ProgressHandle.Status> listener) {
+        notifier.removeActionListener(listener);
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    @Override
+    public final int hashCode() {
+        int hash = 5;
+        hash = 53 * hash + Objects.hashCode(this.id);
+        return hash;
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ProgressHandle other = (ProgressHandle) obj;
+        if (!Objects.equals(this.id, other.id)) {
+            return false;
+        }
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/progress/ProgressNotifier.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.core.progress;
+
+/**
+ */
+public interface ProgressNotifier {
+    void register(ProgressHandle handle);
+    boolean hasTasks();
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Mon Aug 05 11:56:19 2013 +0200
@@ -90,6 +90,8 @@
     ABOUT_DIALOG_EMAIL,
     ABOUT_DIALOG_WEBSITE,
 
+    PROGRESS_NOTIFICATION_AREA_TITLE,
+    
     HOME_PANEL_SECTION_SUMMARY,
     HOME_PANEL_TOTAL_MACHINES,
     HOME_PANEL_TOTAL_JVMS,
--- a/client/core/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/core/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Mon Aug 05 11:56:19 2013 +0200
@@ -78,3 +78,5 @@
 CLIENT_PREFS_STORAGE_USERNAME = User Name
 CLIENT_PREFS_STORAGE_PASSWORD = Password
 CLIENT_PREFS_STORAGE_SAVE_ENTITLEMENTS = Save Entitlements
+
+PROGRESS_NOTIFICATION_AREA_TITLE = Running Tasks...
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/test/java/com/redhat/thermostat/client/core/progress/ProgressHandleTest.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.core.progress;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+
+public class ProgressHandleTest {
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testProgressHandle() {
+        ActionListener listener = mock(ActionListener.class);
+        
+        ProgressHandle handle = new ProgressHandle("test #1");
+        handle.addProgressListener(listener);
+
+        ArgumentCaptor<ActionEvent> captor =
+                ArgumentCaptor.forClass(ActionEvent.class);        
+        
+        handle.start();
+        
+        verify(listener).actionPerformed(captor.capture());
+        ActionEvent event = captor.getValue();
+        assertEquals(ProgressHandle.Status.STARTED, event.getActionId());
+        
+        handle.stop();
+
+        verify(listener, times(2)).actionPerformed(captor.capture());
+        
+        event = captor.getValue();
+        assertEquals(ProgressHandle.Status.STOPPED, event.getActionId());
+    }
+}
--- a/client/swing/pom.xml	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/pom.xml	Mon Aug 05 11:56:19 2013 +0200
@@ -170,6 +170,7 @@
             </Export-Package>
             <Private-Package>
               com.redhat.thermostat.client.swing.internal,
+              com.redhat.thermostat.client.swing.internal.progress,
               com.redhat.thermostat.client.swing.internal.components,
               com.redhat.thermostat.client.swing.internal.osgi,
               com.redhat.thermostat.client.swing.internal.views,
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/OverlayPanel.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/OverlayPanel.java	Mon Aug 05 11:56:19 2013 +0200
@@ -157,12 +157,12 @@
 
         Graphics2D graphics = utils.createAAGraphics(g);
         graphics.setColor(utils.deriveWithAlpha(Palette.PALE_GRAY.getColor(), 200));
- 
+        
         Rectangle clip = graphics.getClipBounds();
         Insets borderInsets = getBorder().getBorderInsets(this);
         
-        clip.height = clip.height - borderInsets.bottom - borderInsets.top;
-        clip.width = clip.width - borderInsets.left - borderInsets.right;
+        clip.height = getHeight() - borderInsets.bottom - borderInsets.top;
+        clip.width = getWidth() - borderInsets.left - borderInsets.right;
         
         graphics.translate(borderInsets.left, borderInsets.top);
         graphics.fillRect(clip.x, clip.y, clip.width, clip.height);
@@ -227,7 +227,6 @@
         public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
             if (DEBUG) {
                 super.paintBorder(c, g, x, y, width, height);
-                return;
             }
             
             if (buffer != null && buffer.getWidth() == getWidth() && buffer.getHeight() == getHeight()) {
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ShadowLabel.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ShadowLabel.java	Mon Aug 05 11:56:19 2013 +0200
@@ -52,8 +52,8 @@
 @SuppressWarnings("serial")
 public class ShadowLabel extends JLabel {
 
-    public ShadowLabel(LocalizedString text, Icon icon) {
-        super(text.getContents());
+    private ShadowLabel(String text, Icon icon) {
+        super(text);
         this.setIcon(icon);
         setUI(new ShadowLabelUI());
     }
@@ -62,6 +62,14 @@
         this(text, null);
     }
 
+    public ShadowLabel(LocalizedString text, Icon icon) {
+        this(text.getContents(), icon);
+    }
+    
+    public ShadowLabel() {
+        this("", null);
+    }
+    
     private class ShadowLabelUI extends MetalLabelUI {
         
         @Override
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Mon Aug 05 11:56:19 2013 +0200
@@ -42,6 +42,7 @@
 import javax.swing.JFrame;
 
 import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.client.core.progress.ProgressNotifier;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.ui.ContextAction;
 import com.redhat.thermostat.client.ui.DecoratorProvider;
@@ -97,11 +98,16 @@
     void addMenu(MenuAction action);
 
     /**
+     * Returns the progress notifier associate with this view.
+     */
+    ProgressNotifier getNotifier();
+    
+    /**
      * Removes a menu item to the window. Assumes the menu path is valid (has a
      * non-zero length) and the menu already exists.
      */
     void removeMenu(MenuAction action);
-
+    
     /**
      * Shows a popup context menu created from the list of supplied context
      * actions. When an item in the popup menu is selected, an
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Mon Aug 05 11:56:19 2013 +0200
@@ -93,13 +93,21 @@
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.swing.ComponentVisibleListener;
 import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.client.swing.MenuHelper;
 import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.OverlayPanel;
 import com.redhat.thermostat.client.swing.components.SearchField;
 import com.redhat.thermostat.client.swing.components.SearchField.SearchAction;
 import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
 import com.redhat.thermostat.client.swing.internal.components.DecoratedDefaultMutableTreeNode;
+import com.redhat.thermostat.client.swing.internal.progress.AggregateNotificationPanel;
+import com.redhat.thermostat.client.swing.internal.progress.AggregateProgressBarOverlayLayout;
+import com.redhat.thermostat.client.swing.internal.progress.ProgressNotificationArea;
+import com.redhat.thermostat.client.swing.internal.progress.SwingProgressNotifier;
+import com.redhat.thermostat.client.swing.internal.progress.SwingProgressNotifier.PropertyChange;
+import com.redhat.thermostat.client.swing.internal.progress.ThermostatGlassPane;
 import com.redhat.thermostat.client.ui.ContextAction;
 import com.redhat.thermostat.client.ui.Decorator;
 import com.redhat.thermostat.client.ui.DecoratorProvider;
@@ -116,6 +124,7 @@
 import com.redhat.thermostat.storage.core.Ref;
 import com.redhat.thermostat.storage.core.VmRef;
 
+@SuppressWarnings("restriction")
 public class MainWindow extends JFrame implements MainView {
     
     public static final String MAIN_WINDOW_NAME = "Thermostat_mainWindo_JFrame_parent#1";
@@ -314,6 +323,8 @@
 
     }
 
+    private SwingProgressNotifier notifier;
+    
     private static final long serialVersionUID = 5608972421496808177L;
 
     private final JMenuBar mainMenuBar = new JMenuBar();
@@ -381,8 +392,13 @@
         
         //agentVmTree.setLargeModel(true);
         agentVmTree.setRowHeight(25);
+
+        statusBar = new StatusBar();
         
-        statusBar = new StatusBar();
+        ThermostatGlassPane glassPane = new ThermostatGlassPane();
+        setGlassPane(glassPane);
+        setupNotificationPane(statusBar, glassPane);
+        
         getContentPane().add(statusBar, BorderLayout.SOUTH);
         
         setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
@@ -404,9 +420,68 @@
                 fireViewAction(Action.HIDDEN);
             }
         });
-
     }
 
+    private void setupNotificationPane(StatusBar statusBar, final ThermostatGlassPane glassPane) {
+
+        AggregateNotificationPanel aggregateNotificationArea = new AggregateNotificationPanel();
+        
+        ProgressNotificationArea notificationArea = new ProgressNotificationArea();
+        notifier = new SwingProgressNotifier(aggregateNotificationArea, notificationArea, glassPane);
+
+        statusBar.add(notificationArea, BorderLayout.CENTER);
+
+        glassPane.setLayout(new AggregateProgressBarOverlayLayout());
+        LocalizedString title = translator.localize(LocaleResources.PROGRESS_NOTIFICATION_AREA_TITLE);
+        final OverlayPanel overlay = new OverlayPanel(title, false);
+        glassPane.add(overlay);
+
+        glassPane.addHierarchyListener(new ComponentVisibleListener() {
+            @Override
+            public void componentShown(Component component) {
+                overlay.setOverlayVisible(true);
+            }
+
+            @Override
+            public void componentHidden(Component component) {
+                overlay.setOverlayVisible(false);
+            }
+        });
+
+        overlay.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                glassPane.setVisible(false);
+            }
+        });
+
+        overlay.add(aggregateNotificationArea, BorderLayout.CENTER);
+        notifier.addPropertyChangeListener(new com.redhat.thermostat.common.
+                                           ActionListener<SwingProgressNotifier.PropertyChange>()
+        {
+            @Override
+            public void actionPerformed(com.redhat.thermostat.common.
+                                        ActionEvent<PropertyChange> actionEvent)
+            {
+                glassPane.setVisible(false);
+            }
+        });
+        
+        statusBar.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                if (notifier.hasTasks()) {
+                    glassPane.setVisible(!glassPane.isVisible());
+                }
+            }
+        });
+    }
+    
+    @Override
+    public SwingProgressNotifier getNotifier() {
+        return notifier;
+    }
+    
     private void setupMenus() {
 
         JMenu fileMenu = new JMenu(translator.localize(LocaleResources.MENU_FILE).getContents());
@@ -553,7 +628,6 @@
         return result;
     }
 
-    @SuppressWarnings("restriction")
     public class ShutdownClient extends WindowAdapter implements java.awt.event.ActionListener, sun.misc.SignalHandler {
 
         @Override
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Mon Aug 05 11:56:19 2013 +0200
@@ -54,6 +54,7 @@
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.client.core.InformationService;
 import com.redhat.thermostat.client.core.NameMatchingRefFilter;
+import com.redhat.thermostat.client.core.progress.ProgressNotifier;
 import com.redhat.thermostat.client.core.views.AgentInformationDisplayView;
 import com.redhat.thermostat.client.core.views.AgentInformationViewProvider;
 import com.redhat.thermostat.client.core.views.ClientConfigViewProvider;
@@ -119,7 +120,7 @@
 
     private MainView view;
     private Keyring keyring;
-
+    
     private HostInfoDAO hostInfoDAO;
     private VmInfoDAO vmInfoDAO;
     private AgentInfoDAO agentInfoDAO;
@@ -199,7 +200,7 @@
         this(context, appSvc, new MainWindow(), new RegistryFactory(context), shutdown);
     }
 
-    MainWindowControllerImpl(BundleContext context, ApplicationService appSvc,
+    MainWindowControllerImpl(final BundleContext context, ApplicationService appSvc,
             final MainView view,
             RegistryFactory registryFactory,
             final CountDownLatch shutdown) {
@@ -280,6 +281,8 @@
                 updateView();
 
                 installListenersAndStartRegistries();
+                
+                registerProgressNotificator(context);
             }
 
             @Override
@@ -355,7 +358,7 @@
         view.updateTree(hostFilters, vmFilters, hostTreeDecorators, vmTreeDecorators, loader);
     }
 
-    private void initView() {
+    private void initView() {        
         view.setWindowTitle(appInfo.getName());
         view.addActionListener(new ActionListener<MainView.Action>() {
 
@@ -442,6 +445,11 @@
         vmInfoRegistry.start();
     }
 
+    private void registerProgressNotificator(BundleContext context) {
+        ProgressNotifier notifier = view.getNotifier();
+        context.registerService(ProgressNotifier.class, notifier, null);
+    }
+    
     private void uninstallListenersAndStopRegistries() {
         menuRegistry.removeActionListener(menuListener);
         menuListener = null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/AggregateNotificationPanel.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import com.redhat.thermostat.client.swing.components.ThermostatThinScrollBar;
+
+import java.awt.BorderLayout;
+
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+@SuppressWarnings("serial")
+public class AggregateNotificationPanel extends JPanel {
+
+    private JPanel progressBarPane;
+    
+    public AggregateNotificationPanel() {
+        this.setLayout(new BorderLayout());
+        
+        progressBarPane = new JPanel();
+        BoxLayout layout = new BoxLayout(progressBarPane, BoxLayout.Y_AXIS);
+        progressBarPane.setLayout(layout);
+        
+        JScrollPane scrollPane = new JScrollPane(progressBarPane);
+
+        scrollPane.setVerticalScrollBar(new ThermostatThinScrollBar(ThermostatThinScrollBar.VERTICAL));
+        scrollPane.setHorizontalScrollBar(new ThermostatThinScrollBar(ThermostatThinScrollBar.HORIZONTAL));
+
+        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        
+        scrollPane.setBorder(null);
+        scrollPane.setViewportBorder(null);
+        scrollPane.getViewport().setOpaque(false);
+
+        add(scrollPane);        
+    }
+    
+    public void addProgress(AggregateProgressComponent progress) {
+        progressBarPane.add(progress);
+        revalidate();
+        repaint();
+    }
+
+    void removeProgress(AggregateProgressComponent progressBar) {
+        progressBarPane.remove(progressBar);
+        revalidate();
+        repaint();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/AggregateProgressBarOverlayLayout.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+
+import com.redhat.thermostat.client.swing.components.AbstractLayout;
+import com.redhat.thermostat.client.swing.components.OverlayPanel;
+
+public class AggregateProgressBarOverlayLayout extends AbstractLayout {
+
+    @Override
+    protected void doLayout(Container parent) {
+        Component[] children = parent.getComponents();
+        for (Component _child : children) {
+            if (!(_child instanceof OverlayPanel)) {
+                continue;
+            }
+
+            OverlayPanel child = (OverlayPanel) _child;
+            if (!child.isVisible()) {
+                continue;
+            }
+            
+            // limit the size to some reasonable default so that
+            // the panel doesn't go offscreen if it grows too much
+            Dimension preferredSize = child.getPreferredSize();
+            if (preferredSize.height > 300) {
+                preferredSize.height = 300;
+            }
+            
+            // FIXME: the magic number is referred to StatusBar grip icon
+            // size. There's no way we can access it at the moment, so we
+            // just rely on the fact that we know it's a 16x16 icon...
+            // We should probably set this information somewhere so that
+            // the layout will still work in the unlikely case this icon should
+            // ever change
+            int x = parent.getWidth() - preferredSize.width +
+                    child.getInsets().left - 16 + 2;
+            int y = parent.getHeight() - preferredSize.height + 16;
+            Rectangle bounds =
+                    new Rectangle(x, y, preferredSize.width, preferredSize.height);
+
+            child.setSize(preferredSize);
+            child.setBounds(bounds);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/AggregateProgressComponent.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import java.awt.BasicStroke;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import com.redhat.thermostat.client.core.progress.ProgressHandle;
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.swing.components.DebugBorder;
+import com.redhat.thermostat.client.swing.components.GradientPanel;
+import com.redhat.thermostat.client.swing.components.ShadowLabel;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+
+@SuppressWarnings("serial")
+public class AggregateProgressComponent extends GradientPanel {
+    
+    public AggregateProgressComponent(ProgressHandle handle) {
+        
+        super(Palette.WHITE.getColor(), Palette.PALE_GRAY.getColor());
+        
+        setBorder(new AggregateProgressComponentBorder());
+        
+        JPanel panel = new JPanel(new GridLayout());
+        panel.setOpaque(false);
+        add(panel);
+        
+        ShadowLabel text = new ShadowLabel(new LocalizedString(handle.getName()));
+        panel.add(text);
+
+        JProgressBar progressBar = new JProgressBar();
+        progressBar.setString(handle.getName());
+        progressBar.setStringPainted(true);
+      
+        progressBar.setIndeterminate(handle.isIndeterminate());
+        panel.add(progressBar);
+    }
+    
+    private static class AggregateProgressComponentBorder extends DebugBorder {
+        @Override
+        public void paintBorder(Component c, Graphics g, int x, int y,
+                                int width, int height)
+        {
+            GraphicsUtils utils = GraphicsUtils.getInstance();
+            Graphics2D graphics = utils.createAAGraphics(g);
+            
+            graphics.setColor(utils.deriveWithAlpha(Palette.EGYPTIAN_BLUE.getColor(), 120));
+            
+            graphics.setStroke(new BasicStroke(1.2f));
+            graphics.drawLine(x, y, x + width - 1, y);
+            graphics.dispose();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/ProgressNotificationArea.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.SwingConstants;
+
+import com.redhat.thermostat.client.core.progress.ProgressHandle;
+import com.redhat.thermostat.client.swing.components.FontAwesomeIcon;
+import com.redhat.thermostat.client.swing.components.Icon;
+import com.redhat.thermostat.client.swing.components.ShadowLabel;
+
+@SuppressWarnings("serial")
+public class ProgressNotificationArea extends JPanel {
+    
+    private ProgressHandle runningTask;
+    private ShadowLabel taskLabel;
+    private Icon moreTasksIcon;
+
+    private boolean hasMore;
+    
+    public ProgressNotificationArea() {
+        setLayout(new BorderLayout(0, 0));
+        
+        taskLabel = new ShadowLabel();
+        taskLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+        taskLabel.setVerticalAlignment(SwingConstants.CENTER);
+        
+        moreTasksIcon = new FontAwesomeIcon('\uf0d8', 12);
+    }
+
+    public void setRunningTask(final ProgressHandle handle) {
+        removeAll();
+        
+        taskLabel.setText(handle.getName());
+        if (hasMore) {
+            taskLabel.setIcon(moreTasksIcon);
+        } else {
+            taskLabel.setIcon(null);
+        }
+        
+        add(taskLabel, BorderLayout.CENTER);
+        
+        JProgressBar progressBar = new JProgressBar();
+        progressBar.setIndeterminate(handle.isIndeterminate());
+        add(progressBar, BorderLayout.EAST);
+
+        progressBar.setName(handle.getName());
+        
+        runningTask = handle;
+        
+        revalidate();
+        repaint();
+    }
+    
+    public void setHasMore(boolean hasMore) {
+        this.hasMore = hasMore;
+        if (hasMore) {
+            taskLabel.setIcon(moreTasksIcon);
+        } else {
+            taskLabel.setIcon(null);
+        }
+    }
+    
+    public ProgressHandle getRunningTask() {
+        return runningTask;
+    }
+
+    public void reset() {
+        removeAll();
+        revalidate();
+        repaint();
+        runningTask = null;
+        hasMore = false;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/SwingProgressNotifier.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import com.redhat.thermostat.client.core.progress.ProgressHandle;
+import com.redhat.thermostat.client.core.progress.ProgressNotifier;
+import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.swing.SwingUtilities;
+
+public class SwingProgressNotifier implements ProgressNotifier, SwingComponent {
+
+    public enum PropertyChange {
+        ALL_PROGRESS_DONE,
+    }
+    
+    // for tests, so that code run synchronously
+    private boolean runInEDT;
+    
+    private final ActionNotifier<SwingProgressNotifier.PropertyChange> notifier;
+
+    public void addPropertyChangeListener(ActionListener<SwingProgressNotifier.PropertyChange> listener) {
+        notifier.addActionListener(listener);
+    }
+    
+    public void removePropertyChangeListener(ActionListener<SwingProgressNotifier.PropertyChange> listener) {
+        notifier.removeActionListener(listener);
+    }
+    
+    private Map<ProgressHandle, AggregateProgressComponent> tasks;
+    
+    private ProgressNotificationArea notificationArea;
+    private AggregateNotificationPanel aggregateNotificationArea;
+    
+    public SwingProgressNotifier(AggregateNotificationPanel aggregateNotificationArea,
+                                 ProgressNotificationArea notificationArea,
+                                 ThermostatGlassPane glassPane)
+    {
+        this(aggregateNotificationArea, notificationArea, glassPane, true);
+    }
+    
+    SwingProgressNotifier(AggregateNotificationPanel aggregateNotificationArea,
+                          ProgressNotificationArea notificationArea,
+                          ThermostatGlassPane glassPane, boolean runInEDT)
+    {
+        this.notificationArea = notificationArea;
+        this.aggregateNotificationArea = aggregateNotificationArea;
+        tasks = new ConcurrentHashMap<>();
+
+        notifier = new ActionNotifier<>(this);
+        
+        this.runInEDT = runInEDT;
+    }
+    
+    private void handleTask(ActionEvent<ProgressHandle.Status> status, ProgressHandle handle) {
+        switch (status.getActionId()) {
+        case STARTED: {
+            AggregateProgressComponent progressBar = new AggregateProgressComponent(handle);
+            aggregateNotificationArea.addProgress(progressBar);
+            tasks.put(handle, progressBar);
+            
+            notificationArea.setRunningTask(handle);
+            
+            checkTasksNumber();
+            
+        } break;
+
+        case STOPPED: {
+            AggregateProgressComponent progressBar = tasks.remove(handle);
+            aggregateNotificationArea.removeProgress(progressBar);
+            
+            if (tasks.isEmpty()) {
+                notificationArea.reset();
+                notifier.fireAction(PropertyChange.ALL_PROGRESS_DONE);
+            } else {
+                // pick another task, only if this is the task that was just
+                // removed
+                if (handle.equals(notificationArea.getRunningTask())) {
+                   for (ProgressHandle newHandle : tasks.keySet()) {
+                       notificationArea.setRunningTask(newHandle);
+                       break;
+                   }
+                }
+                checkTasksNumber();
+            }
+            
+        } break;
+            
+        default:
+            throw new UnsupportedOperationException("Case not implemented");
+        }
+    }
+    
+    private void checkTasksNumber() {
+        if (tasks.size() > 1) {
+            notificationArea.setHasMore(true);
+        } else {
+            notificationArea.setHasMore(false);
+        }
+    }
+    
+    @Override
+    public void register(final ProgressHandle handle) {
+        
+        handle.addProgressListener(new ActionListener<ProgressHandle.Status>() {
+            @Override
+            public void actionPerformed(final ActionEvent<ProgressHandle.Status> status) {
+                if (runInEDT) {
+                    SwingUtilities.invokeLater(new Runnable() {
+                        @Override
+                        public void run() {
+                            handleTask(status, handle);
+                        }
+                    });
+                } else {
+                    handleTask(status, handle);
+                }
+            }
+        });
+    }
+
+    @Override
+    public AggregateNotificationPanel getUiComponent() {
+        return aggregateNotificationArea;
+    }
+
+    @Override
+    public boolean hasTasks() {
+        return !tasks.isEmpty();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/progress/ThermostatGlassPane.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import javax.swing.JPanel;
+
+@SuppressWarnings("serial")
+public class ThermostatGlassPane extends JPanel {
+
+    public ThermostatGlassPane() {
+        setOpaque(false);
+    }
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Mon Aug 05 11:56:18 2013 +0200
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Mon Aug 05 11:56:19 2013 +0200
@@ -65,6 +65,7 @@
 import org.osgi.framework.BundleException;
 
 import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.client.core.progress.ProgressNotifier;
 import com.redhat.thermostat.client.core.views.AgentInformationViewProvider;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.ClientConfigViewProvider;
@@ -194,6 +195,9 @@
         ArgumentCaptor<ActionListener> grabListener = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(view).addActionListener(grabListener.capture());
         
+        ProgressNotifier notifier = mock(ProgressNotifier.class);
+        when(view.getNotifier()).thenReturn(notifier);
+        
         RegistryFactory registryFactory = mock(RegistryFactory.class);
         hostFilterRegistry = mock(HostFilterRegistry.class);
         vmFilterRegistry = mock(VmFilterRegistry.class);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/progress/SwingProgressNotifierTest.java	Mon Aug 05 11:56:19 2013 +0200
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.progress;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertTrue;
+
+import javax.swing.RepaintManager;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.core.progress.ProgressHandle;
+import com.redhat.thermostat.client.core.progress.ProgressHandle.Status;
+import com.redhat.thermostat.client.core.progress.ProgressNotifier;
+import com.redhat.thermostat.client.swing.internal.progress.SwingProgressNotifier.PropertyChange;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+
+public class SwingProgressNotifierTest {
+
+    private AggregateNotificationPanel aggregateNotificationArea;
+    private ProgressNotificationArea notificationArea;
+    private ThermostatGlassPane glassPane;
+    
+    @BeforeClass
+    public void setUpOnce() {
+        // This is needed because some other test may have installed the
+        // EDT violation checker repaint manager.
+        // We don't need this check here, since we are not testing Swing
+        // code but only the functionality of the notifier
+        RepaintManager.setCurrentManager(new RepaintManager());
+    }
+    
+    @Before
+    public void setUp() {
+        aggregateNotificationArea = mock(AggregateNotificationPanel.class);
+        notificationArea = mock(ProgressNotificationArea.class);
+        glassPane = mock(ThermostatGlassPane.class);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void testNotifier() throws InterruptedException {
+        ProgressNotifier notifier =
+                new SwingProgressNotifier(aggregateNotificationArea,
+                                          notificationArea, glassPane, false);
+        
+        ProgressHandle handle = mock(ProgressHandle.class);
+        notifier.register(handle);
+        
+        final boolean [] result = new boolean[1];
+        ((SwingProgressNotifier) notifier).addPropertyChangeListener(new ActionListener<SwingProgressNotifier.PropertyChange>() {
+            @Override
+            public void actionPerformed(ActionEvent<PropertyChange> actionEvent) {
+                result[0] = true;
+            }
+        });
+        
+        ArgumentCaptor<ActionListener> captor =
+                ArgumentCaptor.forClass(ActionListener.class);
+        verify(handle).addProgressListener(captor.capture());
+        
+        ActionListener listener = captor.getValue();
+        
+        ActionEvent<ProgressHandle.Status> event =
+                new ActionEvent<ProgressHandle.Status>(handle, Status.STARTED);
+        listener.actionPerformed(event);
+        
+        ArgumentCaptor<AggregateProgressComponent> notificationAreaCaptor =
+                ArgumentCaptor.forClass(AggregateProgressComponent.class);
+        verify(aggregateNotificationArea).addProgress(notificationAreaCaptor.capture());
+        verify(notificationArea).setRunningTask(handle);
+        verify(notificationArea).setHasMore(false);
+        
+        assertTrue(notifier.hasTasks());
+        
+        AggregateProgressComponent aggregateComponent = notificationAreaCaptor.getValue();
+        
+        event = new ActionEvent<ProgressHandle.Status>(handle, Status.STOPPED);
+        listener.actionPerformed(event);
+        
+        verify(aggregateNotificationArea).removeProgress(aggregateComponent);
+        verify(notificationArea).reset();
+        verify(notificationArea).setHasMore(false);
+        
+        assertTrue(result[0]);
+    }
+}