changeset 1832:ffd37dfffbdd

Redesign ActionToggleButton states. Use in Profiling, Threads, Notifications Reviewed-by: neugens, jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-September/016403.html http://icedtea.classpath.org/pipermail/thermostat/2015-October/016608.html PR2671
author Andrew Azores <aazores@redhat.com>
date Tue, 13 Oct 2015 12:48:05 -0400
parents 747a32443e86
children 2649ea55de87
files client/core/src/main/java/com/redhat/thermostat/client/core/ToggleActionState.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButtonUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButton.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButtonUI.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadView.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadMainPanel.java thread/client-swing/src/test/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadViewTest.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/JmxNotificationsView.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewController.java vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewControllerTest.java vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResources.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileController.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileView.java vm-profiler/client-swing/src/main/resources/com/redhat/thermostat/vm/profiler/client/swing/internal/strings.properties vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileControllerTest.java
diffstat 20 files changed, 556 insertions(+), 202 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/ToggleActionState.java	Tue Oct 13 12:48:05 2015 -0400
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.core;
+
+public interface ToggleActionState {
+    boolean isTransitionState();
+    boolean isActionEnabled();
+    boolean isButtonEnabled();
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButtonUI.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionButtonUI.java	Tue Oct 13 12:48:05 2015 -0400
@@ -59,13 +59,13 @@
 
 class ActionButtonUI extends MetalButtonUI {
 
-    private BufferedImage sourceIcon;
-    private BufferedImage rollOverIcon;
-    private Image disabledIcon;
+    protected BufferedImage sourceIcon;
+    protected BufferedImage rollOverIcon;
+    protected Image disabledIcon;
     
     ActionButtonUI() {}
     
-    private BufferedImage getBrighterImage(BufferedImage source) {
+    protected BufferedImage getBrighterImage(BufferedImage source) {
         float[] kernel = new float[] { 0, 0, 0, 0, 1.2f, 0, 0, 0, 0 };
 
         BufferedImageOp brighterOp = new ConvolveOp(new Kernel(3, 3, kernel),
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButton.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButton.java	Tue Oct 13 12:48:05 2015 -0400
@@ -40,6 +40,7 @@
 import javax.swing.Icon;
 import javax.swing.JToggleButton;
 
+import com.redhat.thermostat.client.core.ToggleActionState;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 
 @SuppressWarnings("serial")
@@ -47,17 +48,27 @@
         
     private String lastText;
     private boolean showText;
+    private ActionToggleButtonUI buttonUI;
+
     public ActionToggleButton(final Icon icon) {
         this(icon, LocalizedString.EMPTY_STRING);
     }
     
     public ActionToggleButton(final Icon icon, LocalizedString text) {
-        super(icon);
-                
+        this(icon, icon, text);
+    }
+
+    public ActionToggleButton(final Icon defaultStateIcon, final Icon selectedStateIcon, LocalizedString text) {
+        super();
+
+        setIcon(defaultStateIcon);
+        setSelectedIcon(selectedStateIcon);
+
         showText = true;
         setText(text.getContents());
-        
-        setUI(new ActionButtonUI());
+
+        buttonUI = new ActionToggleButtonUI();
+        setUI(buttonUI);
         setOpaque(false);
         setContentAreaFilled(false);
         setBorder(new ToolbarButtonBorder(this));
@@ -88,6 +99,14 @@
         } else {
             setText_noClient("");
         }
-    }    
+    }
+
+    public void setToggleActionState(ToggleActionState toggleActionState) {
+        setEnabled(!toggleActionState.isTransitionState() && toggleActionState.isButtonEnabled());
+        setSelected(toggleActionState.isActionEnabled());
+        buttonUI.setState(toggleActionState);
+        repaint();
+    }
+
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButtonUI.java	Tue Oct 13 12:48:05 2015 -0400
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components;
+
+import com.redhat.thermostat.client.core.ToggleActionState;
+
+import javax.swing.AbstractButton;
+import javax.swing.ButtonModel;
+import javax.swing.GrayFilter;
+import javax.swing.JComponent;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+
+/**
+ * An ActionButtonUI which is intended specifically for "togglebuttons", which
+ * are defined to have exactly 4 possible states: conceptually, they are
+ * STARTING, STARTED, STOPPING, and STOPPED. The initial state is STOPPED.
+ * Each state is represented by a pairing of "button enabled" and
+ * "button selected" states:
+ *
+ * STOPPED: enabled, not selected
+ * STARTING: disabled, selected
+ * STARTED: enabled, selected
+ * STOPPING: disabled, not selected
+ *
+ * @see ActionToggleButton
+ * @see ToggleActionState
+ */
+public class ActionToggleButtonUI extends ActionButtonUI {
+
+    protected BufferedImage selectedIcon;
+    protected Image rolledOverSelectedIcon;
+    protected Image disabledSelectedIcon;
+    private ToggleActionState toggleActionState;
+
+    @Override
+    protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) {
+        AbstractButton button = (AbstractButton) c;
+        ButtonModel model = button.getModel();
+
+        javax.swing.Icon icon = button.getIcon();
+        int w = icon.getIconWidth();
+        int h = icon.getIconHeight();
+
+        if (sourceIcon == null) {
+            sourceIcon = new BufferedImage(w + 1, h + 1,
+                    BufferedImage.TYPE_INT_ARGB);
+            Graphics imageGraphics = sourceIcon.getGraphics();
+            icon.paintIcon(null, imageGraphics, 0, 0);
+        }
+
+        if (rollOverIcon == null) {
+            rollOverIcon = getBrighterImage(sourceIcon);
+        }
+
+        if (disabledIcon == null) {
+            disabledIcon = GrayFilter.createDisabledImage(sourceIcon);
+        }
+
+        if (selectedIcon == null) {
+            selectedIcon = new BufferedImage(w + 1, h + 1, BufferedImage.TYPE_INT_ARGB);
+            Graphics imageGraphics = selectedIcon.getGraphics();
+            button.getSelectedIcon().paintIcon(null, imageGraphics, 0, 0);
+        }
+
+        if (rolledOverSelectedIcon == null) {
+            rolledOverSelectedIcon = getBrighterImage(selectedIcon);
+        }
+
+        if (disabledSelectedIcon == null) {
+            disabledSelectedIcon = GrayFilter.createDisabledImage(selectedIcon);
+        }
+
+        int x = 3;
+        int y = button.getHeight() / 2 - h / 2;
+
+        String text = button.getText();
+        if (text == null || text.equals("")) {
+            x = button.getWidth() / 2 - w / 2;
+        }
+
+        boolean transitionState, actionEnabled, buttonEnabled;
+        if (toggleActionState == null) {
+            transitionState = false;
+            actionEnabled = false;
+            buttonEnabled = false;
+        } else {
+            transitionState = toggleActionState.isTransitionState();
+            actionEnabled = toggleActionState.isActionEnabled();
+            buttonEnabled = toggleActionState.isButtonEnabled();
+        }
+
+        boolean stopped = !transitionState && !actionEnabled;
+        boolean starting = transitionState && actionEnabled;
+        boolean started = !transitionState && actionEnabled;
+        boolean stopping = transitionState && !actionEnabled;
+
+        if (!buttonEnabled) {
+            g.drawImage(disabledIcon, x, y, null);
+        } else if (stopped) {
+            if (model.isRollover()) {
+                g.drawImage(rollOverIcon, x, y, null);
+            } else {
+                g.drawImage(sourceIcon, x, y, null);
+            }
+        } else if (starting) {
+            g.drawImage(disabledIcon, x, y, null);
+        } else if (started) {
+            if (model.isRollover()) {
+                g.drawImage(rolledOverSelectedIcon, x, y, null);
+            } else {
+                g.drawImage(selectedIcon, x, y, null);
+            }
+        } else if (stopping) {
+            g.drawImage(disabledSelectedIcon, x, y, null);
+        }
+    }
+
+    void setState(ToggleActionState toggleActionState) {
+        this.toggleActionState = toggleActionState;
+    }
+
+}
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadView.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadView.java	Tue Oct 13 12:48:05 2015 -0400
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.thread.client.common.view;
 
+import com.redhat.thermostat.client.core.ToggleActionState;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionListener;
@@ -50,6 +51,40 @@
         START_LIVE_RECORDING,
         STOP_LIVE_RECORDING
     };
+
+    public enum MonitoringState implements ToggleActionState {
+        STARTED(true, false, true),
+        STOPPED(true, false, false),
+        STARTING(true, true, true),
+        STOPPING(true, true, false),
+        DISABLED(false, false, false),
+        ;
+
+        private final boolean isTransitionState;
+        private final boolean isActionEnabled;
+        private final boolean isButtonEnabled;
+
+        MonitoringState(boolean isButtonEnabled, boolean isTransitionState, boolean isActionEnabled) {
+            this.isButtonEnabled = isButtonEnabled;
+            this.isTransitionState = isTransitionState;
+            this.isActionEnabled = isActionEnabled;
+        }
+
+        @Override
+        public boolean isTransitionState() {
+            return isTransitionState;
+        }
+
+        @Override
+        public boolean isActionEnabled() {
+            return isActionEnabled;
+        }
+
+        @Override
+        public boolean isButtonEnabled() {
+            return isButtonEnabled;
+        }
+    }
     
     protected ApplicationService appService;
     protected String uniqueId;
@@ -68,7 +103,7 @@
     }
     
     public abstract void setEnableRecordingControl(boolean enable);
-    public abstract void setRecording(boolean recording, boolean notify);
+    public abstract void setRecording(MonitoringState monitoringState, boolean notify);
     
     public abstract ThreadTableView createThreadTableView();
     public abstract ThreadTimelineView createThreadTimelineView();
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Tue Oct 13 12:48:05 2015 -0400
@@ -88,12 +88,10 @@
         
         initControllers();
         
-        view.setRecording(isRecording(), false);
+        view.setRecording(isRecording() ? ThreadView.MonitoringState.STARTED : ThreadView.MonitoringState.STOPPED, false);
         view.addThreadActionListener(new ThreadActionListener());
 
-        if (!vmInfoDao.getVmInfo(ref).isAlive()) {
-            view.setEnableRecordingControl(false);
-        }
+        view.setEnableRecordingControl(vmInfoDao.getVmInfo(ref).isAlive());
     }
     
     private boolean isRecording() {
@@ -117,11 +115,15 @@
         public void actionPerformed(ActionEvent<ThreadAction> actionEvent) {
             switch (actionEvent.getActionId()) {
             case START_LIVE_RECORDING:
+                view.setRecording(ThreadView.MonitoringState.STARTING, false);
                 startHarvester();
+                view.setRecording(ThreadView.MonitoringState.STARTED, false);
                 break;
             
             case STOP_LIVE_RECORDING:
+                view.setRecording(ThreadView.MonitoringState.STOPPING, false);
                 stopHarvester();
+                view.setRecording(ThreadView.MonitoringState.STOPPED, false);
                 break;
                 
             default:
@@ -137,7 +139,8 @@
                     boolean result = collector.startHarvester();
                     if (!result) {
                         view.displayWarning(translator.localize(LocaleResources.WARNING_CANNOT_ENABLE));
-                        view.setRecording(false, false);
+                        view.setEnableRecordingControl(false);
+                        view.setRecording(ThreadView.MonitoringState.DISABLED, false);
                     }
                 }
             }, translator.localize(LocaleResources.STARTING_MONITORING));
@@ -150,7 +153,8 @@
                     boolean result = collector.stopHarvester();
                     if (!result) {
                         view.displayWarning(translator.localize(LocaleResources.WARNING_CANNOT_DISABLE));
-                        view.setRecording(true, false);
+                        view.setEnableRecordingControl(false);
+                        view.setRecording(ThreadView.MonitoringState.DISABLED, false);
                     }
                 }
             }, translator.localize(LocaleResources.STOPPING_MONITORING));
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Tue Oct 13 12:48:05 2015 -0400
@@ -210,7 +210,7 @@
                                                      viewFactory, notifier);
 
         verify(collector).isHarvesterCollecting();
-        verify(view, times(1)).setRecording(false, false);
+        verify(view, times(1)).setRecording(ThreadView.MonitoringState.STOPPED, false);
 
         threadActionListener = viewArgumentCaptor.getValue();
         threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.START_LIVE_RECORDING));
@@ -218,7 +218,8 @@
         verify(appExecutor, times(1)).execute(isA(Runnable.class));
         longRunningTaskCaptor.getValue().run();
 
-        verify(view, times(1)).setRecording(false, false);
+        verify(view, times(1)).setRecording(ThreadView.MonitoringState.STARTING, false);
+        verify(view, times(1)).setRecording(ThreadView.MonitoringState.STARTED, false);
         verify(collector).startHarvester();
 
         threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.STOP_LIVE_RECORDING));
@@ -227,7 +228,8 @@
         longRunningTaskCaptor.getValue().run();
 
         verify(collector).stopHarvester();
-        verify(view, times(1)).setRecording(false, false);
+        verify(view, times(1)).setRecording(ThreadView.MonitoringState.STOPPING, false);
+        verify(view, times(2)).setRecording(ThreadView.MonitoringState.STOPPED, false);
 
         threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.STOP_LIVE_RECORDING));
 
@@ -235,9 +237,8 @@
         longRunningTaskCaptor.getValue().run();
 
         verify(collector, times(2)).stopHarvester();
-        verify(view, times(1)).setRecording(true, false);
-
-        verify(view, times(0)).setEnableRecordingControl(false);
+        verify(view, times(2)).setRecording(ThreadView.MonitoringState.STOPPING, false);
+        verify(view, times(3)).setRecording(ThreadView.MonitoringState.STOPPED, false);
     }
 
     @Test
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Tue Oct 13 12:48:05 2015 -0400
@@ -77,7 +77,7 @@
 
     private JTabbedPane topPane;
     private JTabbedPane bottomPane;
-    
+
     private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
 
     private boolean skipNotification = false;
@@ -85,6 +85,7 @@
     private int threadDetailsPaneID = 0;
     
     private UIDefaults uiDefaults;
+    private boolean viewControlsEnabled = true;
 
     public SwingThreadView(UIDefaults uiDefaults) {
         
@@ -94,13 +95,13 @@
         // TODO use ComponentVisiblityNotifier instead
         // sadly, the BasicView.notifier field can not be accessed here
         panel.addHierarchyListener(new ComponentVisibleListener() {
-            
+
             @Override
             public void componentShown(Component component) {
                 SwingThreadView.this.notify(Action.VISIBLE);
                 restoreDivider();
             }
-            
+
             @Override
             public void componentHidden(Component component) {
                 SwingThreadView.this.notify(Action.HIDDEN);
@@ -108,17 +109,15 @@
         });
         
         panel.getSplitPane().addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY,
-                                                       new PropertyChangeListener()
-        {
-            @Override
-            public void propertyChange(PropertyChangeEvent evt) {
-                JSplitPane sourceSplitPane = (JSplitPane) evt.getSource();
-                saveDivider(sourceSplitPane.getDividerLocation());
-            }
-        });
-        
+                new PropertyChangeListener() {
+                    @Override
+                    public void propertyChange(PropertyChangeEvent evt) {
+                        JSplitPane sourceSplitPane = (JSplitPane) evt.getSource();
+                        saveDivider(sourceSplitPane.getDividerLocation());
+                    }
+                });
+
         panel.getToggleButton().setToolTipText(t.localize(LocaleResources.START_RECORDING).getContents());
-        panel.getToggleButton().setText(t.localize(LocaleResources.THREAD_MONITOR_SWITCH).getContents());
         panel.getToggleButton().addItemListener(new ItemListener()
         {
             @Override
@@ -198,21 +197,23 @@
 
     @Override
     public void setEnableRecordingControl(final boolean enable) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                panel.getToggleButton().setEnabled(enable);
-            }
-        });
+        this.viewControlsEnabled = enable;
+        if (!enable) {
+            setRecording(MonitoringState.DISABLED, false);
+        }
     }
     
     @Override
-    public void setRecording(final boolean recording, final boolean notify) {
+    public void setRecording(final MonitoringState monitoringState, final boolean notify) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
                 if (!notify) skipNotification = true;
-                panel.getToggleButton().setSelected(recording);
+                if (!viewControlsEnabled) {
+                    panel.getToggleButton().setToggleActionState(MonitoringState.DISABLED);
+                } else {
+                    panel.getToggleButton().setToggleActionState(monitoringState);
+                }
                 if (!notify) skipNotification = false;
             }
         });
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadMainPanel.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadMainPanel.java	Tue Oct 13 12:48:05 2015 -0400
@@ -44,12 +44,18 @@
 
 import com.redhat.thermostat.client.swing.IconResource;
 import com.redhat.thermostat.client.swing.components.ActionToggleButton;
+import com.redhat.thermostat.client.swing.components.FontAwesomeIcon;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.swing.components.Icon;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.thread.client.common.locale.LocaleResources;
 
 @SuppressWarnings("serial")
 class ThreadMainPanel extends JPanel {
+
+    private static final Icon START_ICON = IconResource.SAMPLE.getIcon();
+    private static final Icon STOP_ICON = new FontAwesomeIcon('\uf04d', START_ICON.getIconHeight());
+
     private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
     private JSplitPane splitPane;
     
@@ -64,7 +70,7 @@
         JPanel content = new JPanel();
         headerPanel.setContent(content);
         
-        toggleButton = new ActionToggleButton(IconResource.SAMPLE.getIcon());
+        toggleButton = new ActionToggleButton(START_ICON, STOP_ICON, t.localize(LocaleResources.THREAD_MONITOR_SWITCH));
         toggleButton.setName("recordButton");
         headerPanel.addToolBarButton(toggleButton);
         
--- a/thread/client-swing/src/test/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadViewTest.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/thread/client-swing/src/test/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadViewTest.java	Tue Oct 13 12:48:05 2015 -0400
@@ -48,6 +48,7 @@
 
 import javax.swing.JFrame;
 
+import com.redhat.thermostat.thread.client.common.view.ThreadView;
 import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
 
 import org.fest.swing.annotation.GUITest;
@@ -147,11 +148,11 @@
         
         // now try "programmatically"
         
-        view.setRecording(true, true);
+        view.setRecording(ThreadView.MonitoringState.STARTED, true);
         
         togglefixture.requireToolTip(t.localize(LocaleResources.STOP_RECORDING).getContents());
     
-        view.setRecording(false, false);
+        view.setRecording(ThreadView.MonitoringState.STOPPED, false);
         
         togglefixture.requireToolTip(t.localize(LocaleResources.START_RECORDING).getContents());
     }
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/JmxNotificationsView.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/JmxNotificationsView.java	Tue Oct 13 12:48:05 2015 -0400
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.vm.jmx.client.core;
 
+import com.redhat.thermostat.client.core.ToggleActionState;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionListener;
@@ -48,11 +49,46 @@
         TOGGLE_NOTIFICATIONS,
     }
 
+    public enum MonitoringState implements ToggleActionState {
+        STARTED(true, false, true),
+        STOPPED(true, false, false),
+        STARTING(true, true, true),
+        STOPPING(true, true, false),
+        DISABLED(false, false, false),
+        ;
+
+        private final boolean isTransitionState;
+        private final boolean isActionEnabled;
+        private final boolean isButtonEnabled;
+
+        MonitoringState(boolean isButtonEnabled, boolean isTransitionState, boolean isActionEnabled) {
+            this.isButtonEnabled = isButtonEnabled;
+            this.isTransitionState = isTransitionState;
+            this.isActionEnabled = isActionEnabled;
+        }
+
+        @Override
+        public boolean isTransitionState() {
+            return isTransitionState;
+        }
+
+        @Override
+        public boolean isActionEnabled() {
+            return isActionEnabled;
+        }
+
+        @Override
+        public boolean isButtonEnabled() {
+            return isButtonEnabled;
+        }
+    }
+
+
     public abstract void addNotificationActionListener(ActionListener<NotificationAction> listener);
     public abstract void removeNotificationActionListener(ActionListener<NotificationAction> listener);
 
     public abstract void setViewControlsEnabled(boolean enabled);
-    public abstract void setNotificationsEnabled(boolean enabled);
+    public abstract void setMonitoringState(MonitoringState monitoringState);
     public abstract void clearNotifications();
     public abstract void addNotification(JmxNotification data);
     public abstract void displayWarning(LocalizedString warning);
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewController.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewController.java	Tue Oct 13 12:48:05 2015 -0400
@@ -101,11 +101,15 @@
         
         // Callbacks for toggle notifications
         final Runnable successAction = new Runnable() {
-
             @Override
             public void run() {
-                notificationsEnabled.set(!notificationsEnabled.get());
-                view.setNotificationsEnabled(notificationsEnabled.get());
+                boolean val = notificationsEnabled.get();
+                notificationsEnabled.set(!val);
+                if (val) {
+                    view.setMonitoringState(JmxNotificationsView.MonitoringState.STOPPED);
+                } else {
+                    view.setMonitoringState(JmxNotificationsView.MonitoringState.STARTED);
+                }
             }
         };
 
@@ -116,12 +120,11 @@
                 LocalizedString warning;
                 if (notificationsEnabled.get()) {
                     warning = t.localize(LocaleResources.NOTIFICATIONS_CANNOT_DISABLE);
-                }
-                else {
+                } else {
                     warning = t.localize(LocaleResources.NOTIFICATIONS_CANNOT_ENABLE);
                 }
                 view.displayWarning(warning);
-                view.setNotificationsEnabled(notificationsEnabled.get());
+                view.setViewControlsEnabled(false);
             }
         };
         
@@ -138,6 +141,11 @@
                     stopUpdatingView();
                     break;
                 case VISIBLE:
+                    if (notificationsEnabled.get() && isVmAlive()) {
+                        view.setMonitoringState(JmxNotificationsView.MonitoringState.STARTED);
+                    } else {
+                        view.setMonitoringState(JmxNotificationsView.MonitoringState.STOPPED);
+                    }
                     view.setViewControlsEnabled(isVmAlive());
                     startUpdatingView();
                     break;
@@ -149,6 +157,12 @@
             @Override
             public void actionPerformed(ActionEvent<NotificationAction> actionEvent) {
                 if (actionEvent.getActionId() == NotificationAction.TOGGLE_NOTIFICATIONS) {
+                    final boolean enabled = notificationsEnabled.get();
+                    if (!enabled) {
+                        view.setMonitoringState(JmxNotificationsView.MonitoringState.STARTING);
+                    } else {
+                        view.setMonitoringState(JmxNotificationsView.MonitoringState.STOPPING);
+                    }
                     // This can block on network, do outside EDT/UI thread
                     appSvc.getApplicationExecutor().execute(new Runnable() {
                         @Override
@@ -179,8 +193,13 @@
                     return;
                 }
 
-                notificationsEnabled.set(status.isEnabled());
-                view.setNotificationsEnabled(notificationsEnabled.get());
+                boolean monitoring = status.isEnabled();
+                notificationsEnabled.set(monitoring);
+                if (monitoring) {
+                    view.setMonitoringState(JmxNotificationsView.MonitoringState.STARTED);
+                } else {
+                    view.setMonitoringState(JmxNotificationsView.MonitoringState.STOPPED);
+                }
 
                 List<JmxNotification> notifications = dao.getNotifications(vm, lastTimeStamp);
                 for (JmxNotification notification : notifications) {
@@ -190,6 +209,8 @@
             }
         });
 
+        view.setViewControlsEnabled(true);
+
     }
 
     private boolean isVmAlive() {
--- a/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewControllerTest.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewControllerTest.java	Tue Oct 13 12:48:05 2015 -0400
@@ -201,7 +201,7 @@
         JmxNotificationStatus status = mock(JmxNotificationStatus.class);
         when(notificationDao.getLatestNotificationStatus(vm)).thenReturn(null);
 
-        verify(view, never()).setNotificationsEnabled(anyBoolean());
+        verify(view, never()).setMonitoringState(any(JmxNotificationsView.MonitoringState.class));
         verify(view, never()).addNotification(isA(JmxNotification.class));
     }
 
@@ -245,7 +245,8 @@
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
 
         verify(toggleReq).sendEnableNotificationsRequestToAgent(eq(vm), eq(true));
-        verify(view).setNotificationsEnabled(true);
+        verify(view).setMonitoringState(JmxNotificationsView.MonitoringState.STARTING);
+        verify(view).setMonitoringState(JmxNotificationsView.MonitoringState.STARTED);
     }
     
     @Test
@@ -258,9 +259,10 @@
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
 
         verify(toggleReq).sendEnableNotificationsRequestToAgent(vm, true);
-        verify(view, never()).setNotificationsEnabled(true);
-        verify(view).setNotificationsEnabled(false);
-        
+        verify(view).setMonitoringState(JmxNotificationsView.MonitoringState.STARTING);
+        verify(view, never()).setMonitoringState(JmxNotificationsView.MonitoringState.STARTED);
+        verify(view).setViewControlsEnabled(false);
+
         ArgumentCaptor<LocalizedString> warningCaptor = ArgumentCaptor.forClass(LocalizedString.class);
         verify(view).displayWarning(warningCaptor.capture());
         assertEquals(translator.localize(LocaleResources.NOTIFICATIONS_CANNOT_ENABLE).getContents(),
@@ -280,7 +282,8 @@
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
 
         verify(toggleReq).sendEnableNotificationsRequestToAgent(vm, false);
-        verify(view).setNotificationsEnabled(false);
+        verify(view).setMonitoringState(JmxNotificationsView.MonitoringState.STOPPING);
+        verify(view).setMonitoringState(JmxNotificationsView.MonitoringState.STOPPED);
     }
 
     @Test
@@ -339,8 +342,10 @@
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
 
         verify(toggleReq).sendEnableNotificationsRequestToAgent(vm, false);
-        verify(view, never()).setNotificationsEnabled(false);
-        verify(view, times(2)).setNotificationsEnabled(true);
+        verify(view, times(1)).setMonitoringState(JmxNotificationsView.MonitoringState.STARTED);
+        verify(view, times(1)).setMonitoringState(JmxNotificationsView.MonitoringState.STOPPING);
+        verify(view, times(1)).setMonitoringState(JmxNotificationsView.MonitoringState.STARTING);
+        verify(view).setViewControlsEnabled(false);
         
         ArgumentCaptor<LocalizedString> warningCaptor = ArgumentCaptor.forClass(LocalizedString.class);
         verify(view).displayWarning(warningCaptor.capture());
--- a/vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java	Tue Oct 13 12:48:05 2015 -0400
@@ -67,7 +67,9 @@
 import com.redhat.thermostat.client.swing.IconResource;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.ActionToggleButton;
+import com.redhat.thermostat.client.swing.components.FontAwesomeIcon;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.swing.components.Icon;
 import com.redhat.thermostat.client.swing.components.LocalizedLabel;
 import com.redhat.thermostat.client.swing.components.experimental.EventTimeline;
 import com.redhat.thermostat.client.swing.components.experimental.EventTimelineRangeChangeListener;
@@ -89,6 +91,9 @@
     private static final Translate<LocaleResources> translate = LocaleResources.createLocalizer();
     private List<ActionListener<NotificationAction>> listeners = new CopyOnWriteArrayList<>();
 
+    private static final Icon START_ICON = IconResource.SAMPLE.getIcon();
+    private static final Icon STOP_ICON = new FontAwesomeIcon('\uf04d', START_ICON.getIconHeight());
+
     private final HeaderPanel visiblePanel;
 
     private ActionToggleButton toolbarButton;
@@ -98,6 +103,7 @@
     private DetailPanel timelineDetails;
     private Timeline ruler;
     private EventTimeline timeline;
+    private boolean viewControlsEnabled = true;
 
     public JmxNotificationsSwingView() {
 
@@ -155,7 +161,7 @@
 
         new ComponentVisibilityNotifier().initialize(contents, notifier);
 
-        toolbarButton = new ActionToggleButton(IconResource.SAMPLE.getIcon(), translate.localize(LocaleResources.NOTIFICATIONS_ENABLE));
+        toolbarButton = new ActionToggleButton(START_ICON, STOP_ICON, translate.localize(LocaleResources.NOTIFICATIONS_ENABLE));
         toolbarButton.setName("toggleNotifications");
         toolbarButton.setToolTipText(translate.localize(LocaleResources.NOTIFICATIONS_ENABLE_DESCRIPTION).getContents());
         toolbarButton.addActionListener(new java.awt.event.ActionListener() {
@@ -199,30 +205,32 @@
         listeners.remove(listener);
     }
 
-    private void fireNotificationAction(NotificationAction action) {
-        for (ActionListener<NotificationAction> listener : listeners) {
-            listener.actionPerformed(new ActionEvent<>(this, action));
-        }
-    }
-
     @Override
-    public void setViewControlsEnabled(final boolean enabled) {
+    public void setMonitoringState(final MonitoringState monitoringState) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                toolbarButton.setEnabled(enabled);
+                if (!viewControlsEnabled) {
+                    toolbarButton.setToggleActionState(MonitoringState.DISABLED);
+                } else {
+                    toolbarButton.setToggleActionState(monitoringState);
+                }
             }
         });
     }
 
     @Override
-    public void setNotificationsEnabled(final boolean enabled) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                toolbarButton.setSelected(enabled);
-            }
-        });
+    public void setViewControlsEnabled(boolean enabled) {
+        this.viewControlsEnabled = enabled;
+        if (!enabled) {
+            setMonitoringState(MonitoringState.DISABLED);
+        }
+    }
+
+    private void fireNotificationAction(NotificationAction action) {
+        for (ActionListener<NotificationAction> listener : listeners) {
+            listener.actionPerformed(new ActionEvent<>(this, action));
+        }
     }
 
     @Override
--- a/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResources.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResources.java	Tue Oct 13 12:48:05 2015 -0400
@@ -47,6 +47,7 @@
 
     PROFILER_CURRENT_STATUS_ACTIVE,
     PROFILER_CURRENT_STATUS_INACTIVE,
+    PROFILER_CURRENT_STATUS_DEAD,
     START_PROFILING,
     STARTING_PROFILING,
     STOP_PROFILING,
--- a/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java	Tue Oct 13 12:48:05 2015 -0400
@@ -69,9 +69,13 @@
 import javax.swing.table.DefaultTableModel;
 
 import com.redhat.thermostat.client.swing.EdtHelper;
+import com.redhat.thermostat.client.swing.IconResource;
 import com.redhat.thermostat.client.swing.NonEditableTableModel;
 import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.ActionToggleButton;
+import com.redhat.thermostat.client.swing.components.FontAwesomeIcon;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.swing.components.Icon;
 import com.redhat.thermostat.client.swing.components.ThermostatScrollPane;
 import com.redhat.thermostat.client.swing.components.ThermostatTable;
 import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
@@ -86,6 +90,9 @@
 
 public class SwingVmProfileView extends VmProfileView implements SwingComponent {
 
+    private static final Icon START_ICON = IconResource.SAMPLE.getIcon();
+    private static final Icon STOP_ICON = new FontAwesomeIcon('\uf04d', START_ICON.getIconHeight());
+
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
     private static final double SPLIT_PANE_RATIO = 0.3;
@@ -94,8 +101,7 @@
 
     private HeaderPanel mainContainer;
 
-    private JToggleButton startButton;
-    private JToggleButton stopButton;
+    private ActionToggleButton toggleButton;
 
     private DefaultListModel<Profile> listModel;
     private JList<Profile> profileList;
@@ -103,6 +109,7 @@
     private DefaultTableModel tableModel;
 
     private JLabel currentStatusLabel;
+    private boolean viewControlsEnabled = true;
 
     static class ProfileItemRenderer extends DefaultListCellRenderer {
         @Override
@@ -123,22 +130,37 @@
     public SwingVmProfileView() {
         listModel = new DefaultListModel<>();
 
+        toggleButton = new ActionToggleButton(START_ICON, STOP_ICON, translator.localize(LocaleResources.START_PROFILING));
+        toggleButton.toggleText(false);
+        toggleButton.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                JToggleButton button = (JToggleButton) e.getSource();
+                if (button.isSelected()) {
+                    fireProfileAction(ProfileAction.START_PROFILING);
+                } else {
+                    fireProfileAction(ProfileAction.STOP_PROFILING);
+                }
+            }
+        });
+
         mainContainer = new HeaderPanel(translator.localize(LocaleResources.PROFILER_HEADING));
         new ComponentVisibilityNotifier().initialize(mainContainer, notifier);
 
         JPanel contentContainer = new JPanel(new BorderLayout());
         mainContainer.setContent(contentContainer);
+        mainContainer.addToolBarButton(toggleButton);
 
-        JComponent actionsPanel = createActionsPanel();
+        JComponent actionsPanel = createStatusPanel();
         contentContainer.add(actionsPanel, BorderLayout.PAGE_START);
 
         JComponent profilingResultsPanel = createInformationPanel();
         contentContainer.add(profilingResultsPanel, BorderLayout.CENTER);
     }
 
-    private JPanel createActionsPanel() {
+    private JPanel createStatusPanel() {
         GridBagLayout layout = new GridBagLayout();
-        JPanel actionsPanel = new JPanel(layout);
+        JPanel statusPanel = new JPanel(layout);
 
         GridBagConstraints constraints = new GridBagConstraints();
         constraints.fill = GridBagConstraints.HORIZONTAL;
@@ -151,40 +173,14 @@
 
         String wrappedText = "<html>" + translator.localize(LocaleResources.PROFILER_DESCRIPTION).getContents() + "</html>";
         JLabel descriptionLabel = new JLabel(wrappedText);
-        actionsPanel.add(descriptionLabel, constraints);
+        statusPanel.add(descriptionLabel, constraints);
 
         constraints.gridy = 1;
         constraints.gridx = 0;
         constraints.gridwidth = 1;
         currentStatusLabel = new JLabel("Current Status: {0}");
-        actionsPanel.add(currentStatusLabel, constraints);
-
-        constraints.fill = GridBagConstraints.NONE;
-        constraints.gridx = GridBagConstraints.RELATIVE;
-        constraints.weightx = 0.0;
-        startButton = new JToggleButton(translator.localize(LocaleResources.START_PROFILING).getContents());
-        startButton.addActionListener(new java.awt.event.ActionListener() {
-            @Override
-            public void actionPerformed(java.awt.event.ActionEvent e) {
-                JToggleButton button = (JToggleButton) e.getSource();
-                if (button.isSelected()) {
-                    fireProfileAction(ProfileAction.START_PROFILING);
-                }
-            }
-        });
-        actionsPanel.add(startButton, constraints);
-        stopButton = new JToggleButton(translator.localize(LocaleResources.STOP_PROFILING).getContents());
-        stopButton.addActionListener(new java.awt.event.ActionListener() {
-            @Override
-            public void actionPerformed(java.awt.event.ActionEvent e) {
-                JToggleButton button = (JToggleButton) e.getSource();
-                if (button.isSelected()) {
-                    fireProfileAction(ProfileAction.STOP_PROFILING);
-                }
-            }
-        });
-        actionsPanel.add(stopButton, constraints);
-        return actionsPanel;
+        statusPanel.add(currentStatusLabel, constraints);
+        return statusPanel;
     }
 
     private JComponent createInformationPanel() {
@@ -278,40 +274,38 @@
     }
 
     @Override
-    public void enableStartProfiling(final boolean start) {
+    public void setProfilingState(final ProfilingState profilingState) {
+        final String status, buttonLabel;
+        if (!viewControlsEnabled) {
+            status = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_DEAD).getContents();
+            buttonLabel = translator.localize(LocaleResources.START_PROFILING).getContents();
+        } else if (profilingState == ProfilingState.STOPPING || profilingState == ProfilingState.STARTED) {
+            status = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_ACTIVE).getContents();
+            buttonLabel = translator.localize(LocaleResources.STOP_PROFILING).getContents();
+        } else {
+            status = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_INACTIVE).getContents();
+            buttonLabel = translator.localize(LocaleResources.START_PROFILING).getContents();
+        }
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                startButton.setEnabled(start);
+                currentStatusLabel.setText(status);
+                if (!viewControlsEnabled) {
+                    toggleButton.setToggleActionState(ProfilingState.DISABLED);
+                } else {
+                    toggleButton.setToggleActionState(profilingState);
+                }
+                toggleButton.setText(buttonLabel);
             }
         });
     }
 
     @Override
-    public void enableStopProfiling(final boolean stop) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                stopButton.setEnabled(stop);
-            }
-        });
-    }
-
-    @Override
-    public void setProfilingStatus(final String text, final boolean active) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                currentStatusLabel.setText(text);
-                if (active) {
-                    startButton.setSelected(true);
-                    stopButton.setSelected(false);
-                } else {
-                    startButton.setSelected(false);
-                    stopButton.setSelected(true);
-                }
-            }
-        });
+    public void setViewControlsEnabled(boolean enabled) {
+        this.viewControlsEnabled = enabled;
+        if (!enabled) {
+            setProfilingState(ProfilingState.DISABLED);
+        }
     }
 
     @Override
--- a/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileController.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileController.java	Tue Oct 13 12:48:05 2015 -0400
@@ -74,6 +74,7 @@
 import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser;
 import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.Profile;
 import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.ProfileAction;
+import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.ProfilingState;
 import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
 import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
@@ -148,6 +149,7 @@
                         updater.stop();
                         break;
                     case VISIBLE:
+                        view.setViewControlsEnabled(isAlive());
                         updater.start();
                         break;
                     default:
@@ -162,10 +164,10 @@
                 ProfileAction id = actionEvent.getActionId();
                 switch (id) {
                 case START_PROFILING:
-                    startProfiling(view);
+                    startProfiling();
                     break;
                 case STOP_PROFILING:
-                    stopProfiling(view);
+                    stopProfiling();
                     break;
                 case PROFILE_SELECTED:
                     updateViewWithProfileRunData();
@@ -177,20 +179,19 @@
 
         });
 
+        view.setViewControlsEnabled(isAlive());
     }
 
-    private void startProfiling(final VmProfileView view) {
-        disableViewControlsAndSendRequest(view, true);
+    private void startProfiling() {
+        setProgressNotificationAndSendRequest(true);
     }
 
-    private void stopProfiling(final VmProfileView view) {
-        disableViewControlsAndSendRequest(view, false);
+    private void stopProfiling() {
+        setProgressNotificationAndSendRequest(false);
     }
 
-    private void disableViewControlsAndSendRequest(VmProfileView view, boolean start) {
+    private void setProgressNotificationAndSendRequest(boolean start) {
         showProgressNotification(start);
-        // disable the UI until we get a update in storage
-        disableViewControls();
         sendProfilingRequest(start);
     }
 
@@ -202,15 +203,15 @@
             @Override
             public void fireComplete(Request request, Response response) {
                 switch (response.getType()) {
-                case OK:
-                    updateViewWithCurrentProfilingStatus();
-                    break;
-                default:
-                    // FIXME show message to user
+                    case OK:
+                        updateViewWithCurrentProfilingStatus();
+                        break;
+                    default:
+                        // FIXME show message to user
 
-                    hideProgressNotificationIfVisible();
-                    profilingStartOrStopRequested = false;
-                    break;
+                        hideProgressNotificationIfVisible();
+                        profilingStartOrStopRequested = false;
+                        break;
                 }
             }
         });
@@ -219,54 +220,46 @@
     }
 
     private void updateViewWithCurrentProfilingStatus() {
-        boolean currentlyActive = false;
+        ProfilingState profilingState = ProfilingState.STOPPED;
 
         ProfileStatusChange currentStatus = profileDao.getLatestStatus(vm);
         if (currentStatus != null) {
-            currentlyActive = currentStatus.isStarted();
+            boolean currentlyActive = currentStatus.isStarted();
+            if (currentlyActive && profilingStartOrStopRequested) {
+                profilingState = ProfilingState.STARTING;
+            } else if (currentlyActive) {
+                profilingState = ProfilingState.STARTED;
+            } else if (profilingStartOrStopRequested) {
+                profilingState = ProfilingState.STOPPING;
+            } else {
+                profilingState = ProfilingState.STOPPED;
+            }
         }
 
-        String message;
-        if (currentlyActive) {
-            message = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_ACTIVE).getContents();
-        } else {
-            message = translator.localize(LocaleResources.PROFILER_CURRENT_STATUS_INACTIVE).getContents();
-        }
-
+        view.setViewControlsEnabled(isAlive());
         if (!isAlive()) {
-            disableViewControls();
-            view.setProfilingStatus(message, currentlyActive);
+            view.setProfilingState(ProfilingState.DISABLED);
         } else if (profilingStartOrStopRequested) {
             boolean statusChanged = (previousStatus == null && currentStatus != null)
                     || (currentStatus != null && !(currentStatus.equals(previousStatus)));
             if (statusChanged) {
-                enableViewControlsFor(currentlyActive);
-                view.setProfilingStatus(message, currentlyActive);
+                view.setProfilingState(profilingState);
                 profilingStartOrStopRequested = false;
                 hideProgressNotificationIfVisible();
             }
         } else {
-            enableViewControlsFor(currentlyActive);
-            view.setProfilingStatus(message, currentlyActive);
+            view.setProfilingState(profilingState);
         }
 
         previousStatus = currentStatus;
     }
 
-    private void disableViewControls() {
-        view.enableStartProfiling(false);
-        view.enableStopProfiling(false);
-    }
-
-    private void enableViewControlsFor(boolean currentlyActive) {
-        view.enableStartProfiling(!currentlyActive);
-        view.enableStopProfiling(currentlyActive);
-    }
-
     private void showProgressNotification(boolean start) {
         if (start) {
+            view.setProfilingState(ProfilingState.STARTING);
             progressDisplay = new ProgressHandle(translator.localize(LocaleResources.STARTING_PROFILING));
         } else {
+            view.setProfilingState(ProfilingState.STOPPING);
             progressDisplay = new ProgressHandle(translator.localize(LocaleResources.STOPPING_PROFILING));
         }
         progressDisplay.setIndeterminate(true);
--- a/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileView.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileView.java	Tue Oct 13 12:48:05 2015 -0400
@@ -41,6 +41,7 @@
 
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.client.core.ToggleActionState;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.vm.profiler.client.core.ProfilingResult;
 
@@ -81,6 +82,40 @@
         PROFILE_SELECTED,
     }
 
+    enum ProfilingState implements ToggleActionState {
+        STARTED(true, false, true),
+        STOPPED(true, false, false),
+        STARTING(true, true, true),
+        STOPPING(true, true, false),
+        DISABLED(false, false, false),
+        ;
+
+        private final boolean isTransitionState;
+        private final boolean isActionEnabled;
+        private final boolean isButtonEnabled;
+
+        ProfilingState(boolean isButtonEnabled, boolean isTransitionState, boolean isActionEnabled) {
+            this.isButtonEnabled = isButtonEnabled;
+            this.isTransitionState = isTransitionState;
+            this.isActionEnabled = isActionEnabled;
+        }
+
+        @Override
+        public boolean isTransitionState() {
+            return isTransitionState;
+        }
+
+        @Override
+        public boolean isActionEnabled() {
+            return isActionEnabled;
+        }
+
+        @Override
+        public boolean isButtonEnabled() {
+            return isButtonEnabled;
+        }
+    }
+
     public abstract void addProfileActionListener(ActionListener<ProfileAction> listener);
 
     public abstract void removeProfileActionlistener(ActionListener<ProfileAction> listener);
@@ -91,11 +126,8 @@
      * indicating profiling in the UI
      */
 
-    /** Enable (or disable) UI that starts profiling */
-    public abstract void enableStartProfiling(boolean start);
-    /** Enable (or disable) UI that stops profiling */
-    public abstract void enableStopProfiling(boolean stop);
-    public abstract void setProfilingStatus(String text, boolean enabled);
+    public abstract void setProfilingState(ProfilingState profilingState);
+    public abstract void setViewControlsEnabled(boolean enabled);
 
     public abstract void setAvailableProfilingRuns(List<Profile> data);
 
--- a/vm-profiler/client-swing/src/main/resources/com/redhat/thermostat/vm/profiler/client/swing/internal/strings.properties	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-profiler/client-swing/src/main/resources/com/redhat/thermostat/vm/profiler/client/swing/internal/strings.properties	Tue Oct 13 12:48:05 2015 -0400
@@ -7,6 +7,7 @@
 
 PROFILER_CURRENT_STATUS_ACTIVE = Currently profiling: yes
 PROFILER_CURRENT_STATUS_INACTIVE = Currently profiling: no
+PROFILER_CURRENT_STATUS_DEAD = Selected VM is inactive
 START_PROFILING = Start Profiling
 STARTING_PROFILING = Starting profiling\u2026
 STOP_PROFILING = Stop Profiling
--- a/vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileControllerTest.java	Tue Oct 13 11:44:25 2015 -0400
+++ b/vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileControllerTest.java	Tue Oct 13 12:48:05 2015 -0400
@@ -41,6 +41,7 @@
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -182,7 +183,7 @@
         ProfileInfo profile = new ProfileInfo(AGENT_ID, VM_ID, PROFILE_TIMESTAMP, PROFILE_ID);
 
         when(profileDao.getAllProfileInfo(vm,
-                new Range<>(SOME_TIMESTAMP - TimeUnit.DAYS.toMillis(1) , SOME_TIMESTAMP)))
+                new Range<>(SOME_TIMESTAMP - TimeUnit.DAYS.toMillis(1), SOME_TIMESTAMP)))
             .thenReturn(Arrays.asList(profile));
 
         ProfileStatusChange status = new ProfileStatusChange(AGENT_ID, VM_ID, PROFILE_TIMESTAMP, false);
@@ -197,9 +198,7 @@
         assertEquals(1, resultList.size());
         assertEquals(PROFILE_TIMESTAMP, resultList.get(0).timeStamp);
 
-        verify(view).setProfilingStatus("Currently profiling: no", false);
-        verify(view).enableStartProfiling(true);
-        verify(view).enableStopProfiling(false);
+        verify(view, times(2)).setViewControlsEnabled(true);
     }
 
     @Test
@@ -215,8 +214,6 @@
         Runnable runnable = runnableCaptor.getValue();
         runnable.run();
 
-        verify(view).enableStartProfiling(false);
-        verify(view).enableStopProfiling(false);
     }
 
     @Test
@@ -228,14 +225,13 @@
 
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, ProfileAction.START_PROFILING));
 
-        verify(view).enableStartProfiling(false);
-        verify(view).enableStopProfiling(false);
-
         ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
         verify(queue).putRequest(requestCaptor.capture());
         Request expectedRequest = ProfileRequest.create(AGENT_ADDRESS, VM_ID, ProfileRequest.START_PROFILING);
         Request actualRequest = requestCaptor.getValue();
         assertRequestEquals(actualRequest, expectedRequest);
+
+        verify(view).setProfilingState(VmProfileView.ProfilingState.STARTING);
     }
 
     @Test
@@ -247,9 +243,6 @@
 
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, ProfileAction.START_PROFILING));
 
-        verify(view).enableStartProfiling(false);
-        verify(view).enableStopProfiling(false);
-
         ProfileStatusChange status = new ProfileStatusChange(AGENT_ID, VM_ID, PROFILE_TIMESTAMP, true);
         when(profileDao.getLatestStatus(vm)).thenReturn(status);
 
@@ -258,7 +251,7 @@
 
         runnableCaptor.getValue().run();
 
-        verify(view).enableStopProfiling(true);
+        verify(view, times(2)).setProfilingState(VmProfileView.ProfilingState.STARTING);
     }
 
     @Test
@@ -277,6 +270,8 @@
         Request actualRequest = requestCaptor.getValue();
 
         assertRequestEquals(actualRequest, expectedRequest);
+
+        verify(view).setProfilingState(VmProfileView.ProfilingState.STOPPING);
     }
 
     @Test
@@ -288,8 +283,7 @@
 
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, ProfileAction.STOP_PROFILING));
 
-        verify(view).enableStartProfiling(false);
-        verify(view).enableStopProfiling(false);
+        verify(view).setProfilingState(VmProfileView.ProfilingState.STOPPING);
 
         ProfileStatusChange status = new ProfileStatusChange(AGENT_ID, VM_ID, PROFILE_TIMESTAMP, false);
         when(profileDao.getLatestStatus(vm)).thenReturn(status);
@@ -299,7 +293,7 @@
 
         runnableCaptor.getValue().run();
 
-        verify(view).enableStartProfiling(true);
+        verify(view, times(2)).setProfilingState(VmProfileView.ProfilingState.STOPPING);
     }
 
     @Test