Mercurial > hg > release > thermostat-0.15
changeset 1207:27c09390b3ce
Progress Notification (first part)
review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007622.html
reviwed-by: jerboaa
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]); + } +}