package nxm.sys.lib;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.metal.DefaultMetalTheme;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.metal.MetalTheme;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.MidasColor;

/** A class to hold a Color theme for Windows and Widgets. <br>

    IMPORTANT: Theme logic was moved to this LIB Theme to support an eventual split of the graphical
    and non-graphical NextMidas code into separate JDK modules. User code should generally continue
    to refer to the libg.Theme.  The lib.Theme constructor returns a libg.Theme object if the libg
    package is present.<br>

    <br>
    Since NeXtMidas 1.9.3 this class extends {@link MetalTheme} rather than {@link Object}. This
    change was made so that this "NeXtMidas Theme" can also be used as a theme for the "Metal" Look
    and Feel (the default look and feel for Java). Note that the Java look and feels (and the Metal
    themes) are intended to be used as global settings whereas the NeXtMidas themes can be used
    in a localized manner (since NeXtMidas 3.3.0).

    @author Jeff Schoen
    @since Theme method implementation moved here in NeXtMidas 3.9.2, but user code should still reference libg.Theme
*/
@InternalUseOnly("User code should reference libg.Theme, unless the user would like to break a dependency on our graphical code.")
public class Theme extends MetalTheme implements Cloneable {

  /** Commonly used Color constants used by NeXtMidas themes.
      @since NeXtMidas 3.5.3
   */
  @InternalUseOnly
  public static final Color DARK_BLUE = new Color(0x2A6AA0), // (i.e. RGB of 42,106,166)
                            DARK_GRAY = new Color(0xA0A0A0),
                                 BLUE = new Color(0x2f77ba), // 12% brighter than DARK_BLUE (i.e. RGB of 47, 119, 186)
                        STRONG_ORANGE = new Color(0xC88000);

  /** The default BLACK color. */
  private static final Color BLACK = new Color(0x080808);

  /** The default GRAY  color. */
  private static final Color GRAY  = new Color(0x808080);

  /** The default WHITE color. */
  private static final Color WHITE = new Color(0xD0D0D0);

  /** Mapping of non_case sensitive UIManager keys to all upper case (Table format) */
  public static HashMap<String,String> UIManagerKeysMapping = null;

  /** Java Metal Themes. <br>
      <br>
      These need to be manually specified because they are not automatically available within Swing.
      Each specifier holds three fields:
      <pre>
        [0] - The Java version the theme first appeared in.
        [1] - The name to use for the theme.
        [2] - The class name of the theme.
      </pre>
   */
  private static final String[][] METAL_THEMES
          = { { "1.4.0", "Java",  "javax.swing.plaf.metal.DefaultMetalTheme" },
              { "1.4.0", "Metal", "javax.swing.plaf.metal.DefaultMetalTheme" },
              { "1.5.0", "Ocean", "javax.swing.plaf.metal.OceanTheme"        } };

  /** The properties to list in a graphical query. */
  public static final String propertyList="Name,Descr,Inline,Font,Cbg,Cfg,Cwbg,Cwfg,Cwti,Cwfh,Cwts,Cwbs";

  /** This is the default font. */
  private static Font DEFAULT_FONT;

  /** This is the "Default" theme. */
  private static Theme DEFAULT_THEME;

  /** The global theme in use. */
  private static Theme globalTheme;
  private static final Class<?> LIBG_THEME_CLASS;
  private static final Class<?> CLAZZ_FOR_THEME_TO_INSTANTIATE;
  private static Constructor<?> THEME_CONSTRUCTOR;
  static {
    Class<?> libgThemeClass;
    try {
      libgThemeClass = Class.forName("nxm.sys.libg.Theme");
    } catch (ClassNotFoundException e) {
      libgThemeClass=null;
    }
    LIBG_THEME_CLASS=libgThemeClass;
    if(LIBG_THEME_CLASS!=null) {
      CLAZZ_FOR_THEME_TO_INSTANTIATE=LIBG_THEME_CLASS;
    }
    else {
      CLAZZ_FOR_THEME_TO_INSTANTIATE=Theme.class;
    }
    try {
      THEME_CONSTRUCTOR = CLAZZ_FOR_THEME_TO_INSTANTIATE.getConstructor(String.class,String.class);
    }
    catch (NoSuchMethodException|SecurityException ex) {
      System.out.println("WARNING: unable to get Theme constructor "+ex);
      THEME_CONSTRUCTOR = null;
    }
  }

  private static Theme libgGlobalTheme;
  /** The list of available theme names <code>{@value}</code> */
  public static final String nameList = "DeskTop,Default,WoB,GoB,BoW,MnM,Browser,Gear1,Gear2,Gear3,Gear4,Nak";

  /** The default global theme to be used when none is explicitly set. */
  public static final String defaultTheme = "GEAR3";

  /** Headless check. */
  private static boolean headless = false; // Actual value set when class initializes.
  private static int headlessInitStatus = 0; // Actual value set when class initializes.

  /** Initialized check. */
  private static boolean initialized = false;

  /** Graphics Modes. */
  public static final String optionsList = "Headless,AWT,Swing,RealTime,Mobile,SwingDB,SwingWidgets,SwingPopups,NoSLAF,NoXLAF,InitOnLoad,Debug,HeadlessDetected,SwingLegacy,Contrast";
  /** Windows (including Plots) not drawn. */
  public static final int HEADLESS = 0x1;
  /** Windows (including Plots) drawn on AWT panels. */
  public static final int AWT = 0x2;
  /** Windows (including Plots) drawn on SWING panels. */
  public static final int SWING = 0x4;
  /** Plot rendering drawn OUTSIDE of the SWING thread (EDT). */
  public static final int REALTIME = 0x8;
  /** enable options for Mobile clients. */
  public static final int MOBILE = 0x10;
  /** enable SWING Double Buffering. */
  public static final int SWINGDB = 0x20;
  /** whether to make GWidgets on a SWING panel. */
  public static final int SWINGWIDGETS = 0x40;
  /** whether to make GWidget popups on a SWING panel. */
  public static final int SWINGPOPUPS = 0x80;
  /** prevent from using Swing Look And Feel classes for lightweight graphics engines. Since 2.9.1. */
  public static final int NOSLAF = 0x100;
  /** prevent eXternal Swing Look And Feel changes via javax.swing.UIManager.setLookAndFeel()
      which can cause mis-match looking displays. Since 2.9.1. */
  public static final int NOXLAF = 0x200;
  /** Initialize graphics themes on load instead of on demand. Since 2.9.1. */
  public static final int INITONLOAD = 0x400;
  /** Turns debugging stuff on. Since 2.9.1. */
  public static final int DEBUG = 0x800;
  /** System detected HEADLESS scenario. Since 3.5.0 */
  public static final int HEADLESSDETECTED = 0x1000;
  /** enable SWING Legacy Support for paint() methods. Since 3.9.0 */
  public static final int SWINGLEGACY = 0x2000;
  /** enable high CONTRAST plot modes. Since 4.1.4 */
  public static final int CONTRAST = 0x4000;
  /** Default options (Global). usually over-ridden in the startup script */
  private static int options = SWING|SWINGDB|SWINGWIDGETS|SWINGPOPUPS|SWINGLEGACY; // default changed since NXM 3.9.0

  /** This is the set of all defined themes. */
  private static Table definedThemes = new Table();

  /** The name of the current theme.            */ public  String  name;
  /** The description of the current theme.     */ public  String  descr;
  /** True if no border for embedding           */ public  boolean inline;
  /** This is the font being used.              */ private Font    font;
  /** The color "black".                        */ public  Color   Cblack;
  /** The color "gray".                         */ public  Color   Cgray;
  /** The color "white".                        */ public  Color   Cwhite;
  /** The background color.                     */ public  Color   cbg;
  /** The foreground color.                     */ public  Color   cfg;
  /** The widget background color.              */ public  Color   cwbg;
  /** The widget foreground color.              */ public  Color   cwfg;
  /** The widget title color.                   */ public  Color   cwti;
  /** The widget fg highlight color.            */ public  Color   cwfh;
  /** The widget bg highlight color.            */ public  Color   cwbh;
  /** The widget shadow color (top).            */ public  Color   cwts;
  /** The widget shadow color (bottom).         */ public  Color   cwbs;
  /** menu shading?                             */ public  Color   cwms;
  /** background shading?                       */ public  Color   cbgx;

  /** List of the display names for the Theme components @since 3.5.0*/
  public static final String[][] COMPONENT_LIST =
      {{"cbg","Background color."}
      ,{"cfg","Foreground color."}
      ,{"cwbg","Widget background color."}
      ,{"cwfg","Widget foreground color."}
      ,{"cwti","Widget title color."}
      ,{"cwfh","Widget foreground highlight color."}
      ,{"cwbh","Widget background highlight color."}
      ,{"cwts","Widget shadow color (top)."}
      ,{"cwbs","Widget shadow color (bottom)."}
      ,{"cwms","Menu shading"}
      ,{"cbgx","Background shading"}};

  /** background color theme component */
  public static final String THEME_COMPONENT_CBG = "cbg";
  /** foreground color theme component */
  public static final String THEME_COMPONENT_CFG = "cfg";
  /** widget background color theme component */
  public static final String THEME_COMPONENT_CWBG = "cwbg";
  /** widget foreground color theme component */
  public static final String THEME_COMPONENT_CWFG = "cwfg";
  /** widget title color theme component */
  public static final String THEME_COMPONENT_CWTI = "cwti";
  /** widget foreground highlight color theme component */
  public static final String THEME_COMPONENT_CWFH = "cwfh";
  /** widget background highlight color theme component */
  public static final String THEME_COMPONENT_CWBH = "cwbh";
  /** widget shadow color (top) theme component */
  public static final String THEME_COMPONENT_CWTS = "cwts";
  /** widget shadow color (bottom) theme component */
  public static final String THEME_COMPONENT_CWBS = "cwbs";
  /** menu shading theme component */
  public static final String THEME_COMPONENT_CWMS = "cwms";
  /** background shading theme component */
  public static final String THEME_COMPONENT_CBGX = "cbgx";

  static final double DEFAULT_VISIBILITY_TOLERANCE = 0.077;
  /** Tolerance level for warning when testing the difference between two colors. since 3.5.0 */
  private double visibilityTolerance = DEFAULT_VISIBILITY_TOLERANCE;

  /** index for visibility test name */
  public static final int VISIBILITY_TEST_IDX_NAME = 0;
  /** index for visibility test component 1 */
  public static final int VISIBILITY_TEST_IDX_COMPONENT_1 = 1;
  /** index for visibility test component 2 */
  public static final int VISIBILITY_TEST_IDX_COMPONENT_2 = 2;
  /** index for visibility test solution */
  public static final int VISIBILITY_TEST_IDX_SOLUTION = 3;
  /** index for inverting colors of visibility test solution */
  public static final String VISIBILITY_TEST_SOLUTION_INVERT = "Invert";
  /** index for blending colors of visibility test solution */
  public static final String VISIBILITY_TEST_SOLUTION_BLEND = "Blend";

  /** The seconds to delay the graphics init */
  public static double graphicsDelay = 0;

  static {
    try {
      String graphicsProp = System.getProperty("GRAPHICS");
      if (graphicsProp != null) setOptions(graphicsProp);
      // graphics delay option added in 3.1.0 as workaround for Bug 2499
      String graphicsDelayProp = System.getProperty("GRAPHICSDELAY");
      if (graphicsDelayProp != null) {
        double delay = Double.parseDouble(graphicsDelayProp);
        if (delay > 0) graphicsDelay = delay;
      }

    } catch (Exception e) {
      warn(null, "Theme: Cannot get GRAPHICS property.", e);
    }
    if (getOption(INITONLOAD)) initialize();
  }

  /** Sets the initial theme for the GUI */
  public static void initialize() {
    if (initialized) return;
    initialized   = true;
    final Shell shell = Shell.getCurrent();
    final Midas midas = (shell==null)? null: shell.M;

    // The order of the next 5 lines is important.
    initHeadlessStatus();
    DEFAULT_FONT  = new Font("Monospaced",Font.PLAIN,12); // Changed to set the font regardless of headless mode since 3.9.0
    initThemes(midas);
    Class<?> clazz;
    try {
      clazz = Class.forName("nxm.sys.libg.Theme");
    } catch (ClassNotFoundException e1) {
      clazz = null;
    }
    if(clazz!=null) {
      try {
        DEFAULT_THEME = (Theme) clazz.getConstructor().newInstance(); // this returns the default theme of the libg.theme class
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
          | NoSuchMethodException | SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    else {
      DEFAULT_THEME = (Theme)definedThemes.get("DEFAULT");
    }
    globalTheme = DEFAULT_THEME;

    // now check the Midas environment
    if (midas!=null) {
      Table MR = midas.getResults();
      String tn = MR.getS("ENV.THEME");
      if (tn != null) { // 2012-08-14: since NeXtMidas 3.3.0 to prevent NPE when tn is null
        try { Theme.setGlobalTheme(tn, null); }
        catch (Exception e) {
          midas.warning("Theme: Cannot set global theme to ENV.THEME="+tn+"  exception="+e);
        }
        MR.put("ENV.GRAPHICS",getOptions(),Table.FORCE);
      }
    }

    if (getOption(NOXLAF) && !headless && (midas!=null)) {
      // since 2.9.0: skip the below (by default) for BUG 209
      UIManager.addPropertyChangeListener(new PropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent evt) { lookAndFeelChanged(midas); }
      });
    }

  }

  /** Gets the global theme.
   *  @return the global theme */
  public static Theme global() {
    initialize();
    return globalTheme;
  }

  /** Gets the list of valid theme names.
   *  @return list of valid theme names */
  public static String[] getThemeNames() {
    initialize();
    Object[] themes = definedThemes.iterator().values;
    String[] names  = new String[themes.length];
    for (int i = 0; i < themes.length; i++) {
      names[i] = ((Theme)themes[i]).getName();
    }
    return names;
  }

  /** set the options flags
     @param arg the new options flags in a bitmask
  */
  public static void setOptions (int arg) {
    options = arg;
  }

  /** set the options flags to one of {@link #optionsList}.
      @param arg A NeXtMidas parsable flags list in a string
  */
  public static void setOptions (String arg) {
    setOptions(Parser.mask(optionsList,arg,options));
  }

  /** Get the options flags as a string.
   *  @return the options flag as a string
   */
  public static String getOptions() {
    return Parser.mask2s(optionsList,options);
  }

  /** Check if the specified mask are enabled in the options flags.
   *  @param mask mask to check
   *  @return true if that option is enabled
   */
  public static boolean getOption (int mask) {
    return ((options&mask)!=0);
  }

  /** Checks to see the look and feel was changed correctly. */
  private static void lookAndFeelChanged (Midas midas) {
    String globalTheme  = getGlobalTheme();
    String currentTheme = getCurrentTheme();

    if (currentTheme == null) { return; } // Using Java 1.4.2 - see getCurrentTheme()

    if (globalTheme.equalsIgnoreCase("JAVA"))  { globalTheme  = "METAL"; }
    if (currentTheme.equalsIgnoreCase("JAVA")) { currentTheme = "METAL"; }

    if (!globalTheme.equalsIgnoreCase(currentTheme)) {
      midas.warning("THEME: Global look and feel illegally changed from '"+
        getGlobalTheme()+"' to '"+getCurrentTheme()+"' without using the methods in Theme.");
    }
  }

  /** Gets the current theme from Swing. */
  private static String getCurrentTheme () {
    LookAndFeel look = UIManager.getLookAndFeel();
    String      name = null;

    if (look instanceof MetalLookAndFeel) {
      MetalTheme metalTheme = MetalLookAndFeel.getCurrentTheme();
      if (metalTheme.getClass().equals(DefaultMetalTheme.class)) {
        name = "Metal";
      }
      else if (metalTheme instanceof Theme) {
        name = ((Theme)metalTheme).getName();
      }
      else {
        name = metalTheme.getClass().getName();

        int index  = Math.max(name.lastIndexOf("."), name.lastIndexOf("$"));

        if (index > 0) {
          name = name.substring(index+1);
          if (name.endsWith("Theme")) {
            name = name.substring(0,name.length()-5);
          }
        }
      }
    } else {
      name = look.getName();
    }

    return name;
  }

  /** Creates a default theme. */
  public Theme() {
    setByName("DEFAULT");
  }

  /** Creates one of the legacy themes. This constructor is private and should only be called from
      other constructors or when creating the set of legacy themes.
      @param name   The name of the current theme.
      @param descr  A description of the theme.
   */
  @InternalUseOnly
  public Theme (String name, String descr) {
    if (name == null) {
      this.name  = "Anonymous";
      this.descr = "An anonymous theme.";
    } else {
      this.name  = name;
      this.descr = (descr != null)? descr: "A theme for NeXtMidas.";
    }

    final String names = "," + nameList + ",";
    final String match = "," + name + ",";

    if (names.indexOf(match) >= 0) {
      setByName(name.toUpperCase());
      this.name = name;
    }
  }

  /** Creates a new theme. If any of the parameters (except <code>name</code>) are <code>null</code>
      then the parameter will default to that used by the default theme (<code>theme</code>). <br>
      <br>
      This constructor is private and subject to frequent changes.
      @param theme  The theme that defines the defaults values.
      @param name   The name of the current theme.
      @param descr  A description of the theme.
      @param inline No border since embedding in another display (affects cbg)
      @param font   This is the font being used.
      @param cbg    The background color.
      @param cfg    The foreground color.
      @param cwbg   The widget background color.
      @param cwfg   The widget foreground color.
      @param cwti   The widget title color.
      @param cwfh   The widget highlight color.
      @param cwts   The widget shadow color (top).
      @param cwbs   The widget shadow color (bottom).
   */
  private Theme (Theme theme, String name, String descr, boolean inline, Font font, Color cbg,
                 Color cfg,   Color cwbg,  Color cwfg,   Color cwti,   Color cwfh,  Color cwts, Color cwbs) {
    this (theme, name, descr, inline, font, cbg, cfg, Color.black, Color.gray, Color.white, cbg,
          cfg, cwbg, cwfg, cwti, cwfh, cwts, cwbs);
  }

  /** Creates a new theme. If any of the parameters (except <code>name</code>) are <code>null</code>
      then the parameter will default to that used by the default theme (<code>theme</code>). <br>
      <br>
      This constructor is private and subject to frequent changes.
      @param theme  The theme that defines the defaults values.
      @param name   The name of the current theme.
      @param descr  A description of the theme.
      @param inline No border since embedding in another display (affects cbg)
      @param font   This is the font being used.
      @param Cbg    The background color (deprecated).
      @param Cfg    The foreground color (deprecated).
      @param Cblack The color "black"    (deprecated).
      @param Cgray  The color "gray"     (deprecated).
      @param Cwhite The color "white"    (deprecated).
      @param cbg    The background color.
      @param cfg    The foreground color.
      @param cwbg   The widget background color.
      @param cwfg   The widget foreground color.
      @param cwti   The widget title color.
      @param cwfh   The widget highlight color.
      @param cwts   The widget shadow color (top).
      @param cwbs   The widget shadow color (bottom).
   */
  private Theme (Theme theme, String name, String descr, boolean inline, Font font,
                 Color Cbg,   Color Cfg,   Color Cblack, Color Cgray, Color Cwhite, Color cbg,
                 Color cfg,   Color cwbg,  Color cwfg,   Color cwti,  Color cwfh,  Color cwts,   Color cwbs) {
    this (name, descr);

    if (theme == null) theme = DEFAULT_THEME;

    this.font    = DEFAULT_FONT; /* Ignore font settings, at this time. */
//  this.Cbg     = (Cbg    != null)?   Cbg:      theme.Cbg;  // Since 3.7.1 stop using deprecated versions
//  this.Cfg     = (Cfg    != null)?   Cfg:      theme.Cfg;
    this.Cblack  = (Cblack != null)?   Cblack:   theme.Cblack;
    this.Cgray   = (Cgray  != null)?   Cgray:    theme.Cgray;
    this.Cwhite  = (Cwhite != null)?   Cwhite:   theme.Cwhite;
    this.cbg     = (cbg    != null)?   cbg:      theme.cbg;
    this.cfg     = (cfg    != null)?   cfg:      theme.cfg;
    this.cwbg    = (cwbg   != null)?   cwbg:     theme.cwbg;
    this.cwfg    = (cwfg   != null)?   cwfg:     theme.cwfg;
    this.cwti    = (cwti   != null)?   cwti:     theme.cwti;
    this.cwfh    = (cwfh   != null)?   cwfh:     theme.cwfh;
    this.cwts    = (cwts   != null)?   cwts:     theme.cwts;
    this.cwbs    = (cwbs   != null)?   cwbs:     theme.cwbs;

    this.cwms = MidasColor.getColorMix(this.cwbg, this.cwts, 0.5);
    this.cbgx = this.cbg;
    setInline(inline);
  }

  /** initialize from an existing theme */
  private void fromTheme (Theme theme) {
    this.font    = theme.font;
//  this.Cbg     = theme.Cbg;   Since 3.7.1 - stop using deprecated versions
//  this.Cfg     = theme.Cfg;
    this.Cblack  = theme.Cblack;
    this.Cgray   = theme.Cgray;
    this.Cwhite  = theme.Cwhite;
    this.cbg     = theme.cbg;
    this.cfg     = theme.cfg;
    this.cwbg    = theme.cwbg;
    this.cwfg    = theme.cwfg;
    this.cwti    = theme.cwti;
    this.cwfh    = theme.cwfh;
    this.cwts    = theme.cwts;
    this.cwbs    = theme.cwbs;
    this.cwms    = MidasColor.getColorMix(this.cwbg, this.cwts, 0.5);
    this.cbgx    = this.cbg;
    this.inline  = false;
  }


  /** Creates a new theme based on a table of theme parameters. <br>
      <br>
      The currently supported parameters include:
      <pre>
        THEME  - The name of the theme that defines the defaults values to use.
        NAME   - The name of the theme being created.
        DESCR  - A description of the theme being created.
        CBG    - The name of the background color.
        CFG    - The name of the foreground color.
        CWBG   - The name of the widget background color.
        CWFG   - The name of the widget foreground color.
        CWTI   - The name of the widget title color.
        CWFH   - The name of the widget highlight color.
        CWTS   - The name of the widget shadow color (top).
        CWBS   - The name of the widget shadow color (bottom).
      </pre>
      If any of the colors are not specified the corresponding color from the default theme
      (<code>THEME</code>) will be used.
      @param table The table of theme parameters.
   */
  protected Theme (Table table) {
    this (getTheme(table.getS("THEME")),
          table.getS("NAME"),
          table.getS("DESCR"),
          false, /* inline */
          null,  /* font   */
          getColor("CBG",  table),
          getColor("CFG",  table),
          getColor("CWBG", table),
          getColor("CWFG", table),
          getColor("CWTI", table),
          getColor("CWFH", table),
          getColor("CWTS", table),
          getColor("CWBS", table));
  }

  /** Creates a new theme based on a Swing Look and Feel.
      @param name The name of the Look and Feel.
      @param prop The properties of that look and feel.
   */
  @InternalUseOnly
  public Theme (String name, UIDefaults prop) {
    this (null, name,
         "A theme to match the Java/Swing '" + name + "' Look and Feel.",
          false,                             /* inline */
          null,                              /* font   */        // <-- JC4: Change this!
          prop.getColor("control"),          /* cbg    */
          prop.getColor("controlText"),      /* cfg    */
          prop.getColor("control"),          /* cwbg   */
          prop.getColor("controlText"),      /* cwfg   */
          prop.getColor("controlText"),      /* cwti jph -- J. Czechowski,should I use something else? */
          prop.getColor("controlHighlight"), /* cwfh   */
          prop.getColor("controlShadow"),    /* cwts   */
        // This prevents NPE where there is no "controlLightShadow" for the theme. It also uses controlLightShadow if it
        // exists as controlDkShadow can be too dark making the text and background black.
        // If there is none, then use the "controlDkShadow Since 3.9.0
          prop.getColor("controlLightShadow")!=null? prop.getColor("controlLightShadow") : prop.getColor("controlDkShadow")  /* cwbs   */);
  }

  /** This will create the set of legacy themes that will be added to the list of available themes at startup.
      @return The set of legacy themes.
   */
  private static final Theme[] getLegacyThemes() {
    /** Do not add new themes to this method, add them to the getDefaultThemes() method. */
    try {
      return new Theme[] { (Theme) THEME_CONSTRUCTOR.newInstance("DeskTop", "A desktop-like theme for NeXtMidas."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("Default", "The default NeXtMidas theme."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("WoB",     "A white on black theme for NeXtMidas."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("GoB",     "A gray on black theme for NeXtMidas."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("BoW",     "A black on white theme for NeXtMidas."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("MnM",     "A theme for mobile iPhone clients."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("Browser", "A web-browser-like theme for NeXtMidas."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("Gear1",   "A NeXtMidas theme."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("Gear2",   "A NeXtMidas theme: Gear1 except widget fg and title color is orange."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("Gear3",   "A common NeXtMidas theme: Gear2 except widget bg is black."),
                         (Theme) THEME_CONSTRUCTOR.newInstance("Gear4",   "A NeXtMidas theme: Gear3 except widget highlight is a slightly brighter blue."), // since 3.5.3 BUG 2893
                         (Theme) THEME_CONSTRUCTOR.newInstance("Nak",     "A NeXtMidas theme.") };
    } catch (SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
      return null;
    }
  }

  /** This will create the set of default themes that will be added to the list of available themes at startup.
      @return The set of default themes.
   */
  private static final Theme[] getDefaultThemes(Midas midas) {
    final ArrayList<Theme> themes = new ArrayList<>();

    // Add the SWING/Java's LookAndFeel based themes.
    final LookAndFeelInfo[] lookAndFeels = getLookAndFeels();

    for (LookAndFeelInfo lafinfo : lookAndFeels) {
      try {
        Theme theme = getThemeFor(lafinfo, midas);
        if (theme != null) {
          themes.add(theme);
        }
        else warn(midas, "Theme: Not adding to themes list: "+ lafinfo.getName() + " " + lafinfo.getClassName(), null);
      } catch (Exception e) {
        if (e instanceof UnsupportedLookAndFeelException) { // Jeff's addition since 3.9.0
          warn(midas, "Theme: Unsupported lookandfeel " + lafinfo, e);
        }
        else warn(midas, "Theme: Unable to add to themes list: " + lafinfo, e);
      }
    }

    // Java Metal Themes.
    for (int i=0; i < METAL_THEMES.length; i++) {
      try {
        if (StringUtil.compareVersions(METAL_THEMES[i][0], Shell.getJavaVersion()) <= 0) {
          final MetalTheme theme = (MetalTheme)Class.forName(METAL_THEMES[i][2]).getConstructor().newInstance(); // updated in 3.9.0 to use default constructor
          themes.add(getThemeFor(METAL_THEMES[i][1], theme));
        }
      } catch (Exception e) {
        warn(midas, "Theme: Unable to add to themes list: name=" + METAL_THEMES[i][1] + " class="+METAL_THEMES[i][2], e);
      }
    }

    // Insert any new default themes here.

    return themes.toArray(new Theme[0]);
  }

  /** Get a clone of this theme. Returns null if an exception is thrown.
   *  @return clone of this theme */
  public Theme copy() {
    try {
      return (Theme)this.clone();
    } catch (Exception e) {
      return null;
    }
  }

  /** Gets an instance of a named theme.
      @param name The name of the theme (not case sensitive).
      @return The specified theme, or <code>null</code> if not found.
   */
  public static Theme getTheme (final String name) {
    initialize();
    try {
      Theme theme = (Theme)definedThemes.get(name.toUpperCase());
      return (theme != null)? (Theme)theme.clone(): null;
    }
    catch (CloneNotSupportedException e) {
      throw new RuntimeException("This should be impossible, Theme is Clonable.", e);
    }
  }

  /** Gets an instance of a named theme. If the named theme is not found output an error message
      and return the default theme.
      @param name The name of the theme (not case sensitive).
      @return The specified theme, or <code>null</code> if not found.
   */
  static Theme getThemeSafeVersion (final String name) {
    Theme theme = (name != null)? getTheme(name): null;

    if (theme == null) {
      theme = DEFAULT_THEME;
      Shell.warning("Theme '" + name + "' not found. Available themes include: "
                   + StringUtil.join(getThemeNames()) + ".");
    }
    return theme;
  }

  /** Abbreviated string form showing name and description only. */
  @Override
  public String toString() {
    return name + ": " + descr;
  }

  /** Abbreviated string form showing name and description only.
      @param verbose Return longer form if true
      @return either verbose or regular String form
  */
  public String toString (boolean verbose) {
    if (verbose) {
      return  "Name     "+name  +"\n"
             +"Descr   "+descr  +"\n"
             +"cbg     "+cbg    +"\n"
             +"cfg     "+cfg    +"\n"
             +"cwbg    "+cwbg   +"\n"
             +"cwfg    "+cwfg   +"\n"
             +"cwti    "+cwti   +"\n"
             +"cwfh    "+cwfh   +"\n"
             +"cwts    "+cwts   +"\n"
             +"cwbs    "+cwts;
    } else {
      return toString();
    }
  }

  /** Set the current theme's parameters by name. If an invalid name is given a warning message will
      be sent to the Midas Shell and the theme will be set to <code>DESKTOP</code>.
      @param themeName One of the themes in {@link #nameList}.
   */
  public void setByName (String themeName) {
    name = themeName.toUpperCase();

    Cwhite = WHITE;
    Cgray  = GRAY;
    Cblack = BLACK;

//    Cfg  = Cwhite; // Since 3.7.1 - stop using deprecated versions
//    Cbg  = Cblack;

    font = DEFAULT_FONT;
    cbg = Cwhite;   // Since 3.7.1  - initialize directly without using deprecated version
    cfg = Cblack;

         if (name.startsWith("DES"));
    else if (name.startsWith("DEF")) { cbg=Cwhite; cfg=Cblack; }
    else if (name.startsWith("GOB")) { cbg=Cblack; cfg=Cgray; }
    else if (name.startsWith("WOB")) { cbg=Cblack; cfg=Cwhite; }
    else if (name.startsWith("MNM")) { cbg=Cblack; cfg=DARK_GRAY; }
    else if (name.startsWith("BOW")) { cbg=Cwhite; cfg=Cblack; }
    else if (name.startsWith("BRO")) { cbg=Cwhite; cfg=Cblack; }
    else if (name.startsWith("GEAR"));
    else if (name.startsWith("NAK"));
    else {
      final Theme theme = getThemeSafeVersion(themeName);
      fromTheme(theme);
      return;
    }

    if (name.startsWith("BRO")) {
      cwbg = Cwhite;
      cwts = Color.white;
      cwbs = Color.gray;
      cwfg = Color.black;
      cwti = Color.black;
      cwfh = Color.blue;
    }
    else if (name.startsWith("BOW")) {
      cwbg = Color.lightGray;
      cwts = Color.white;
      cwbs = Color.gray;
      cwfg = Color.black;
      cwti = Color.black;
      cwfh = Color.blue;
    }
    else if (name.startsWith("WOB")) {
      cwbg = cbg;
      cwts = new Color(0x303030);   // cwts and cwbs swapped for Bug 2480
      cwbs = new Color(0x101008);
      cwfh = new Color(0x6AAAD0);
      cwfg = cfg;
      cwti = cfg;
    }
    else if (name.startsWith("GOB")) {
      cwbg = cbg;
      cwts = new Color(0x303030);  // cwts and cwbs swapped for Bug 2480
      cwbs = new Color(0x101008);
      cwfh = new Color(0x6AAAD0);
      cwfg = cfg;
      cwti = cfg;
    }
    else if (name.startsWith("MNM")) {
      cwbg = cbg;
      cwts = new Color(0x303030);  // cwts and cwbs swapped for Bug 2480
      cwbs = new Color(0x101010);
      cwfh = new Color(0x6AAAD0);
      cwfg = cfg;
      cwti = cfg;
    }
    else if (name.startsWith("GEAR")) {
      cwbg = new Color(0x202020); // Very dark gray (mostly black).
      cwts = new Color(0x606060); // very dark gray
      cwbs = new Color(0x101010); // very dark gray (mostly black)
      cwfh = DARK_BLUE;
      if (name.equals("GEAR4")) { // since 3.5.3 for BUG 2893
        cwfh = BLUE;
      }
      cwbh = cwts;
      cwfg = DARK_GRAY;
      cwti = DARK_GRAY;
      if (name.equals("GEAR2")) {
        cwfg = STRONG_ORANGE;
        cwti = STRONG_ORANGE;
      }
      if (name.equals("GEAR3") || name.equals("GEAR4")) { // Gear4 add in 3.5.3 for 2893
        cwfg = STRONG_ORANGE;
        cwti = STRONG_ORANGE;
        cwbg = Cblack;
      }
      cbg=cwbg; cfg=cwfg;
    }
    else if (name.startsWith("NAK")) {
      cwbg = new Color(0x342420);
      cwts = new Color(0x706060);
      cwbs = new Color(0x201010);
      cwfh = new Color(0x3A6AA0);
      cwfg = STRONG_ORANGE;
      cwti = STRONG_ORANGE;
      cbg=cwbg; cfg=cwfg;
    }
    else {
      cwbg = new Color(0x343440);
      cwts = new Color(0x606070);
      cwbs = new Color(0x101020);
      cwfh = new Color(0x4090E0);
      cwfg = new Color(0xA0A0B0);
      cwti = new Color(0xA0A0B0);
    }
    cbgx = cbg;
    cwms = MidasColor.getColorMix(cwbg,cwts,0.5);
    if (inline) cbg = cwbg;

    if (getOption(DEBUG)) {
      System.out.println("---------------------------------");
      System.out.println("  " + this.name);
      System.out.println("---------------------------------");
//      System.out.println("this.Cbg    = " +  MColor.toString(this.Cbg));  removed in 3.7.1
//      System.out.println("this.Cfg    = " +  MColor.toString(this.Cfg));
      System.out.println("this.Cblack = " +  MidasColor.toString(this.Cblack));
      System.out.println("this.Cgray  = " +  MidasColor.toString(this.Cgray));
      System.out.println("this.Cwhite = " +  MidasColor.toString(this.Cwhite));
      System.out.println("this.cbg    = " +  MidasColor.toString(this.cbg));
      System.out.println("this.cfg    = " +  MidasColor.toString(this.cfg));
      System.out.println("this.cwbg   = " +  MidasColor.toString(this.cwbg));
      System.out.println("this.cwfg   = " +  MidasColor.toString(this.cwfg));
      System.out.println("this.cwti   = " +  MidasColor.toString(this.cwti));
      System.out.println("this.cwfh   = " +  MidasColor.toString(this.cwfh));
      System.out.println("this.cwts   = " +  MidasColor.toString(this.cwts));
      System.out.println("this.cwbs   = " +  MidasColor.toString(this.cwbs));
      System.out.println("this.cwms   = " +  MidasColor.toString(this.cwms));
      System.out.println("this.cbgx   = " +  MidasColor.toString(this.cbgx));
    }
  }

  /** Creates a theme based on a table of theme parameters.
      The currently supported parameters include:
      <pre>
        CBG    - The name of the background color.
        CFG    - The name of the foreground color.
        CWBG   - The name of the widget background color.
        CWFG   - The name of the widget foreground color.
        CWTI   - The name of the widget title color.
        CWFH   - The name of the widget highlight color.
        CWTS   - The name of the widget shadow color (top).
        CWBS   - The name of the widget shadow color (bottom).
      </pre>
      With the exception of the widget title color (CWTI), any colors not specified
      will default to the corresponding color from the current global theme. If CWTI
      is not specified it will follow the widget foreground color (CWFG).
      @param table The table of theme parameters.
   */
  public void createTheme (Table table) {
    // Widget Title color (cwti) was a late arrival.  Legacy theme settings may not have specified
    // the widget title color.  It was expected that titles would follow the widget foreground.
    if (table != null) {
      cbg  = getColor("CBG" , table, cbg );
      cfg  = getColor("CFG" , table, cfg );
      cwbg = getColor("CWBG", table, cwbg);
      cwfg = getColor("CWFG", table, cwfg);
      cwti = getColor("CWTI", table, cwfg); // Legacy: widget title color follows FG if not specified
      cwfh = getColor("CWFH", table, cwfh);
      cwbh = getColor("CWBH", table, cwbh);
      cwts = getColor("CWTS", table, cwts);
      cwbs = getColor("CWBS", table, cwbs);
      cwms = getColor("CWMS", table, cwms);
    }
  }

  /** Set if theme should use widget background color of background shading color for background color
      inline removes borders to allow embedding in another display (
      @param inline removes borders by changing the setting of cbg
   */
  public void setInline (boolean inline) {
    this.inline = inline;
    if (inline) cbg = cwbg;
    else cbg = cbgx;
  }

  /** Gets the name of the theme. */
  @Override
  public String getName() { return name; }

  /** Gets the description of the theme.
   *  @return description */
  public String getDescr() { return descr; }

  /** Indicates if the theme is "inlined".
   *  @return true if theme is "inlined" */
  public boolean getInline() { return inline; }

  /** Gets the font for the theme.
   *  @return the font */
  public Font getFont() { return font; }

  /** Gets the current background color for the theme.
   *  @return the current background color */
  public Color getBackground() { return cbg; }

  /** Gets the current background color for the theme.
   *  @return the current background color */
  public Color getCbg() { return getBackground(); }

  /** Gets the current foreground color for the theme.
   *  @return the current foreground color */
  public Color getForeground() { return cfg; }

  /** Gets the current foreground color for the theme.
   *  @return the current foreground color */
  public Color getCfg() { return getForeground(); }

  /** Gets the current widget background color for the theme.
   *  @return the current widget background color */
  public Color getWidgetBackground() { return cwbg; }

  /** Gets the current widget background color for the theme.
   *  @return the current widget background color */
  public Color getCwbg() { return getWidgetBackground(); }

  /** Gets the current widget foreground color for the theme.
   *  @return the current widget foreground color */
  public Color getWidgetForeground() { return cwfg; }

  /** Gets the current widget foreground color for the theme.
   *  @return the current widget foreground color */
  public Color getCwfg() { return getWidgetForeground(); }

  /** Gets the current widget title color for the theme.
   *  @return the current widget title color */
  public Color getCwti() { return getWidgetTitle(); }

  /** Sets the current widget title color for the theme.
   *  @param color the current widget title color */
  public void setCwti(Color color) { setWidgetTitle(color); }

  /** Sets the current widget title color for the theme.
   *  @param color the current widget title color */
  public void setCwti(String color) { setWidgetTitle(color); }

  /** Gets the current widget title color for the theme.
   *  @return the current widget title color */
  public Color getWidgetTitle() { return cwti; }

  /** Sets the current widget title color for the theme.
     @param color A color object
  */
  public void setWidgetTitle(Color color) { cwti = color; }

  /** Sets the current widget title color for the theme.
     @param color A color name or string representation
  */
  public void setWidgetTitle(String color) { setWidgetTitle(MidasColor.getColor(color)); } //jph -- J. Czechowski, why not have color on end???

  /** Gets the current widget highlight color for the theme.
   *  @return the current widget highlight color */
  public Color getWidgetForegroundHighlight() { return cwfh; }

  /** Gets the current widget highlight color for the theme.
   *  @return current widget highlight color */
  public Color getCwfh() { return getWidgetForegroundHighlight(); }

  /** Gets the current widget shading color (top) for the theme.
   *  @return the current widget shading color (top) */
  public Color getWidgetTopShade() { return cwts; }

  /** Gets the current widget shading color (top) for the theme.
   *  @return the current widget shading color (top) */
  public Color getCwts() { return getWidgetTopShade(); }

  /** Gets the current widget shading color (bottom) for the theme.
   *  @return the current widget shading color (bottom) */
  public Color getWidgetBottomShade() { return cwbs; }

  /** Gets the current widget shading color (bottom) for the theme.
   *  @return current widget shading color (bottom)
   */
  public Color getCwbs() { return getWidgetBottomShade(); }

  /** Gets the current widget shading color (middle) for the theme.
   *  @return the current widget shading color (middle) */
  public Color getWidgetMiddleShade() { return cwms; }

  /** Gets the current widget shading color (middle) for the theme.
   *  @return the current widget shading color (middle) */
  public Color getCwms() { return cwms; }

  /** gets the current widget background highlighting color for the theme
   *  @return the current widget background highlighting color */
  public Color getCwbh() { return cwbh; }

  /** Gets the current widget background shading color for the theme
   *  @return current widget background shading color */
  public Color getCbgx() { return cbgx; }

  /** Get the current color being used by the Theme for a given component.
      For example, to get the current {@link #cwti} color, this method is called
      with "CWTI" as the argument.
      @param name The abbreviated name of the theme component ({@link #cwti}, {@link #cwfg}, etc)
      @return The color of the Theme component, or null if given an unknown.
      @since NeXtMidas 3.1.0
   */
  public Color getColor (String name) {
    // This method was added so that the theme component color can be obtained for an unknown String
    name = name.toLowerCase();
    if (!getComponentList().contains(name)) return null;

    if      (name.equals("cwms")) return this.getCwms();
    else if (name.equals("cwfg")) return this.getCwfg();
    else if (name.equals("cwti")) return this.getCwti();
    else if (name.equals("cbg"))  return this.getCbg();
    else if (name.equals("cfg"))  return this.getCfg();
    else if (name.equals("cwbg")) return this.getCwbg();
    else if (name.equals("cbgx")) return this.getCbgx();
    else if (name.equals("cwbh")) return this.getCwbh();
    else if (name.equals("cwbs")) return this.getCwbs();
    else if (name.equals("cwfh")) return this.getCwfh();
    else if (name.equals("cwts")) return this.getCwts();
    else return null;
  }

  /** Gets a named color from a table.
      @param name  The name of the table entry.
      @param table The table the entry is in.
      @return The appropriate color or <code>null</code> if either there is no
              corresponding entry in the table or the color is not recognized.
   */
  private static Color getColor (String name, Table table) {
    return getColor(name, table, null);
  }

  /** Gets a named color from a table.
      @param name  The name of the table entry.
      @param table The table the entry is in.
      @param def   The default to return if the named field is not in the table.
      @return The appropriate color or the default if either there is no
              corresponding entry in the table or the color is not recognized.
   */
  private static Color getColor (String name, Table table, Color def) {
    Object obj = table.get(name);
    return (obj == null)? def : MidasColor.getColor(obj);
  }

 /** Set the color of a theme component
     @param name the theme component name
     @param newColor color to set the theme component to
     @since 3.5.0
  */
  @InternalUseOnly
 public void setColor (String name, Color newColor) {
   // This method was added so that the theme component color can be set for an unknown String
   name = name.toLowerCase();

   if      (name.equals("cwms")) cwms = newColor;
   else if (name.equals("cwfg")) cwfg = newColor;
   else if (name.equals("cwti")) cwti = newColor;
   else if (name.equals("cbg"))  cbg = newColor;
   else if (name.equals("cfg"))  cfg = newColor;
   else if (name.equals("cwbg")) cwbg = newColor;
   else if (name.equals("cbgx")) cbgx = newColor;
   else if (name.equals("cwbh")) cwbh = newColor;
   else if (name.equals("cwbs")) cwbs = newColor;
   else if (name.equals("cwfh")) cwfh = newColor;
   else if (name.equals("cwts")) cwts = newColor;
 }

  /** A comma delimited list of valid Theme components {@link Theme#COMPONENT_LIST}
      @return A string containing a list of theme components (comma separated)
      @since 3.5.0
    */
 @InternalUseOnly
  public static String getComponentList () {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    for (int ii=0; ii<COMPONENT_LIST.length; ii++) {
      sb.append(COMPONENT_LIST[ii][0]);
      if ( first) {
        first = false;
      } else {
        sb.append(",");
      }
    }
    return sb.toString();
  }

  /** Return a description of a Theme component
      @param name  A valid theme component {@link Theme#COMPONENT_LIST}
      @return the theme components's description
      @since 3.5.0
   */
 @InternalUseOnly
  public String getComponentDescription (String name) {
    String description = null;
    name = name.toLowerCase();
    for (int ii=0; ii<COMPONENT_LIST.length; ii++) {
      if (COMPONENT_LIST[ii][0].equals(name)) {
        return COMPONENT_LIST[ii][1];
      }
    }
    return description;
  }

  /** Get the color difference between two theme components
      @param colorStr1 The abbreviated name of a theme component ({@link #cwti}, {@link #cwfg}, etc) (cannot be null)
      @param colorStr2 The abbreviated name of a theme component ({@link #cwti}, {@link #cwfg}, etc) (cannot be null)
      @return Color difference (null if there is no color at any of the specified theme components)
      @see MidasColor#colorDifference(Color, Color)
      @since 3.5.0
   */
  Double colorDifference (String colorStr1, String colorStr2) {
    Color color1 = getColor(colorStr1);
    Color color2 = getColor(colorStr2);

    if ( color1 == null || color2 == null ) {
      return null;
    } else {
      return Double.valueOf(MidasColor.colorDifference(color1,color2));
    }
  }

  /** NOTE: This method is used for testing purposes.
      @param lafInfo  The Java look and feel info.
      @return A theme with the same properties as the Swing look and feel.
      @throws Exception For any number of reasons.
*/
  @InternalUseOnly
  public static Theme getThemeFor (LookAndFeelInfo lafInfo) throws Exception {
    return getThemeFor(lafInfo, null);
  }

  /** This will create a NeXtMidas theme for a given Swing Look and Feel (LAF).
      @param lafInfo  The Java look and feel info.
      @param midas    Midas context (if available).
      @return A theme with the same properties as the Swing look and feel.
              Since 3.3.4: returns null if that LAF should not be used (skipped) for BUG 2749.
      @throws Exception For any number of reasons.
      @since  NeXtMidas 1.9.5
   */
  private static Theme getThemeFor (LookAndFeelInfo lafInfo, Midas midas) throws Exception {
    String lafName = lafInfo.getName(); // since 4.1.1
    if ("GTK+".equals(lafName) && (midas == null)) { // GTK+ and no midas context
      // since 3.3.4: skip initializing GTK+ LAF as workaround for BUG 2749, otherwise in Java 7 on GTK+ platform (e.g. Linux),
      //   running inside SWT/GTK+ based-application (e.g. Eclipse Windows Builder) causes application to exit with:
      //   GThread-ERROR **: GThread system may only be initialized once. aborting...
      return null;
    }
    // JC4: This next line prevents a NullPointerException with the GTK+ look and feel. The fact
    //      that it changes the look and feel should be irrelevant since this method is called
    //      by <clinit> before the look and feel is officially set. 535119-1 (since 1.9.6)
    // KE: Change in 3.9.0 to prevent deprecation and reflection errors. Use the constructor
    // that takes in the class name instead of the LookAndFeel object. Then get the laf object
    // from the UIManager.
    // Jeff Schoen: previous workaround causes errors on platform not supporting GTK+ (Fedora 33)
    // JTM/ZWM adding try-catch block (since 4.1.0)
    try {
      UIManager.setLookAndFeel(lafInfo.getClassName());
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
      UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
      lafName = "Nimbus";
    }
    final LookAndFeel laf = UIManager.getLookAndFeel();

    Constructor<?> themeCon = CLAZZ_FOR_THEME_TO_INSTANTIATE.getConstructor(String.class, UIDefaults.class);

    return (Theme)themeCon.newInstance(lafName, laf.getDefaults());
  }

  /** This will create a NeXtMidas theme for a given Swing (Metal) Look and Feel.
      @param name  The name to use.
      @param jTheme The Java look and feel theme.
      @return A theme with the same properties as the Swing look and feel.
      @throws Exception For any number of reasons.
      @since  NeXtMidas 1.9.5
   */
  private static Theme getThemeFor (String name, MetalTheme jTheme) throws Exception {
    final LookAndFeel laf = new MetalLookAndFeel();

    // JC4: This next lines change the look and feel but such should be irrelevant since this
    //      method is called by <clinit> before the look and feel is officially set.
    MetalLookAndFeel.setCurrentTheme(jTheme); // <- TODO: JC4 why we are calling this?
    UIManager.setLookAndFeel(laf);
    UIDefaults lafDefaults = laf.getDefaults();
    Constructor<?> themeCon = CLAZZ_FOR_THEME_TO_INSTANTIATE.getConstructor(String.class, UIDefaults.class);
    Theme result = (Theme) themeCon.newInstance(name, lafDefaults);
    return result;
  }

  /** Gets the set of available Java/Swing Look and Feel's. This will not include the list of
      NeXtMidas themes in it.
      @return The set of currently-available Look And Feels (some of which might not be supported
              on the current platform due to copyright/trademark issues).
      @since NeXtMidas 1.9.5
   */
  private static LookAndFeelInfo[] getLookAndFeels() {
    final ArrayList<LookAndFeelInfo> plafs = new ArrayList<>();

    plafs.addAll(Arrays.asList(UIManager.getInstalledLookAndFeels()));

    LookAndFeel[] auxPlafs = UIManager.getAuxiliaryLookAndFeels();

    if (auxPlafs != null) {
      for (LookAndFeel auxLaf : auxPlafs) {
        plafs.add(new LookAndFeelInfo(auxLaf.getName(), auxLaf.getClass().getName()));
      }
    }

    return plafs.toArray(new LookAndFeelInfo[0]);
  }

  /** Gets the class name of a specified Look and Feel.
      @param name The name of the Look and Feel.
      @return The name of the class for the Look and Feel or <code>null</code> if the
              name does not match any available Look and Feels.
      @since NeXtMidas 1.9.5
   */
  private static String getLookAndFeelClassName (final String name) {
    final LookAndFeelInfo[] plafs = getLookAndFeels();

    for (LookAndFeelInfo lafInfo : plafs) {
      if (lafInfo.getName().equalsIgnoreCase(name)) {
        return lafInfo.getClassName();
      }
    }

    return null;
  }

  /** Gets the Metal theme name of a specified Look and Feel.
      @param name The name of the Look and Feel.
      @return The name of the class for the Look and Feel or <code>null</code> if the
              name does not match any official Metal themes.
      @since NeXtMidas 1.9.6
   */
  private static String getMetalThemeClassName (final String name) {
    for (int i = 0; i < METAL_THEMES.length; i++) {
      if (METAL_THEMES[i][1].equalsIgnoreCase(name)) {
        return METAL_THEMES[i][2];
      }
    }

    return null;
  }

  /** Gets the global NeXtMidas theme and the active Java/Swing look and feel.
      @return The global theme name.
      @since NeXtMidas 1.9.5
   */
  public static String getGlobalTheme() {
//    if(libgThemeClass!=null)  return (libgGlobalTheme != null)? libgGlobalTheme.getName(): null;
    return (globalTheme != null)? globalTheme.getName(): null;
  }

  /** Sets the global NeXtMidas theme and the active Java/Swing look and feel.
      @param themeName The theme name.
      @since NeXtMidas 1.9.5
      @throws UnsupportedLookAndFeelException If the specified Look and Feel is not supported on
                this platform due to copyright/trademark reasons (this is commonly seen when trying
                to set the look and feel to <code>"Windows"</code> on a non-Windows system).
      @throws IllegalArgumentException If the specified look and feel does not match any of the
                available look and feels.
      @see #getThemeNames()
   */
  public static void setGlobalTheme (final String themeName)
                        throws UnsupportedLookAndFeelException, IllegalArgumentException {
        setGlobalTheme(themeName, Shell.getSharedMidasContext());
  }

  /** Sets the global NeXtMidas theme and the active Java/Swing look and feel.
      @param themeName The theme name.
      @param midas Midas instance
      @since NeXtMidas 1.9.5
      @throws UnsupportedLookAndFeelException If the specified Look and Feel is not supported on
                this platform due to copyright/trademark reasons (this is commonly seen when trying
                to set the look and feel to <code>"Windows"</code> on a non-Windows system).
      @throws IllegalArgumentException If the specified look and feel does not match any of the
                available look and feels.
      @see #getThemeNames()
   */
  public static void setGlobalTheme (String themeName, Midas midas)
                        throws UnsupportedLookAndFeelException, IllegalArgumentException {

    if (headless) return;
    if (getOption(NOSLAF)) return; // Special lightweight mode

    Theme  theme  = getTheme(themeName);
    String clazz  = getLookAndFeelClassName(themeName);
    String mTheme = getMetalThemeClassName(themeName);

    if (theme != null) {
      if (clazz == null) {
        clazz = MetalLookAndFeel.class.getName();
      }
    } else {
      String lafNames = StringUtil.join(getThemeNames());
      throw new IllegalArgumentException("The '"+themeName+"' Look & Feel is not recognized. "
                                         + "Available options include: "+lafNames+".");
    }

    try {
      if (mTheme != null) {
        try { // Do this to prevent any circular nonsense.
          MetalTheme metalTheme = (MetalTheme)Class.forName(mTheme).getConstructor().newInstance(); // updated in 3.9.0 to use default constructor
          MetalLookAndFeel.setCurrentTheme(metalTheme);
        } catch (Exception e) {
          MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme());
        }
      } else {
        MetalLookAndFeel.setCurrentTheme(theme);
      }
      globalTheme = theme;
      if (midas != null) ((Results)midas.getResults()).put("ENV.THEME", getGlobalTheme(), Table.FORCE);
      UIManager.setLookAndFeel(clazz);


      // Perform visibility tests on theme (since 3.5.0)
      //globalTheme.warnThemeColorVisibility(midas,false);
      globalTheme.adjustThemeColorVisibilityIssues(midas, false);

    } catch (Exception e) {
      throw new IllegalArgumentException("Unable to load the required Look & Feel class (" + clazz + ").", e);
    }
  }

  /** Perform visibility tests and make adjustments adjustments to theme
      components that cause a failed test.

      The color visibility test framework allows NextMidas to dynamically
      adjust the color of theme components if they are not human readable.
      Various GUI widgets that utilize a Theme, such as subclasses to GWidget
      and the Shellgui, are responsible for defining color visibility tests
      and how to resolve the issue when a test fails.  The tests and adjustments
      are performed when a new theme is set.

      A test for combinations of theme components, such as the foreground
      and background colors of a Menu Item, are maintained in an Array&lt;String&gt;
      structure and defined as follows:
      Field 1 - Name of Test
      Field 2 - Main Theme component (Such as foreground)
      Field 3 - Second Theme component - This will be modified if test fails
      Field 4 - Solution type

      The data structure is a two dimensional String array

      @param midas the midas context
      @param supressConsoleWarning true if warning message should not be displayed in console
      @since 3.5.0
  */
  @InternalUseOnly
  public void adjustThemeColorVisibilityIssues (Midas midas, boolean supressConsoleWarning) {

    List<List<String>> tests = getThemeVisibilityTests();

    List<Integer> themeWarnings = getColorVisibilityWarnings(midas, tests);

    Iterator<Integer> i = themeWarnings.iterator();
    while ( i.hasNext()) {
      Integer themeWarning = i.next();
      int idx = themeWarning.intValue();
      List<String> test = tests.get(idx);
      adjustPoorVisibility(midas, test, supressConsoleWarning);
    }
  }

  /** Check for potential visibility issues for entire Theme
      @param midas the Midas context
      @param supressConsole  Do not output any warnings.
      @return List of warnings
      @since 3.5.0
   */
  @InternalUseOnly
  public List<String> warnThemeColorVisibility (Midas midas, boolean supressConsole) {

    List<List<String>> colorVisibilityTests = getThemeVisibilityTests();
    List<String> visibilityWarnings = new ArrayList<>();

    List<Integer> warningsIdxs = getColorVisibilityWarnings(midas, colorVisibilityTests);

    Iterator<Integer> iter = warningsIdxs.iterator();
    while ( iter.hasNext()) {
      Integer testIdx = iter.next();
      List<String> test = colorVisibilityTests.get(testIdx.intValue());
      String msg = "Theme "+name+": Visibility test for '"
           + test.get(VISIBILITY_TEST_IDX_NAME) +"' indicates poor color combination";
      visibilityWarnings.add(msg);
      if (!supressConsole) warn(midas,msg,null);
    }

    return visibilityWarnings;
  }


  /** Define the visibility tests.
      @return A list of visibility tests
   */
  private static List<List<String>> getThemeVisibilityTests() {

    List<List<String>> tests = new ArrayList<>();

    tests.add(Theme.buildVisibilityTest(
        "Unselected menu item",
        Theme.THEME_COMPONENT_CWBG,
        Theme.THEME_COMPONENT_CWFG,
        Theme.VISIBILITY_TEST_SOLUTION_BLEND));

    tests.add(Theme.buildVisibilityTest(
        "Selected menu item/Shellgui input promp",
        Theme.THEME_COMPONENT_CWBG,
        Theme.THEME_COMPONENT_CWFH,
        Theme.VISIBILITY_TEST_SOLUTION_BLEND));

    tests.add(Theme.buildVisibilityTest(
        "Menu title",
        Theme.THEME_COMPONENT_CWBG,
        Theme.THEME_COMPONENT_CWTI,
        Theme.VISIBILITY_TEST_SOLUTION_BLEND));

      return tests;
  }

  /** Convenient method for creating a visibility test
      @since 3.5.0
      @return ArrayList of strings containing test definition
   */
  private static List<String> buildVisibilityTest(String name, String component1, String component2, String solution) {

    List<String> newtest = new ArrayList<>();
    newtest.add(Theme.VISIBILITY_TEST_IDX_NAME,         name);
    newtest.add(Theme.VISIBILITY_TEST_IDX_COMPONENT_1,  component1);
    newtest.add(Theme.VISIBILITY_TEST_IDX_COMPONENT_2,  component2);
    newtest.add(Theme.VISIBILITY_TEST_IDX_SOLUTION,     solution);

    return newtest;
  }

  /** Change the color for a particular test.  The second color of the visibility test will be
      changed to the the inverted fist color.
      Warning.  This may have adverse impact on another color combination.
      @since 3.5.0
   */
  private void adjustPoorVisibility (Midas midas, List<String> test, boolean supressConsoleWarning) {
    // Extract attributes of this visibility test
    String testName = test.get(VISIBILITY_TEST_IDX_NAME);
    String component1 = test.get(VISIBILITY_TEST_IDX_COMPONENT_1);
    String component2 = test.get(VISIBILITY_TEST_IDX_COMPONENT_2);
    String solution = test.get(VISIBILITY_TEST_IDX_SOLUTION);

    Color color1 = getColor(component1);
    Color color2 = getColor(component2);

    String msg = "";

    // Based on solution type modify theme
    if (solution.equals(VISIBILITY_TEST_SOLUTION_INVERT)) {
        // Solution is to Invert color of 2nd component
        Color newColor = MidasColor.invert(color1);
        setColor(component2,newColor);
        msg = "Altering color of " + component2
            + " to improve " + testName;

      }
      else if (solution.equals(VISIBILITY_TEST_SOLUTION_BLEND)) {
        // Solution is to blend colors of both components and update 2nd component with new color
        Color newColor = MidasColor.invert(color1);
        newColor = MidasColor.blend(newColor,color2);
        setColor(component2,newColor);
        msg = "Altering color of " + component2
            + " to improve " + testName;
      }

    if ( !supressConsoleWarning)
      warn(midas,msg,null);
  }

  /** Check for theme visibility issues.
      @param midas the Midas context
      @param visibilityTests list of list of theme components to test visbility
      @return List of indexes (Integer) to the tests that failed
      @since 3.5.0
   */
  private List<Integer> getColorVisibilityWarnings (Midas midas, List<List<String>> visibilityTests) {
    List<Integer> visibilityWarnings = new ArrayList<>();

    for (int ii=0; ii<visibilityTests.size(); ii++) {
      List<String> test = visibilityTests.get(ii);
      Double deltaInputPrompt = colorDifference(test.get(VISIBILITY_TEST_IDX_COMPONENT_1),
                                                test.get(VISIBILITY_TEST_IDX_COMPONENT_2));
      if ( deltaInputPrompt != null) {
        if (deltaInputPrompt < visibilityTolerance) {
          visibilityWarnings.add(ii);
        }
      }
    }

    return visibilityWarnings;
  }

  /** Get the tolerance level when performing a visibility test
   *  @return the tolerance level when performing a visibility test
   *  @since 3.5.0
   */
  @InternalUseOnly
  public double getVisibilityTolerance() {
    return visibilityTolerance;
  }

  /** Set the tolerance level when performing a visibility test
      @param visibilityTolerance the tolerance level to use when performing a visibility test
      @since 3.5.0
   */
  @InternalUseOnly
  public void setVisibilityTolerance (double visibilityTolerance) {
    this.visibilityTolerance = visibilityTolerance;
  }

  /** Initializes the themes.
      @since NeXtMidas 2.1.0
      @deprecated Since NeXtMidas 3.3.4, instead use {@link #initialize()}.
   */
  @Deprecated
  public static void initThemes() {
    initialize();
  }

  /** Initializes the themes. */
  @InternalUseOnly
  private static void initThemes (Midas midas) {
    try {                // Add the legacy themes.
      Theme[] legacyThemes = getLegacyThemes();

      for (Theme legacyTheme : legacyThemes) {
        definedThemes.put(legacyTheme.getName().toUpperCase(), legacyTheme);
      }
    } catch (Exception e) {
      warn(midas, "Theme: Unable to initialize legacy themes.", e);
    }

    if (headless) return;          // Cannot do the rest if HEADLESS
    if (getOption(NOSLAF)) return; // Special lightweight mode to skip loading Swing Look-And-Feels

    try {         // Add the Java/Swing Look and Feels as NeXtMidas themes.
      Theme[] defaultThemes = getDefaultThemes(midas);
      for (Theme defaultTheme : defaultThemes) {
        definedThemes.put(defaultTheme.getName().toUpperCase(), defaultTheme);
      }
    } catch (Exception e) {
      warn(midas, "Theme: Unable to initialize default themes.", e);
    }

  }

  /** Initializes the headless status flag. */
  @SuppressWarnings("deprecation")
  private static void initHeadlessStatus () {
    Shell shell = Shell.getCurrent();

    if (getOption(HEADLESS)) {
      headless = true;
      return;
    }
    try {
      String headlessProp = System.getProperty("HEADLESS");
      if (headlessProp != null && !headlessProp.equals("AUTO")) {
        headless = StringUtil.isTrue(headlessProp);
        return;
      }
    } catch (Exception e) {
      if (getOption(DEBUG)) e.printStackTrace();
      System.err.println("Theme: Cannot get HEADLESS property.");
      headless = true;
      headlessInitStatus |= 0x1;
    }
    // The following checks for the case where DISPLAY is set for X11 forwarding, but the
    // client side is not accepting connections (i.e. did not use "xhost +"). Note that in
    // this case, it is common to see warnings like:
    //    Xlib: connection to "10.10.1.50:0.0" refused by server
    //    Xlib: No protocol specified
    // These warnings are low-level X11 warnings and there is no way to avoid them (even
    // running "xclock" in this case will result in the same warnings).
    if (!headless) try {
      headless = (Toolkit.getDefaultToolkit() == null);
    } catch (Throwable t) {
      if (getOption(DEBUG)) t.printStackTrace();
      // Only print warning, do not show stack trace since it is usually irrelevant. In this
      // case the error message is usually a good one, e.g. "Can't connect to X11 window
      // server using '10.10.1.50:0' as the value of the DISPLAY variable.".
      System.out.println("WARN: " + t.getMessage());
      headless = true;
      headlessInitStatus |= 0x2;
    }
    if (!headless) try {
      // The next two lines may cause X11 warnings on some older Linux versions.
      GraphicsEnvironment graphicsEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
      headless = graphicsEnv.isHeadlessInstance() || GraphicsEnvironment.isHeadless();
    } catch (Throwable t) {
      if (getOption(DEBUG)) t.printStackTrace();
      // Either Error or Exception thrown when getting local graphics environment. This
      // typically happens when running on a Linux system that is not headless but the
      // current user does not have access to the display.
      headless = true;
      headlessInitStatus |= 0x4;
    }
    if (headless) setOptions("+HEADLESSDETECTED"); // Indicate that headless was detected by the system, not specified by the user.
  }

  /** Indicates if the shell is running in headless mode. If the shell is running in headless mode
      then any features that require graphical capabilities should be disabled by default. This
      indicates if the shell is "structurally" in headless mode (e.g. no monitor is detected, or
      running on Linux without access to an active X11 session) and may return <code>false</code>
      when the shell is asked to run in headless mode, but it is still capable of running
      non-headless. To prevent Java from trying to auto-detect if it is in headless mode (or to
      override the setting), use the following definitions when starting NeXtMidas:
      <pre>
        -DHEADLESS=AUTO    (checks to see if this JVM is headless)
        -DHEADLESS=TRUE    (force session to be headless, skips checks)
        -DHEADLESS=FALSE   (force that it is not headless, skips checks)
       or
        -DGRAPHICS=+HEADLESS (same as HEADLESS=TRUE)
       also
        -DGRAPHICS=+INITONLOAD (force theme initialization on load for non-Midas graphics)
      </pre>
      @return <code>true</code> if in headless mode, <code>false</code> if not.
      @since NeXtMidas 2.1.0
   */
  public static boolean isHeadless () {
    initialize();
    return headless || getOption(HEADLESS) || getOption(HEADLESSDETECTED);
  }

  /** Get headless initialization status
   *  @return 0 for no error, and  greater than 0 for error during headless initialization check
   *  @see #initHeadlessStatus()
   *  @since 3.5.0
   */
  @InternalUseOnly
  public static int getHeadlessInitStatus () {
    return headlessInitStatus;
  }

  /////////////////////////////////////////////////////////////////////////////
  //// THE FOLLOWING METHODS ARE USED TO IMPLEMENT THIS NEXTMIDAS THEME AS ////
  ////     A THEME THAT CAN BE USED BY THE JAVA'S "METAL" LOOK & FEEL.     ////
  /////////////////////////////////////////////////////////////////////////////
  /* JC4: Some of the MetalTheme parameters are poorly defined, here is my approximation as to what
          they represent:
             black      - Widget foreground color.
             white      - Widget background color.
             primary1   - Scroll bar highlight color.
             primary2   - This is the color for highlighted items in a menu.
             primary3   - This is the color for highlighted text.
             secondary1 - This is the border color for buttons/menus, background depressed items.
             secondary2 - This is the border color.
             secondary3 - This is the background color.
   */

  @Override
  public    FontUIResource getControlTextFont()   { return wrap(font); }
  @Override
  public    FontUIResource getMenuTextFont()      { return wrap(font); }
  @Override
  public    FontUIResource getSubTextFont()       { return wrap(font); }
  @Override
  public    FontUIResource getSystemTextFont()    { return wrap(font); }
  @Override
  public    FontUIResource getUserTextFont()      { return wrap(font); }
  @Override
  public    FontUIResource getWindowTitleFont()   { return wrap(font); }

  @Override
  public    ColorUIResource getBlack()            { return wrap(cwfg); }
  @Override
  public    ColorUIResource getControlTextColor() { return wrap(cfg ); }
  @Override
  protected ColorUIResource getPrimary1()         { return wrap(cfg ); }
  @Override
  protected ColorUIResource getPrimary2()         { return wrap(cwfh); }
  @Override
  protected ColorUIResource getPrimary3()         { return wrap(cwfh); }
  @Override
  protected ColorUIResource getSecondary1()       { return wrap(cwms); }
  @Override
  protected ColorUIResource getSecondary2()       { return wrap(cwts); }
  @Override
  protected ColorUIResource getSecondary3()       { return wrap(cbg ); }
  @Override
  public    ColorUIResource getSystemTextColor()  { return wrap(cfg ); }
  @Override
  public    ColorUIResource getUserTextColor()    { return wrap(cfg ); }
  @Override
  public    ColorUIResource getWhite()            { return wrap(cbgx); }

  /** This will wrap a basic {@link Font} as a {@link FontUIResource} for use with the
      {@link MetalTheme} implementation methods.
      @param font The font to wrap.
      @return The wrapped font.
   */
  private static FontUIResource wrap (Font font) { return new FontUIResource(font); }

  /** This will wrap a basic {@link Color} as a {@link ColorUIResource} for use with the
      {@link MetalTheme} implementation methods.
      @param color The color to wrap.
      @return The wrapped color.
   */
  private static ColorUIResource wrap (Color color) { return new ColorUIResource(color); }

  /** Loads current default set of Java UIManager keys */
  @InternalUseOnly
  public static void loadUIManagerMap () {
    HashMap<String,String> hash = new HashMap<>(600,25);
    Enumeration<Object> keys =    UIManager.getDefaults().keys();
    while (keys.hasMoreElements()) {
      String key = keys.nextElement().toString();
      String uKey = key.toUpperCase();
      hash.put(uKey,key);
    }
    UIManagerKeysMapping = hash;
  }

  /** Gets the current set of UI manager keys mapping
      @return UI Manager Keys Mapping
   */
  @InternalUseOnly
  public static HashMap<String,String> getUIManagerMapping () { return UIManagerKeysMapping; }

  /** Updates the SWING UIManager with the given values in the list. <br>
      The input Table should be in the format of sub-Tables. For example, the
      <code>UIManager.put</code> call is in the format  <code>Component.variable</code>
      The equivalent NeXtMidas Table format is <code>Table.subTable</code>. For example:
      <code>UIMananager.put("ScrollBar.width",10) </code> in NeXtMidas is passed in as
      a Table that looks like this:
      <code>{SCROLLBAR={WIDTH={10}}</code>
      @param tab Table of values with keys that correspond to UIManager keys
      @return The input Table, which may be modified if any of its values were invalid
      @since 2.7.3
   */
  public static Table updateUIManager (Table tab) {
    if (tab==null) { return null; }  // nothing to do
    boolean valid = true;

    // Only load the mappings table the first time
    if (UIManagerKeysMapping == null) loadUIManagerMap();

    HashMap<String,String> hash = getUIManagerMapping();

    tab.setMode(Table.HM);
    String[] tabKeys = tab.getKeys();
    String cmd;
    Object value;

    // Go through the input table and convert into a UIManager call
    for (String key : tabKeys) {
      Object obj = tab.get(key);
      if (obj instanceof Table) {
        Table tmpTab = (Table)obj;
        String[] tmpTabKeys = tmpTab.getKeys();
        for (String subTabKey : tmpTabKeys) {
          cmd = key+"."+subTabKey;
          value = tmpTab.get(subTabKey);
          cmd = hash.get(cmd);
          if (cmd==null) {
            valid = false;
          } else {
            valid = updateUIManager(cmd,value);
          }
          if (!valid) {
            Shell.warning("Theme: unable to update Swing UIManager with inputs: "+key+"."+ subTabKey+", "+value);
            String tmp = UIManager.get(cmd).toString();
            if (tmp != null) {
              tmpTab.remove(subTabKey);
              tmpTab.put(subTabKey, tmp.toString());
            }
          }
        }
      }
    }
    return tab;
  }

  /** Updates a single UIManager value.
      @param key String form of a UIManager call (e.g. "ScrollBar.width")
      @param value The value to put in the UIManager
      @return TRUE if the update was successful, FALSE otherwise
      @since 2.7.3
   */
  public static boolean updateUIManager (String key, Object value) {

    String sVal = value.toString();
    String type;
    boolean valid = true;
    try {
      type  = (UIManager.get(key).getClass().getSimpleName()); // what type of data is required
      if (type.equalsIgnoreCase("Integer")) {
        UIManager.put(key, Integer.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Float")) {
        UIManager.put(key, Float.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Double")) {
        UIManager.put(key, Double.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Long")) {
        UIManager.put(key, Long.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Boolean")) {
        UIManager.put(key, Boolean.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Char") || type.equalsIgnoreCase("String")) {
        UIManager.put(key, (value));
      } else if (type.equalsIgnoreCase("Byte")) {
        UIManager.put(key, Byte.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Short")) {
        UIManager.put(key, Short.valueOf(sVal));
      } else if (type.equalsIgnoreCase("ColorUIResource")) {
        UIManager.put(key, wrap(MidasColor.getColor(value)));
      } else if (type.equalsIgnoreCase("Boolean")) {
        UIManager.put(key, Boolean.valueOf(sVal));
      } else if (type.equalsIgnoreCase("Color")) {
        UIManager.put(key, MidasColor.getColor(value));
      } else if (type.equalsIgnoreCase("Dimension")) {
        // Careful, this will increment the list
        Dimension d = (Dimension)value;
        UIManager.put(key,d);
      } else if (type.equalsIgnoreCase("Font") || type.equalsIgnoreCase("FontUIResource")) {
        Font f = new Font(sVal, DEFAULT_FONT.getStyle(), DEFAULT_FONT.getSize());
        UIManager.put(key, wrap(f));
      } else if (type.equalsIgnoreCase("ImageIconUIResource")) {
        // TBD
      } else {  // everything else
        UIManager.put(key, (value));
      }
    } catch (Exception ex) {
      //Shell.warning("Theme: unable to update Swing UIManager with inputs: "+
      //      key+", "+value);
      valid = false;
    }
    return valid;
  }

  /** Convenience method for updating Swing UIManager Scrollbar's width.
   *  @param w scroll bar width */
  public static void setScrollBarWidth (int w) {
    UIManager.put("ScrollBar.width", Integer.valueOf(w));
  }

  /** Convenience method to get Swing UIManager Scrollbar width.
   *  @return scroll bar width */
  public static int getScrollBarWidth () {
    return UIManager.getInt("ScrollBar.width");
    }

  /** Convenience method to set Swing UIManager Scrollbar's color.
   *  @param c scroll bar color */
  public static void setScrollBarColor (String c) {
    setScrollBarColor(MidasColor.getColor(c));
  }

  /** Convenience method to set Swing UIManager Scrollbar's color.
   *  @param c color for Swing UIManager Scrollbar */
  public static void setScrollBarColor (Color c) {
    UIManager.put("ScrollBar.thumb", wrap(Color.white));
  }

  /** if Midas context is not null, use it to display warning, otherwise use System.err.
      @since NeXtMidas 3.3.4
   */
  private static void warn (Midas midas, CharSequence msg, Throwable throwable) {
    if (midas != null) {
      midas.printStackTrace(msg, throwable, false, Midas.WARNING);
    } else {
      System.err.println(msg);
      if (throwable != null) throwable.printStackTrace();
    }
  }

}
