changeset 221:5b50c4cec7ce

Add client preferences and ui to modify them Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-April/000757.html
author Omair Majid <omajid@redhat.com>
date Thu, 12 Apr 2012 11:48:38 -0400
parents 22d3c37b94a0
children 5985416cf4b8
files client/src/main/java/com/redhat/thermostat/client/Main.java client/src/main/java/com/redhat/thermostat/client/MainView.java client/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java client/src/main/java/com/redhat/thermostat/client/config/ClientPreferences.java client/src/main/java/com/redhat/thermostat/client/config/ConnectionConfiguration.java client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java client/src/main/java/com/redhat/thermostat/client/ui/ClientConfigurationController.java client/src/main/java/com/redhat/thermostat/client/ui/ClientConfigurationFrame.java client/src/main/java/com/redhat/thermostat/client/ui/ClientConfigurationView.java client/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties client/src/test/java/com/redhat/thermostat/client/config/ClientPreferencesTest.java client/src/test/java/com/redhat/thermostat/client/ui/ClientConfigurationControllerTest.java client/src/test/java/com/redhat/thermostat/client/ui/ClientConfigurationFrameTest.java client/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java
diffstat 15 files changed, 759 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/main/java/com/redhat/thermostat/client/Main.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/java/com/redhat/thermostat/client/Main.java	Thu Apr 12 11:48:38 2012 -0400
@@ -47,6 +47,7 @@
 import javax.swing.JPopupMenu;
 import javax.swing.SwingUtilities;
 
+import com.redhat.thermostat.client.config.ClientPreferences;
 import com.redhat.thermostat.client.config.ConnectionConfiguration;
 import com.redhat.thermostat.client.locale.LocaleResources;
 import com.redhat.thermostat.client.ui.ConnectionSelectionDialog;
@@ -80,7 +81,8 @@
             System.exit(-1);
         }
 
-        StartupConfiguration config = new ConnectionConfiguration();
+        ClientPrefs prefs = new ClientPreferences();
+        StartupConfiguration config = new ConnectionConfiguration(prefs);
         
         StorageProvider connProv = new MongoStorageProvider(config);
         DAOFactory daoFactory = new MongoDAOFactory(connProv);
--- a/client/src/main/java/com/redhat/thermostat/client/MainView.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/java/com/redhat/thermostat/client/MainView.java	Thu Apr 12 11:48:38 2012 -0400
@@ -43,6 +43,7 @@
 
     enum Action {
         HOST_VM_TREE_FILTER,
+        SHOW_CLIENT_CONFIG,
         SHUTDOWN
     }
 
--- a/client/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Thu Apr 12 11:48:38 2012 -0400
@@ -38,8 +38,11 @@
 
 import java.util.Collection;
 import java.util.concurrent.TimeUnit;
-import java.util.logging.Logger;
 
+import com.redhat.thermostat.client.config.ClientPreferences;
+import com.redhat.thermostat.client.ui.ClientConfigurationController;
+import com.redhat.thermostat.client.ui.ClientConfigurationFrame;
+import com.redhat.thermostat.client.ui.ClientConfigurationView;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.Timer;
@@ -49,7 +52,6 @@
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.utils.LoggingUtils;
 
 public class MainWindowControllerImpl implements MainWindowController {
 
@@ -85,7 +87,7 @@
         public Collection<VmRef> getVMs(HostRef host) {
             return vmsDAO.getVMs(host);
         }
-        
+
     }
 
     private void initializeTimer() {
@@ -135,6 +137,9 @@
                     String filter = view.getHostVmTreeFilter();
                     setHostVmTreeFilter(filter);
                     break;
+                case SHOW_CLIENT_CONFIG:
+                    showConfigureClientPreferences();
+                    break;
                 case SHUTDOWN:
                     stop();
                     break;
@@ -142,7 +147,6 @@
                     assert false;
                 }
             }
-            
         });
     }
 
@@ -151,4 +155,10 @@
         view.showMainWindow();
     }
 
+    private void showConfigureClientPreferences() {
+        ClientPreferences prefs = new ClientPreferences();
+        ClientConfigurationView view = new ClientConfigurationFrame();
+        ClientConfigurationController controller = new ClientConfigurationController(prefs, view);
+        controller.showDialog();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/config/ClientPreferences.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.config;
+
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+public class ClientPreferences {
+
+    private static final String CONNECTION_URL = "connection-url";
+
+    private final Preferences prefs;
+
+    public ClientPreferences() {
+        this(Preferences.userRoot().node("thermostat"));
+    }
+
+    ClientPreferences(Preferences prefs) {
+        this.prefs = prefs;
+    }
+
+    public String getConnectionUrl() {
+        return prefs.get(CONNECTION_URL, "mongodb://127.0.0.1:27518");
+    }
+
+    public void setConnectionUrl(String url) {
+        prefs.put(CONNECTION_URL, url);
+    }
+
+    public void flush() throws BackingStoreException {
+        prefs.flush();
+    }
+}
--- a/client/src/main/java/com/redhat/thermostat/client/config/ConnectionConfiguration.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/java/com/redhat/thermostat/client/config/ConnectionConfiguration.java	Thu Apr 12 11:48:38 2012 -0400
@@ -40,10 +40,14 @@
 
 public class ConnectionConfiguration implements StartupConfiguration {
 
+    private final ClientPreferences clientPrefs;
+
+    public ConnectionConfiguration(ClientPreferences clientPrefs) {
+        this.clientPrefs = clientPrefs;
+    }
+
     @Override
     public String getDBConnectionString() {
-        // TODO: this needs to be read from preferences or from the agent
-        // configuration
-        return "mongodb://127.0.0.1:27518";
+        return clientPrefs.getConnectionUrl();
     }
 }
--- a/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Thu Apr 12 11:48:38 2012 -0400
@@ -57,6 +57,7 @@
     MENU_FILE_EXIT,
     MENU_EDIT,
     MENU_EDIT_CONFIGURE_AGENT,
+    MENU_EDIT_CONFIGURE_CLIENT,
     MENU_HELP,
     MENU_HELP_ABOUT,
 
@@ -178,7 +179,12 @@
 
     CONFIGURE_AGENT_WINDOW_TITLE,
     CONFIGURE_AGENT_AGENTS_LIST,
-    CONFIGURE_ENABLE_BACKENDS;
+    CONFIGURE_ENABLE_BACKENDS,
+
+    CLIENT_PREFS_WINDOW_TITLE,
+    CLIENT_PREFS_GENERAL,
+    CLIENT_PREFS_STORAGE_URL,
+    ;
 
     static final String RESOURCE_BUNDLE =
             "com.redhat.thermostat.client.locale.strings";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/ClientConfigurationController.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.prefs.BackingStoreException;
+
+import com.redhat.thermostat.client.config.ClientPreferences;
+import com.redhat.thermostat.client.ui.ClientConfigurationView.Action;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class ClientConfigurationController implements ActionListener<Action> {
+
+    private static final Logger logger = LoggingUtils.getLogger(ClientConfigurationController.class);
+
+    private final ClientConfigurationView view;
+    private final ClientPreferences model;
+
+    public ClientConfigurationController(ClientPreferences model, ClientConfigurationView view) {
+        this.model = model;
+        this.view = view;
+
+        view.addListener(this);
+    }
+
+    public void showDialog() {
+        updateViewFromModel();
+        view.showDialog();
+    }
+
+    private void updateViewFromModel() {
+        view.setConnectionUrl(model.getConnectionUrl());
+    }
+
+    private void updateModelFromView() {
+        model.setConnectionUrl(view.getConnectionUrl());
+
+        try {
+            model.flush();
+        } catch (BackingStoreException e) {
+            logger.log(Level.WARNING, "error saving client preferences", e);
+        }
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent<Action> actionEvent) {
+        if (actionEvent.getSource() != view) {
+            return;
+        }
+
+        switch (actionEvent.getActionId()) {
+            case CLOSE_ACCEPT:
+                updateModelFromView();
+                /* fall through */
+            case CLOSE_CANCEL:
+                view.hideDialog();
+                break;
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/ClientConfigurationFrame.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.locale.Translate.localize;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.LayoutStyle.ComponentPlacement;
+
+import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+
+public class ClientConfigurationFrame extends JFrame implements ClientConfigurationView, java.awt.event.ActionListener {
+
+    private final JTextField storageUrl;
+    private final JButton btnOk;
+    private final JButton btnCancel;
+
+    private CopyOnWriteArrayList<ActionListener<Action>> listeners = new CopyOnWriteArrayList<>();
+
+    public ClientConfigurationFrame() {
+        setTitle(localize(LocaleResources.CLIENT_PREFS_WINDOW_TITLE));
+
+        btnOk = new JButton(localize(LocaleResources.BUTTON_OK));
+        btnOk.addActionListener(this);
+        btnOk.setName("ok");
+        btnCancel = new JButton(localize(LocaleResources.BUTTON_CANCEL));
+        btnCancel.addActionListener(this);
+        btnCancel.setName("cancel");
+        JLabel lblClientConfiguration = new JLabel(localize(LocaleResources.CLIENT_PREFS_GENERAL));
+
+        JLabel lblStorageUrl = new JLabel(localize(LocaleResources.CLIENT_PREFS_STORAGE_URL));
+
+        storageUrl = new JTextField();
+        storageUrl.setColumns(10);
+        storageUrl.setName("connectionUrl");
+
+        GroupLayout groupLayout = new GroupLayout(getContentPane());
+        groupLayout.setHorizontalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
+                    .addContainerGap(251, Short.MAX_VALUE)
+                    .addComponent(btnCancel)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(btnOk)
+                    .addContainerGap())
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addGroup(groupLayout.createSequentialGroup()
+                            .addGap(12)
+                            .addComponent(lblStorageUrl)
+                            .addPreferredGap(ComponentPlacement.UNRELATED)
+                            .addComponent(storageUrl, GroupLayout.DEFAULT_SIZE, 305, Short.MAX_VALUE))
+                        .addComponent(lblClientConfiguration))
+                    .addContainerGap())
+        );
+        groupLayout.setVerticalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(lblClientConfiguration)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
+                        .addComponent(lblStorageUrl)
+                        .addComponent(storageUrl, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
+                    .addPreferredGap(ComponentPlacement.RELATED, 185, Short.MAX_VALUE)
+                    .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
+                        .addComponent(btnOk)
+                        .addComponent(btnCancel))
+                    .addContainerGap())
+        );
+        getContentPane().setLayout(groupLayout);
+    }
+
+    @Override
+    public void showDialog() {
+        assertInEDT();
+        this.pack();
+        this.setVisible(true);
+    }
+
+    @Override
+    public void hideDialog() {
+        assertInEDT();
+
+        this.setVisible(false);
+        this.dispose();
+    }
+
+    @Override
+    public String getConnectionUrl() {
+        assertInEDT();
+        return storageUrl.getText();
+    }
+
+    @Override
+    public void setConnectionUrl(final String url) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                storageUrl.setText(url);
+            }
+        });
+    }
+
+    @Override
+    public void actionPerformed(java.awt.event.ActionEvent e) {
+        System.out.println("swing action performed");
+        if (e.getSource() == btnOk) {
+            fireAction(new ActionEvent<>(this, Action.CLOSE_ACCEPT));
+        } else if (e.getSource() == btnCancel) {
+            fireAction(new ActionEvent<>(this, Action.CLOSE_CANCEL));
+        }
+    }
+
+    @Override
+    public void addListener(ActionListener<Action> listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeListener(ActionListener<Action> listener) {
+        listeners.remove(listener);
+    }
+
+    private void fireAction(ActionEvent<Action> actionEvent) {
+        System.out.println("thermostat-action fired");
+        for (ActionListener<Action> listener: listeners) {
+            listener.actionPerformed(actionEvent);
+        }
+    }
+
+    private void assertInEDT() {
+        if (!SwingUtilities.isEventDispatchThread()) {
+            throw new IllegalStateException("must be invoked in the EDT");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/ClientConfigurationView.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import com.redhat.thermostat.common.ActionListener;
+
+public interface ClientConfigurationView {
+
+    enum Action {
+        CLOSE_CANCEL,
+        CLOSE_ACCEPT,
+    }
+
+    void addListener(ActionListener<Action> listener);
+
+    void removeListener(ActionListener<Action> listener);
+
+    void setConnectionUrl(String url);
+    String getConnectionUrl();
+
+    void showDialog();
+    void hideDialog();
+
+}
--- a/client/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java	Thu Apr 12 11:48:38 2012 -0400
@@ -307,6 +307,16 @@
         });
         editMenu.add(configureAgentMenuItem);
 
+        JMenuItem configureClientMenuItem = new JMenuItem(localize(LocaleResources.MENU_EDIT_CONFIGURE_CLIENT));
+        configureClientMenuItem.setName("showClientConfig");
+        configureClientMenuItem.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                fireViewAction(Action.SHOW_CLIENT_CONFIG);
+            }
+        });
+        editMenu.add(configureClientMenuItem);
+
         JMenu helpMenu = new JMenu(localize(LocaleResources.MENU_HELP));
         helpMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
         mainMenuBar.add(helpMenu);
--- a/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Thu Apr 12 11:48:38 2012 -0400
@@ -17,6 +17,7 @@
 MENU_FILE_EXIT = Exit
 MENU_EDIT = Edit
 MENU_EDIT_CONFIGURE_AGENT = Configure Agent...
+MENU_EDIT_CONFIGURE_CLIENT = Client Preferences...
 MENU_HELP = Help
 MENU_HELP_ABOUT = About
 
@@ -140,4 +141,8 @@
 
 CONFIGURE_AGENT_WINDOW_TITLE = Configure Agent Backends
 CONFIGURE_AGENT_AGENTS_LIST = Agents
-CONFIGURE_ENABLE_BACKENDS = Enable/Disable Backends
\ No newline at end of file
+CONFIGURE_ENABLE_BACKENDS = Enable/Disable Backends
+
+CLIENT_PREFS_WINDOW_TITLE = Preferences
+CLIENT_PREFS_GENERAL = General
+CLIENT_PREFS_STORAGE_URL = Storage Url
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/config/ClientPreferencesTest.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import java.util.prefs.Preferences;
+
+import org.junit.Test;
+
+public class ClientPreferencesTest {
+
+    @Test
+    public void testGetConnectionUrl() {
+
+        Preferences prefs = mock(Preferences.class);
+        when(prefs.get(eq("connection-url"), any(String.class))).thenReturn("mock-value");
+
+        ClientPreferences clientPrefs = new ClientPreferences(prefs);
+        String value = clientPrefs.getConnectionUrl();
+
+        assertEquals("mock-value", value);
+        verify(prefs).get(eq("connection-url"), any(String.class));
+    }
+
+    @Test
+    public void testSetConnectionUrl() {
+
+        Preferences prefs = mock(Preferences.class);
+
+        ClientPreferences clientPrefs = new ClientPreferences(prefs);
+        clientPrefs.setConnectionUrl("test");
+
+        verify(prefs).put(eq("connection-url"), eq("test"));
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/ui/ClientConfigurationControllerTest.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.config.ClientPreferences;
+import com.redhat.thermostat.common.ActionEvent;
+
+public class ClientConfigurationControllerTest {
+
+    @Test
+    public void verifyShowDialog() {
+        ClientPreferences model = mock(ClientPreferences.class);
+        when(model.getConnectionUrl()).thenReturn("mock-connection-url");
+
+        ClientConfigurationView view = mock(ClientConfigurationView.class);
+        ClientConfigurationController controller = new ClientConfigurationController(model, view);
+
+        controller.showDialog();
+
+        verify(model).getConnectionUrl();
+        verify(view).setConnectionUrl(eq("mock-connection-url"));
+        verify(view).showDialog();
+    }
+
+    @Test
+    public void verifyCloseCancel() {
+        ClientPreferences model = mock(ClientPreferences.class);
+        ClientConfigurationView view = mock(ClientConfigurationView.class);
+        ClientConfigurationController controller = new ClientConfigurationController(model, view);
+
+        controller.actionPerformed(new ActionEvent<>(view, ClientConfigurationView.Action.CLOSE_CANCEL));
+
+        verify(model, times(0)).setConnectionUrl(any(String.class));
+        verify(view, times(0)).getConnectionUrl();
+        verify(view, times(0)).showDialog();
+        verify(view).hideDialog();
+    }
+
+    @Test
+    public void verifyCloseAccept() {
+        ClientPreferences model = mock(ClientPreferences.class);
+        ClientConfigurationView view = mock(ClientConfigurationView.class);
+        when(view.getConnectionUrl()).thenReturn("mock-connection-url");
+        ClientConfigurationController controller = new ClientConfigurationController(model, view);
+
+        controller.actionPerformed(new ActionEvent<>(view, ClientConfigurationView.Action.CLOSE_ACCEPT));
+
+        verify(model).setConnectionUrl(eq("mock-connection-url"));
+        verify(view).getConnectionUrl();
+        verify(view, times(0)).showDialog();
+        verify(view).hideDialog();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/ui/ClientConfigurationFrameTest.java	Thu Apr 12 11:48:38 2012 -0400
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.fest.swing.annotation.GUITest;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JButtonFixture;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+
+public class ClientConfigurationFrameTest {
+
+    private ClientConfigurationFrame frame;
+    private FrameFixture frameFixture;
+    private ActionListener<ClientConfigurationView.Action> l;
+
+    @Before
+    public void setUp() {
+        frame = new ClientConfigurationFrame();
+        l = mock(ActionListener.class);
+        frame.addListener(l);
+        frameFixture = new FrameFixture(frame);
+
+    }
+
+    @After
+    public void tearDown() {
+        frameFixture.cleanUp();
+        frame.removeListener(l);
+        frame = null;
+        l = null;
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void testOkayButton() {
+        frameFixture.show();
+
+        JButtonFixture button = frameFixture.button("ok");
+        button.click();
+
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                frame.hideDialog();
+            }
+        });
+
+        verify(l).actionPerformed(eq(new ActionEvent<>(frame, ClientConfigurationView.Action.CLOSE_ACCEPT)));
+
+
+    }
+
+    @Category(GUITest.class)
+    @Test
+    public void testCancelButton() {
+        frameFixture.show();
+
+        JButtonFixture button = frameFixture.button("cancel");
+        button.click();
+
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                frame.hideDialog();
+            }
+        });
+
+        verify(l).actionPerformed(eq(new ActionEvent<>(frame, ClientConfigurationView.Action.CLOSE_CANCEL)));
+
+    }
+}
--- a/client/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java	Thu Apr 05 15:45:55 2012 -0400
+++ b/client/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java	Thu Apr 12 11:48:38 2012 -0400
@@ -49,6 +49,7 @@
 import org.fest.swing.edt.GuiActionRunner;
 import org.fest.swing.edt.GuiTask;
 import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JMenuItemFixture;
 import org.fest.swing.fixture.JTextComponentFixture;
 import org.fest.swing.junit.v4_5.runner.GUITestRunner;
 import org.hamcrest.Description;
@@ -147,7 +148,7 @@
     @Test
     public void verifyShowMainWindowShowsWindow() {
         GuiActionRunner.execute(new GuiTask() {
-            
+
             @Override
             protected void executeInEDT() throws Throwable {
                 window.showMainWindow();
@@ -158,6 +159,19 @@
 
     @Category(GUITest.class)
     @Test
+    public void verifyThatClientPreferencesMenuItemTriggersEvent() {
+        frameFixture.show();
+        JMenuItemFixture menuItem = frameFixture.menuItem("showClientConfig");
+        menuItem.click();
+        frameFixture.close();
+        frameFixture.requireNotVisible();
+
+        verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SHOW_CLIENT_CONFIG));
+
+    }
+
+    @Category(GUITest.class)
+    @Test
     public void testGetHostVMTreeFilter() {
         frameFixture.show();
         JTextComponentFixture hostVMTreeFilterField = frameFixture.textBox("hostVMTreeFilter");
@@ -166,5 +180,5 @@
         assertEquals("test", window.getHostVmTreeFilter());
     }
 
-    
+
 }