changeset 1103:27b8deb8a6b2

Refactor theme management review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-May/006692.html reviewed-by: vanaltj, obryan
author Mario Torre <neugens.limasoftware@gmail.com>
date Tue, 21 May 2013 20:13:26 +0200
parents 8757e35030f2
children cf2033a6b021
files client/swing/pom.xml client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java distribution/assembly/core-assembly.xml distribution/config/commands/gui.properties distribution/pom.xml laf-utils/Makefile laf-utils/pom.xml laf-utils/src/main/java/com/redhat/thermostat/internal/utils/laf/ThemeManager.java laf-utils/src/main/java/com/redhat/thermostat/internal/utils/laf/gtk/GTKThemeUtils.java laf-utils/src/main/native/GTKThemeUtils.c pom.xml vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingViewTest.java
diffstat 13 files changed, 622 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/client/swing/pom.xml	Tue May 21 12:43:17 2013 -0400
+++ b/client/swing/pom.xml	Tue May 21 20:13:26 2013 +0200
@@ -58,6 +58,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-laf-utils</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-swing-components</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -126,7 +131,6 @@
       <scope>test</scope>
     </dependency>
   </dependencies>
-
   <build>
 
     <resources>
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java	Tue May 21 12:43:17 2013 -0400
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java	Tue May 21 20:13:26 2013 +0200
@@ -44,12 +44,7 @@
 import java.util.logging.Logger;
 
 import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
-import javax.swing.UnsupportedLookAndFeelException;
-import javax.swing.plaf.nimbus.NimbusLookAndFeel;
 
 import org.osgi.framework.BundleContext;
 
@@ -62,6 +57,7 @@
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.common.locale.Translate;
 import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.internal.utils.laf.ThemeManager;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
 import com.redhat.thermostat.storage.core.DbService;
@@ -111,62 +107,14 @@
         this.shutdown = shutdown;
         this.mainWindowRunnable = mainWindowRunnable;
     }
-
-    private void setLAF() {
-        
-        boolean useDefault = false;
-        
-        // check if the user has other preferences...
-        String laf = System.getProperty("swing.defaultlaf");
-        if (laf == null) {
-            useDefault = true;
-            
-        } else if (laf.equalsIgnoreCase("dolphin")) {
-            try {
-                UIManager.setLookAndFeel("com.redhat.swing.laf.dolphin.DolphinLookAndFeel");
-            } catch (UnsupportedLookAndFeelException | ClassNotFoundException |
-                     InstantiationException | IllegalAccessException e) {
-                useDefault = true;
-                logger.log(Level.WARNING, "cannot set DolphinLookAndFeel");
-            }
-        } else if (laf.equalsIgnoreCase("system")) {
-            try {
-                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-            } catch (UnsupportedLookAndFeelException | ClassNotFoundException |
-                     InstantiationException | IllegalAccessException e) {
-                useDefault = true;
-                logger.log(Level.WARNING, "cannot set System LookAndFeel");
-            }
-        }
-        
-        if (useDefault) {
-            try {
-                UIManager.setLookAndFeel(new NimbusLookAndFeel());
-            } catch (UnsupportedLookAndFeelException e) {
-                // well, whatever...
-                logger.log(Level.WARNING, "cannot set NimbusLookAndFeel");
-            }
-        }
-    }
     
     public void run() {
         EventQueue.invokeLater(new Runnable() {
 
             @Override
             public void run() {
-
-                setLAF();
-
-                // Thermostat JPopupMenu instances should all be
-                // ThermostatPopupmenu, so this is redundant, but done in case
-                // some client code doesn't use the internal popup
-                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
-                
-                // TODO: move them in an appropriate place
-                UIManager.getDefaults().put("OptionPane.buttonOrientation", SwingConstants.RIGHT);
-                UIManager.getDefaults().put("OptionPane.isYesLast", true);
-                UIManager.getDefaults().put("OptionPane.sameSizeButtons", true);
-                
+                ThemeManager themeManager = ThemeManager.getInstance();
+                themeManager.setLAF();
             }
 
         });
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Tue May 21 12:43:17 2013 -0400
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Tue May 21 20:13:26 2013 +0200
@@ -99,7 +99,7 @@
         
         Class<?>[] deps = new Class<?>[] {
                 Keyring.class,
-                ApplicationService.class
+                ApplicationService.class,
         };
         dependencyTracker = new MultipleServiceTracker(context, deps, new Action() {
             
@@ -109,7 +109,6 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 Keyring keyring = (Keyring) services.get(Keyring.class.getName());
                 ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
-                
                 cmdReg = new CommandRegistryImpl(context);
                 main = new Main(context, keyring, appSvc, new String[0]);
                 
--- a/distribution/assembly/core-assembly.xml	Tue May 21 12:43:17 2013 -0400
+++ b/distribution/assembly/core-assembly.xml	Tue May 21 20:13:26 2013 +0200
@@ -69,6 +69,7 @@
         <include>com.redhat.thermostat:thermostat-web-cmd</include>
         <include>com.redhat.thermostat:thermostat-web-server</include>
         <include>com.redhat.thermostat:thermostat-system-backend</include>
+        <include>com.redhat.thermostat:thermostat-laf-utils</include>
         <include>org.eclipse.jetty:jetty-server</include>
         <include>org.eclipse.jetty:jetty-webapp</include>
       </includes>
--- a/distribution/config/commands/gui.properties	Tue May 21 12:43:17 2013 -0400
+++ b/distribution/config/commands/gui.properties	Tue May 21 20:13:26 2013 +0200
@@ -12,6 +12,7 @@
           thermostat-client-cli-@project.version@.jar, \
           thermostat-client-swing-@project.version@.jar, \
           thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-utils-@project.version@.jar, \
           thermostat-client-command-@project.version@.jar, \
           thermostat-killvm-client-swing-@project.version@.jar, \
           thermostat-osgi-living-vm-filter-core-@project.version@.jar, \
--- a/distribution/pom.xml	Tue May 21 12:43:17 2013 -0400
+++ b/distribution/pom.xml	Tue May 21 20:13:26 2013 +0200
@@ -233,6 +233,8 @@
                       todir="${project.build.directory}/libs/native" />
                 <copy file="${main.basedir}/agent/core/target/libUserNameUtilWrapper.so"
                       todir="${project.build.directory}/libs/native" />
+                <copy file="${main.basedir}/laf-utils/target/libGTKThemeUtils.so"
+                      todir="${project.build.directory}/libs/native" />                      
               </target>
             </configuration>
             <goals>
@@ -427,6 +429,11 @@
     </dependency>
     <dependency>
         <groupId>com.redhat.thermostat</groupId>
+        <artifactId>thermostat-laf-utils</artifactId>
+        <version>${project.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>com.redhat.thermostat</groupId>
         <artifactId>thermostat-web-client</artifactId>
         <version>${project.version}</version>
     </dependency>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laf-utils/Makefile	Tue May 21 20:13:26 2013 +0200
@@ -0,0 +1,48 @@
+CC         = gcc
+JAVAH      = javah
+MYCFLAGS   = -c -Wall -fPIC
+MYLDFLAGS  = -fPIC -shared
+COPY       = cp -a
+
+CLASSPATH  = target/classes/
+TARGET_DIR = target
+
+INCLUDE    = -I $(TARGET_DIR) -I $(JAVA_HOME)/include/ -I $(JAVA_HOME)/include/linux
+SOURCES    = src/main/native/GTKThemeUtils.c
+TARGET     = $(TARGET_DIR)/GTKThemeUtils.c
+OBJECTS    = $(TARGET:.c=.o)
+
+EXECUTABLE = libGTKThemeUtils.so
+
+MYCFLAGS   += `pkg-config --cflags gtk+-2.0`
+MYCFLAGS   += `pkg-config gthread-2.0 --cflags`
+
+MYLDFLAGS  += `pkg-config --libs gtk+-2.0`
+MYLDFLAGS  += `pkg-config gthread-2.0 --libs`
+
+.PHONY:
+JNI_LIST = com.redhat.thermostat.internal.utils.laf.gtk.GTKThemeUtils
+
+$(JNI_LIST):
+	$(JAVAH) -force -classpath $(CLASSPATH) -d $(TARGET_DIR) $(JNI_LIST)
+
+all: $(JNI_LIST) init $(SOURCES) $(EXECUTABLE)
+
+.PHONY:
+init:
+	$(COPY) $(SOURCES) $(TARGET)
+
+$(EXECUTABLE): $(OBJECTS)
+	$(CC) $(OBJECTS) -o $(TARGET_DIR)/$@ $(MYLDFLAGS) $(LDFLAGS)
+
+.c.o:
+	$(CC) $(MYCFLAGS) $(CFLAGS) $(INCLUDE) $< -o $@
+
+clean-lib:
+	rm $(TARGET_DIR)/$(EXECUTABLE)
+	
+clean-obj:
+	rm $(OBJECTS) $(TARGET)
+	
+clean: clean-obj clean-lib
+	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laf-utils/pom.xml	Tue May 21 20:13:26 2013 +0200
@@ -0,0 +1,114 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>thermostat</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.8.0-SNAPSHOT</version>
+  </parent>
+  
+  <artifactId>thermostat-laf-utils</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat Look And Feel Utils</name>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+	        <Bundle-SymbolicName>com.redhat.thermostat.internal.utils.laf</Bundle-SymbolicName>
+	        <Private-Package>
+	           com.redhat.thermostat.internal.utils.laf.gtk,
+	        </Private-Package>
+            <Export-Package>
+                com.redhat.thermostat.internal.utils.laf,
+            </Export-Package>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.2.1</version>
+        <executions>  
+          <execution>
+            <phase>compile</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+          </execution>  
+        </executions>
+        <configuration>
+          <executable>make</executable>
+          <arguments>
+            <argument>all</argument>
+          </arguments>
+          <systemProperties>
+            <systemProperty>
+              <key>JAVA_HOME</key>
+              <value>${java.home}</value>
+            </systemProperty>
+          </systemProperties>
+        </configuration>
+      </plugin>
+     
+    </plugins>
+    
+    <pluginManagement>
+	  <plugins>
+        <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+        <plugin>
+          <groupId>org.eclipse.m2e</groupId>
+          <artifactId>lifecycle-mapping</artifactId>
+          <version>1.0.0</version>
+          <configuration>
+            <lifecycleMappingMetadata>
+              <pluginExecutions>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>exec-maven-plugin</artifactId>
+                    <versionRange>[1.2.1,)</versionRange>
+                    <goals>
+                      <goal>exec</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore></ignore>
+                  </action>
+                </pluginExecution>
+              </pluginExecutions>
+            </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laf-utils/src/main/java/com/redhat/thermostat/internal/utils/laf/ThemeManager.java	Tue May 21 20:13:26 2013 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.internal.utils.laf;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.JPopupMenu;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import javax.swing.plaf.nimbus.NimbusLookAndFeel;
+
+import com.redhat.thermostat.internal.utils.laf.gtk.GTKThemeUtils;
+
+public class ThemeManager {
+    
+    private static final Logger logger = Logger.getLogger(ThemeManager.class.getSimpleName());
+    private static final ThemeManager theInstance = new ThemeManager();
+    
+    ThemeManager() {}
+    
+    public static ThemeManager getInstance() {
+        return theInstance;
+    }
+
+    private boolean setLAF(String laf) {
+
+        boolean set = false;
+        try {
+            UIManager.setLookAndFeel(laf);
+            set = true;
+        } catch (UnsupportedLookAndFeelException | ClassNotFoundException |
+                InstantiationException | IllegalAccessException e) {
+            logger.log(Level.WARNING, "cannot set look and feel {0}", laf);
+        }
+        return set;
+    }
+
+    public void setLAF() {
+        
+        boolean tryGTKColors = false;
+        
+        // check if the user has other preferences...
+        String laf = System.getProperty("swing.defaultlaf");
+        if (laf == null) {
+            laf = "nimbus";
+            tryGTKColors = true;
+        }
+
+        switch (laf) {
+            case "system":
+                laf = UIManager.getSystemLookAndFeelClassName();
+                break;
+            case "nimbus":
+                laf = NimbusLookAndFeel.class.getName();
+                break;
+            case "dolphin":
+                laf = "com.redhat.swing.laf.dolphin.DolphinLookAndFeel";
+                break;
+            default:
+                break;
+        }
+
+        if (!setLAF(laf)) {
+            setLAF(NimbusLookAndFeel.class.getName());
+        }
+        
+        // Thermostat JPopupMenu instances should all be
+        // ThermostatPopupmenu, so this is redundant, but done in case
+        // some client code doesn't use the internal popup
+        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+
+        UIManager.getDefaults().put("OptionPane.buttonOrientation", SwingConstants.RIGHT);
+        UIManager.getDefaults().put("OptionPane.sameSizeButtons", true);
+        
+        String desktop = System.getProperty("sun.desktop");
+        String os = System.getProperty("os.name");
+        if (os != null && os.equalsIgnoreCase("linux") &&
+            desktop != null && !desktop.equalsIgnoreCase("kde"))
+        {
+            UIManager.getDefaults().put("OptionPane.isYesLast", true);
+        }
+        
+        if (tryGTKColors && desktop != null && desktop.equalsIgnoreCase("gnome")) {
+            GTKThemeUtils utils = new GTKThemeUtils();
+            utils.setNimbusColours();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laf-utils/src/main/java/com/redhat/thermostat/internal/utils/laf/gtk/GTKThemeUtils.java	Tue May 21 20:13:26 2013 +0200
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.internal.utils.laf.gtk;
+
+import java.awt.Color;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+public class GTKThemeUtils {
+
+    private static boolean initialized;
+    static {
+        try {
+            System.loadLibrary("GTKThemeUtils");
+            initialized = init();
+        } catch (UnsatisfiedLinkError ignore) {}
+    }
+    
+    native private static boolean init();
+    native private static boolean hasColor(String id);
+    native private static int getColor(String id);
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static Color getColor(String name, float hOffset, float sOffset,
+                                 float bOffset, int aOffset) throws Exception
+    {    
+        Class derivedColorClass = Class.forName("javax.swing.plaf.nimbus.DerivedColor");
+        Constructor constructor = derivedColorClass.getDeclaredConstructor(new Class[] { 
+                String.class, float.class, float.class, float.class, int.class
+        });
+        
+        constructor.setAccessible(true);
+        
+        Color color = (Color) constructor.newInstance(new Object[] {
+                name, hOffset, sOffset, bOffset, aOffset
+        });
+        
+        return color;
+    }
+    
+    public static void deriveColor(Color color) throws Exception {
+        Method rederiveColorMethod = color.getClass().getMethod("rederiveColor");
+        rederiveColorMethod.setAccessible(true);
+        rederiveColorMethod.invoke(color);
+    }
+        
+    private Color deriveColor(String colorID, Color defaultColor, float bOffset) {
+
+        Color result = defaultColor;
+        
+        int bgColor = getColor(colorID);
+        Color bg = new Color(bgColor);
+            
+        float hOffset = 0.0f;
+        float sOffset = 0.0f;
+        int aOffset = 0;
+            
+        UIManager.put("gtk-color", bg);
+        try {
+            Color derivedColor = getColor("gtk-color", hOffset, sOffset, bOffset, aOffset);
+            deriveColor(derivedColor);
+            result = new Color(derivedColor.getRGB());
+                
+        } catch (Exception ignore) {}
+        
+        return result;
+    }
+    
+    public void setNimbusColours() {
+
+        if (!initialized) {
+            return;
+        }
+        
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                
+                // if we at least have the bg colour we can try the rest,
+                // otherwise, just skip everything and use nimbus defaults
+                if (hasColor("bg_color")) {
+
+                    // Those numbers are some kind of magic, they represent the
+                    // value, or brightness, in the HSV encoding of the colour.
+                    // The idea is to derive a darker version of the
+                    // base colour because nimbus will use a brighter version
+                    // for most components. The version used by nimbus does not
+                    // exactly match because nimbus use many multi-gradient
+                    // paints.
+                    float brightnessOffset = -.300f;
+                    
+                    Color nimbusBase = deriveColor("bg_color", UIManager.getDefaults().getColor("nimbusBase"), brightnessOffset);
+                    Color control = UIManager.getDefaults().getColor("control");
+                    int bgColor = getColor("bg_color");
+                    control = new Color(bgColor);
+                    
+                    Color info = control;
+                    
+                    UIManager.put("nimbusBase", nimbusBase);
+                    
+                    UIManager.put("control", control);
+                    UIManager.put("info", info);
+
+                    Color nimbusFocus = UIManager.getDefaults().getColor("nimbusFocus");
+                    if (hasColor("selected_bg_color")) {
+                        int fgColor = getColor("selected_bg_color");
+                        nimbusFocus = new Color(fgColor);
+                        
+                        UIManager.put("nimbusFocus", nimbusFocus);
+                        UIManager.put("nimbusSelectionBackground", nimbusFocus);
+                        UIManager.put("nimbusSelection", nimbusFocus);
+                        UIManager.put("menu", nimbusFocus);
+                        UIManager.put("Menu.background", nimbusFocus);
+                    }
+                }
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/laf-utils/src/main/native/GTKThemeUtils.c	Tue May 21 20:13:26 2013 +0200
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+#include "com_redhat_thermostat_internal_utils_laf_gtk_GTKThemeUtils.h"
+
+#include <jni.h>
+#include <glib.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <X11/Xlib.h>
+
+JNIEXPORT jboolean JNICALL
+Java_com_redhat_thermostat_internal_utils_laf_gtk_GTKThemeUtils_init
+    (JNIEnv *env, jclass GTKThemeUtils)
+{
+    int (*handler)();
+    int (*io_handler)();
+
+    // without this code we will get BadWindow
+    handler = XSetErrorHandler(NULL);
+    io_handler = XSetIOErrorHandler(NULL);
+
+    g_thread_init(NULL);
+    gdk_threads_init();
+
+    gdk_threads_enter();
+
+    gboolean result = gtk_init_check(NULL, NULL);
+
+    XSetErrorHandler(handler);
+    XSetIOErrorHandler(io_handler);
+
+    gdk_threads_leave();
+
+    return (result == TRUE) ? JNI_TRUE : JNI_FALSE;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_redhat_thermostat_internal_utils_laf_gtk_GTKThemeUtils_hasColor
+    (JNIEnv *env, jclass GTKThemeUtils, jstring jColourID)
+{
+    gdk_threads_enter();
+
+    const char *colourID = (*env)->GetStringUTFChars(env, jColourID, NULL);
+    gboolean result = FALSE;
+
+    GtkWidget *dummy = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    if (dummy == NULL) {
+        goto bailString;
+    }
+
+    gtk_widget_ensure_style(dummy);
+
+    GtkStyle *style = gtk_rc_get_style(dummy);
+    if (style == NULL) {
+        goto bailWidget;
+    }
+
+    GdkColor color;
+    result = gtk_style_lookup_color(style, colourID, &color);
+
+bailWidget:
+    gtk_widget_destroy(dummy);
+
+bailString:
+    (*env)->ReleaseStringUTFChars(env, jColourID, colourID);
+
+    gdk_threads_leave();
+
+    return (result == TRUE) ? JNI_TRUE : JNI_FALSE;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_redhat_thermostat_internal_utils_laf_gtk_GTKThemeUtils_getColor
+    (JNIEnv *env, jclass GTKThemeUtils, jstring jColourID)
+{
+
+    gdk_threads_enter();
+
+    const char *colourID = (*env)->GetStringUTFChars(env, jColourID, NULL);
+
+    GtkWidget *dummy = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    if (dummy == NULL) {
+        goto bailString;
+    }
+
+    gtk_widget_ensure_style(dummy);
+
+    GtkStyle *style = gtk_rc_get_style(dummy);
+    if (style == NULL) {
+        goto bailWidget;
+    }
+
+    jint pixel = 0L;
+    GdkColor color;
+    if (gtk_style_lookup_color(style, colourID, &color)) {
+        gdk_color_parse(colourID, &color);
+
+        // GTK uses 48 bits to represent colours, while we want a format with
+        // 8 bits per channel. We just save the most significant bits rather
+        // than trying to normalise the values.
+        jint r = color.red >> 8;
+        jint g = color.green >> 8;
+        jint b = color.blue >> 8;
+
+        pixel = (r << 16) | (g << 8) | b;
+
+        // fprintf(stderr, "bg colour: %x%x%x\n", r, g, b);
+        // fprintf(stderr, "bg colour: %x%x%x\n", color.red, color.green, color.blue);
+        // fprintf(stderr, "bg colour: %u%u %u\n", color.red, color.green, color.blue);
+    }
+
+bailWidget:
+    gtk_widget_destroy(dummy);
+
+bailString:
+    (*env)->ReleaseStringUTFChars(env, jColourID, colourID);
+
+    gdk_threads_leave();
+
+    return pixel;
+}
--- a/pom.xml	Tue May 21 12:43:17 2013 -0400
+++ b/pom.xml	Tue May 21 20:13:26 2013 +0200
@@ -149,6 +149,7 @@
     <module>vm-heap-analysis</module>
     <module>vm-jmx</module>
     <module>numa</module>
+    <module>laf-utils</module>
     <!-- development related modules -->
     <module>integration-tests</module>
     <module>dev</module>
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingViewTest.java	Tue May 21 12:43:17 2013 -0400
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingViewTest.java	Tue May 21 20:13:26 2013 +0200
@@ -187,8 +187,8 @@
             }
         });
         
-        assertFalse(result[0]);
-        
+        assertFalse(result[0]); 
+       
         final JPanelFixture panel = frame.panel(HeapChartPanel.class.getName());
         JPopupMenuFixture popup = panel.showPopupMenu();
         popup.click();