swingx-master/swingx-demos/swingx-demos-swingxset/src/main/java/org/jdesktop/swingxset/SwingXSet.java
/*
 * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jdesktop.swingxset;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Frame;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXMultiSplitPane;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.MultiSplitLayout;
import org.jdesktop.swingx.MultiSplitLayout.Node;
import org.jdesktop.swingx.appframework.AppSearchFactory;
import org.jdesktop.swingx.appframework.SingleXFrameApplication;
import org.jdesktop.swingx.appframework.StyledTextActions;
import org.jdesktop.swingx.search.SearchFactory;
import org.jdesktop.swingx.util.OS;
import org.jdesktop.swingx.util.font.FontPolicies;
import org.jdesktop.swingx.util.font.FontPolicy;
import org.jdesktop.swingx.util.font.FontSet;
import org.jdesktop.swingx.util.font.UIFontUtils;
import org.jdesktop.swingx.util.font.WrapperFontSet;
import org.jdesktop.swingxset.util.DemoUtils;

import com.sun.swingset3.Demo;
import com.sun.swingset3.codeview.CodeViewer;
import com.sun.swingset3.utilities.RoundedBorder;
import com.sun.swingset3.utilities.Utilities;

/**
 *
 * @author Amy Fowler
 * @author Jeanette Winzenburg (SwingX adaption)
 */
public class SwingXSet extends SingleXFrameApplication  {
    public static final Logger logger = Logger.getLogger(SwingXSet.class.getName());
    
    private static final ServiceLoader<LookAndFeel> LOOK_AND_FEEL_LOADER = ServiceLoader.load(LookAndFeel.class); 

    public static final String CONTROL_VERY_LIGHT_SHADOW_KEY = "controlVeryLightShadowColor";
    public static final String CONTROL_LIGHT_SHADOW_KEY = "controlLightShadowColor";
    public static final String CONTROL_MID_SHADOW_KEY = "controlMidShadowColor";
    public static final String CONTROL_VERY_DARK_SHADOW_KEY = "controlVeryDarkShadowColor";
    public static final String CONTROL_DARK_SHADOW_KEY = "controlDarkShadowColor";
    public static final String TITLE_GRADIENT_COLOR1_KEY = "titleGradientColor1";
    public static final String TITLE_GRADIENT_COLOR2_KEY = "titleGradientColor2";
    public static final String TITLE_FOREGROUND_KEY = "titleForegroundColor";
    public static final String CODE_HIGHLIGHT_KEY = "codeHighlightColor";
    public static final String TITLE_FONT_KEY = "titleFont";
    public static final String SUB_PANEL_BACKGROUND_KEY = "subPanelBackgroundColor";

    private static final Border EMPTY_BORDER = new EmptyBorder(0, 0, 0, 0);
    private static final Border PANEL_BORDER = new EmptyBorder(10, 10, 10, 10);
    
    private static final String DEMO_LIST_FILE = "/META-INF/demolist";

    static {
        // Property must be set *early* due to Apple Bug#3909714
        if (System.getProperty("os.name").equals("Mac OS X")) {
            System.setProperty("apple.laf.useScreenMenuBar", "true"); 
        }
        
        // temporary workaround for problem with Nimbus classname
        UIManager.LookAndFeelInfo lafInfo[] = UIManager.getInstalledLookAndFeels();
        for(int i = 0; i < lafInfo.length; i++) {
            if (lafInfo[i].getName().equals("Nimbus")) {
                lafInfo[i] = new UIManager.LookAndFeelInfo("Nimbus",
                        "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
                break;
            }
        }
        UIManager.setInstalledLookAndFeels(lafInfo);
        UIManager.put("swing.boldMetal", Boolean.FALSE);
        UIManager.put("auto-add-separator", Boolean.FALSE);
    }
    
    public static void main(String[] args) {
        launch(SwingXSet.class, args);
    }
    
    public static boolean runningOnMac() {
        return System.getProperty("os.name").equals("Mac OS X");
    } 
    
    public static boolean usingNimbus() {
        return UIManager.getLookAndFeel().getName().equals("Nimbus");
    }
    
    // End of statics
    
    private ResourceMap resourceMap;
    
    // Application models
    private PropertyChangeListener demoPropertyChangeListener;
    private HashMap<String, JComponent> runningDemoCache;
    private DemoSelector demoSelector;
    
    
    // GUI components and controls
    private MultiSplitLayout multiSplitLayout;
    
    private CodeViewer codeViewer;    
    private ButtonGroup lookAndFeelRadioGroup;
    
    private JPopupMenu popup;
    
    // Properties
    private String lookAndFeel;
    private boolean hasBig;


 // -------------------- SwingXSet application/control code

    /**
     * Sets the bigFontSet property which is a flag indicating whether a large
     * FontSet should be used.
     * <p>
     * If true, the LAF FontSet is scaled up by a fixed amount, otherwise it's
     * taken as is.
     * 
     * The default is false.
     * 
     * @param bigFontSet a boolean indicating whether or not the system Fonts
     *        should be scaled up.
     */
    public void setBigFontSet(boolean bigFontSet) {
        boolean old = isBigFontSet();
        if (old == bigFontSet) return;
        this.hasBig = bigFontSet;
        updateFontSet();
        firePropertyChange("bigFontSet", old, isBigFontSet());
    }
     
    /**
     * Returns the bigFontSet property.
     * @return
     */
     public boolean isBigFontSet() {
         return hasBig;
     }

    /**
     * Toggle the bigFontSet property.
     * <p>
     * Note: this is public as an implementation side-effect. DON'T USE!
     */
    @Action(selectedProperty = "bigFontSet", enabledProperty = "windowsOS")
    public void toggleFontSet() {
    }

    public boolean isWindowsOS() {
        return OS.isWindows();
    }
    /**
     * Internal state update after FontSet flag has been changed.
     */
    private void updateFontSet() {
        if (!isBigFontSet()) {
            UIFontUtils.initFontDefaults(UIManager.getDefaults(), null);
        } else {
//            <snip> Install sscaled font
            FontPolicy windowsPolicy = FontPolicies.getDefaultWindowsPolicy();
            FontSet fontSet = windowsPolicy.getFontSet(null, UIManager
                    .getLookAndFeelDefaults());
            WrapperFontSet scaled = new WrapperFontSet(fontSet, 5);
            UIFontUtils.initFontDefaults(UIManager.getDefaults(), scaled);
//            </snip>
        }
        updateLookAndFeel();
    }


    /**
     * Toggles the codeViewerVisible property.
     * <p>
     * Note: this is public as an implementation side-effect. DON'T USE!
     */
    @Action (selectedProperty = "codeViewerVisible")
    public void toggleCodeViewerVisible() {
    }
    
    public void setCodeViewerVisible(boolean selectorVisible) {
        boolean old = isCodeViewerVisible();
//        <snip> MultiSplit toggle visiility by constraint
        multiSplitLayout.displayNode("source", selectorVisible);
        ((JComponent) getMainFrame().getContentPane()).revalidate();
//        </snip>
        firePropertyChange("codeViewerVisible", old, isCodeViewerVisible());
        requestFocusOnDemo();
    }
    
    
    public boolean isCodeViewerVisible() {
        JComponent selector = getComponentByConstraint("source");
        return selector.isVisible();
    }
    
    /**
     * Toggles the selectorVisible property.
     * <p>
     * Note: this is public as an implementation side-effect. DON'T USE!
     * 
     */
    @Action (selectedProperty = "selectorVisible")
    public void toggleSelectorVisible() {
    }
    
    public void setSelectorVisible(boolean selectorVisible) {
        boolean old = isSelectorVisible();
//        <snip> MultiSplit toggle visiility by constraint
        multiSplitLayout.displayNode("selector", selectorVisible);
        ((JComponent) getMainFrame().getContentPane()).revalidate();
//        </snip>
        firePropertyChange("selectorVisible", old, isSelectorVisible());
        requestFocusOnDemo();
    }

    
    public boolean isSelectorVisible() {
        JComponent selector = getComponentByConstraint("selector");
        return selector.isVisible();
    }
    
    /**
     * @param demoContainer
     */
    private void requestFocusOnDemo() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Demo demo = demoSelector.getSelectedDemo();
                if (demo != null) {
                    Component demoComponent = demo.getDemoComponent();
                    if (demoComponent != null) {
                        KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(demoComponent);
                    }
                }
            }
        });
    }
    
    private void updateFromSelectedDemoChanged() {
        JComponent demoContainer = getComponentByConstraint("demo");
        Demo demo = getDemo();
        if (demo != null) {
            JComponent demoPanel = runningDemoCache.get(demo.getName());
            if (demoPanel == null || demo.getDemoComponent() == null) {
                demo.startInitializing();
                demoPanel = createDemoPanel(demo);
                if (demoPanel != null) {
                    runningDemoCache.put(demo.getName(), demoPanel);
                }
            }
            if (demoPanel != null) {
                demoContainer.removeAll(); //(currentDemoPanel);
                demoContainer.add(demoPanel, BorderLayout.CENTER);
                demoContainer.revalidate();
                demoContainer.repaint();
                multiSplitLayout.layoutByWeight(getMainFrame().getContentPane());
            }
        }

        codeViewer.setSourceFiles(demo != null?
                                  demo.getSourceFiles() : null);
    }
   
    private JComponent createDemoPanel(Demo demo) {
        try {
            return new DemoXPanel(demo);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
        
        return null;
    }
    public Demo getDemo() {
        return demoSelector.getSelectedDemo();
    }

    
    private class DemoSelectionListener implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent event) {
            if (event.getPropertyName().equals("selectedDemo")) {
                updateFromSelectedDemoChanged();
            }
        }
    }
           
    
    // registered on Demo to detect when the demo component is instantiated.
    // we need this because when we embed the demo inside an HTML description pane,
    // we don't have control over the demo component's instantiation
    private class DemoPropertyChangeListener implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if (propertyName.equals("demoComponent")) {
                Demo demo = (Demo)e.getSource();
                JComponent demoComponent = (JComponent)e.getNewValue();
                if (demoComponent != null) {
                    demoComponent.putClientProperty("swingset3.demo", demo);
                    demoComponent.addHierarchyListener(new DemoVisibilityListener());
                    registerPopups(demoComponent);
                }
            } 
        }
    }
    
    private class DemoVisibilityListener implements HierarchyListener {
        public void hierarchyChanged(HierarchyEvent event) {
            if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) > 0) {
                JComponent component = (JComponent)event.getComponent();
                final Demo demo = (Demo)component.getClientProperty("swingset3.demo");
                if (!component.isShowing()) {
                    demo.stop();
                } else {
                    getComponentByConstraint("demo").revalidate();
                    EventQueue.invokeLater(new Runnable() {
                        public void run() {
                            demo.start();
                        }
                    });
                }
            }            
        }        
    }


    private JComponent getComponentByConstraint(String string) {
        Node node = multiSplitLayout.getNodeForName(string);
        return (JComponent) multiSplitLayout.getComponentForNode(node);
    }

    
    public void setLookAndFeel(String lookAndFeel) throws ClassNotFoundException,
        InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
        
        String oldLookAndFeel = this.lookAndFeel;
        
        if (oldLookAndFeel != lookAndFeel) {
            boolean oldBig = isBigFontSet();
            hasBig = false;
            updateFontSet();
            UIManager.setLookAndFeel(lookAndFeel);
            this.lookAndFeel = lookAndFeel;
            updateLookAndFeel();
            setBigFontSet(oldBig);
            firePropertyChange("lookAndFeel", oldLookAndFeel, lookAndFeel);                     
        }
    }
    
    @Action 
    public void setLookAndFeel() {
        ButtonModel model = lookAndFeelRadioGroup.getSelection();
        String lookAndFeelName = model.getActionCommand();
        try {
            setLookAndFeel(lookAndFeelName);
        } catch (Exception ex) {
            displayErrorMessage(resourceMap.getString("error.unableToChangeLookAndFeel") +
                    "to "+lookAndFeelName, ex);
        }
    }
    
    public String getLookAndFeel() {
        return lookAndFeel;
    }

    private void updateLookAndFeel() {
        Window windows[] = Frame.getWindows();

        for(Window window : windows) {
            SwingUtilities.updateComponentTreeUI(window);
            for(JComponent demoPanel : runningDemoCache.values()) {
                SwingUtilities.updateComponentTreeUI(demoPanel);
            }
        }
        
        ((AppSearchFactory) SearchFactory.getInstance()).updateUI();
    }


    // hook used to detect if any components in the demo have registered a
    // code snippet key for the their creation code inside the source
    private void registerPopups(Component component) {
        
        if (component instanceof Container) {
            Component children[] = ((Container)component).getComponents();
            for(Component child: children) {
                if (child instanceof JComponent) {
                    registerPopups(child);
                }
            }
        }
        if (component instanceof JComponent) {
            JComponent jcomponent = (JComponent)component;
            String snippetKey = (String)jcomponent.getClientProperty("snippetKey");
            if (snippetKey != null) {
                jcomponent.setComponentPopupMenu(popup);
            }
        }
    }  
    
    private class ViewCodeSnippetAction extends AbstractAction {
        public ViewCodeSnippetAction() {
            super("View Source Code");
        }
        public void actionPerformed(ActionEvent actionEvent) {
            Container popup = (JComponent)actionEvent.getSource();
            while(popup != null && !(popup instanceof JPopupMenu)) {
                popup = popup.getParent();
            }
            JComponent target = (JComponent)((JPopupMenu)popup).getInvoker();
            
            String snippetKey = (String)target.getClientProperty("snippetKey");
            if (snippetKey != null) {
                codeViewer.highlightSnippetSet(snippetKey);
            } else {
                logger.log(Level.WARNING, "can't find source code snippet for:" + snippetKey);
            }                                    
        }
    }
    
    private static class EditPropertiesAction extends AbstractAction {
        public EditPropertiesAction() {
            super("Edit Properties");
        }
        @Override
        public boolean isEnabled() {
            return false;
        }
        public void actionPerformed(ActionEvent actionEvent) {
            
        }
    } 
    
 //------------------- override/implement Application    
    @Override
    protected void initialize(String args[]) { 
        // following line is for debugging initial layount only, will be removed ..
//        deleteSessionState();
        try {
            // JW: don't want to start up with Nimbus because it misbehaves
            // severely in not cleaning up behind itself ...
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception ex) {
            // not catestrophic
        }
        super.initialize(args);
        resourceMap = getContext().getResourceMap();
        
        runningDemoCache = new HashMap<String, JComponent>();
        
        List<Demo> demos = DemoCreator.getInstance().createDemos(args, DEMO_LIST_FILE);
        for (Demo demo : demos) {
            demo.addPropertyChangeListener(getDemoPropertyChangeListener());
        }
        demoSelector = new DemoSelector(demos);
//        SearchFactory.getInstance().setUseFindBar(true);
    }

    @Override 
    protected void startup() {
        
        configureDefaults();
        
        JXFrame frame = (JXFrame) getMainFrame();
        frame.add(createMainPanel());
        frame.setJMenuBar(createMenuBar());
        applyDefaults();
        installListeners();
        // PENDING JW: moved from start of method to after component creation
        // otherwise throws with bsaf-1.9.1
        // needs tracking - dont know what's happening ...
        StyledTextActions.install(getContext());
        
        show(frame);     
        demoSelector.setDefaultSelectedDemo();
    } 

    
    
    /** 
     * Overridden to apply fade-out hook instead of halting the runtime.
     */
    @Override
    protected void end() {
        DemoUtils.fadeOutAndExit(getMainFrame(), 800);
    }

    @Override
    protected void configureWindow(Window root) {
        super.configureWindow(root);
        if (root == getMainFrame()) {
            // PENDING JW
            // brute force: prevent super from pack()'ing the mainframe
            // initially - actually it's a problem of our overall
            // layout, packing giving horrible results ...
            // we have to do it here (instead of in show as I assumed)
            // because super's injecting properties will invalidate
            root.pack();
            root.setBounds(0, 0, 1024, 748);
            root.validate();
        }
    }

    
    @Override
    protected JXFrame createXFrame() {
//        JXFrame frame = new JXFrame();
//        frame.setRootPane(new JXDemoRootPane());
        return new JXDemoFrame();
    }

    protected void installListeners() {
        UIManager.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                if (event.getPropertyName().equals("lookAndFeel")) {
                    configureDefaults();
                }
            }
        });
        demoSelector.addPropertyChangeListener(new DemoSelectionListener());
    }
    
    // --------------------------- build components
    
    
    protected JComponent createMainPanel() {
//        <snip> MultiSplit layout declaration
        String layout = 
            "(ROW " +
                 "(LEAF name=selector weight=0.25)" +
                 "(COLUMN weight=0.75 " +
                     "(LEAF name= demo weight=0.7)" +
                     "(LEAF name=source weight=0.3)" +
                 ")" +
            ")"; 
        multiSplitLayout = new MultiSplitLayout(MultiSplitLayout.parseModel(layout));
//        </snip>
        JXMultiSplitPane splitPane = new JXMultiSplitPane();
        splitPane.setLayout(multiSplitLayout);

        // Create demo selector panel on left
        JComponent demoSelectorPanel = demoSelector.getSelectorComponent();
        demoSelectorPanel.setBorder(PANEL_BORDER);
        splitPane.add(demoSelectorPanel, "selector");
        
        // Create panel to contain currently running demo
        JComponent demoContainer = new JXPanel();
        demoContainer.setLayout(new BorderLayout());
        demoContainer.setBorder(PANEL_BORDER);
        splitPane.add(demoContainer, "demo");

        // Create source code pane
        codeViewer = new CodeViewer();
//        Font font = codeViewer.getFont();
//        codeViewer.setFont(font.deriveFont(Font.BOLD, 20.f));
        JComponent codeContainer = new JPanel(new BorderLayout());
        codeContainer.add(codeViewer);
        codeContainer.setBorder(PANEL_BORDER);
        splitPane.add(codeContainer, "source");
        
        // Create shareable popup menu for demo actions
        popup = new JPopupMenu();
        popup.add(new EditPropertiesAction());
        popup.add(new ViewCodeSnippetAction());

        return splitPane;
    }
    
    protected JMenuBar createMenuBar() {
    
        JMenuBar menubar = new JMenuBar();
        menubar.setName("menubar");
        
        // File menu
        JMenu fileMenu = new JMenu();
        fileMenu.setName("file");
        menubar.add(fileMenu);
        
        // File -> Quit
        if (!runningOnMac()) {
            JMenuItem quitItem = new JMenuItem();
            quitItem.setName("quit");
            quitItem.setAction(getAction("quit"));
            fileMenu.add(quitItem);
        }
       
        // View menu
        JMenu viewMenu = new JMenu();
        viewMenu.setName("view");
        // View -> Look and Feel       
        viewMenu.add(createLookAndFeelMenu());
        menubar.add(viewMenu);

        viewMenu.add(new JCheckBoxMenuItem(getAction("toggleFontSet")));
        JMenuItem toggleSelectorVisible = new JCheckBoxMenuItem(getAction("toggleSelectorVisible"));
        // PENDING JW: Action binding to selected - force initial state
        toggleSelectorVisible.setSelected(isSelectorVisible());
        viewMenu.add(toggleSelectorVisible);
        JMenuItem toggleCodeViewerVisible = new JCheckBoxMenuItem(getAction("toggleCodeViewerVisible"));
        // PENDING JW: Action binding to selected - force initial state
        toggleCodeViewerVisible.setSelected(isCodeViewerVisible());
        viewMenu.add(toggleCodeViewerVisible);
        
        // debug
//        viewMenu.add(new JMenuItem(getAction("debugVisible")));
//        JMenuItem toggleCO = new JMenuItem(getAction("toggleCO"));
//        viewMenu.add(toggleCO);
        return menubar;
    }

    @Action
    public void debugVisible() {
        toggleCodeViewerVisible();
    }
    
    @Action
    public void toggleCO() {
        JFrame frame = getMainFrame();
        ComponentOrientation current = frame.getComponentOrientation();
        if (current == ComponentOrientation.LEFT_TO_RIGHT) {
            frame.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
        } else {
            frame.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);

        }
        frame.getRootPane().revalidate();
        frame.invalidate();
        frame.validate();
        frame.repaint();

    }

    protected JMenu createLookAndFeelMenu() {
        JMenu menu = new JMenu();
        menu.setName("lookAndFeel");
        
        // Look for toolkit look and feels first
        UIManager.LookAndFeelInfo lookAndFeelInfos[] = UIManager.getInstalledLookAndFeels();
        lookAndFeel = UIManager.getLookAndFeel().getClass().getName();
        lookAndFeelRadioGroup = new ButtonGroup();
        for(UIManager.LookAndFeelInfo lafInfo: lookAndFeelInfos) {
            menu.add(createLookAndFeelItem(lafInfo.getName(), lafInfo.getClassName()));
        }  
        // Now load any look and feels defined externally as service via java.util.ServiceLoader
        LOOK_AND_FEEL_LOADER.iterator();
        for (LookAndFeel laf : LOOK_AND_FEEL_LOADER) {           
            menu.add(createLookAndFeelItem(laf.getName(), laf.getClass().getName()));
        }
         
        return menu;
    }
    
    protected JRadioButtonMenuItem createLookAndFeelItem(String lafName, String lafClassName) {
        JRadioButtonMenuItem lafItem = new JRadioButtonMenuItem();

        lafItem.setSelected(lafClassName.equals(lookAndFeel));
        lafItem.setHideActionText(true);
        lafItem.setAction(getAction("setLookAndFeel"));
        lafItem.setText(lafName);
        lafItem.setActionCommand(lafClassName);
        lookAndFeelRadioGroup.add(lafItem);
        
        return lafItem;
    }
    
    private javax.swing.Action getAction(String actionName) {
        return getContext().getActionMap().get(actionName);
    }
       
    // For displaying error messages to user
    protected void displayErrorMessage(String message, Exception ex) {
        JPanel messagePanel = new JPanel(new BorderLayout());       
        JLabel label = new JLabel(message);
        messagePanel.add(label);
        if (ex != null) {
            JComponent panel = new JXPanel(new BorderLayout());
            panel.setBorder(new RoundedBorder());
            
            // remind(aim): provide way to allow user to see exception only if desired
            StringWriter writer = new StringWriter();
            ex.printStackTrace(new PrintWriter(writer));
            JTextArea exceptionText = new JTextArea();
            exceptionText.setText("Cause of error:\n" +
                    writer.getBuffer().toString());
            exceptionText.setBorder(new RoundedBorder());
            exceptionText.setOpaque(false);
            exceptionText.setBackground(
                    Utilities.deriveColorHSB(UIManager.getColor("Panel.background"),
                    0, 0, -.2f));
            JScrollPane scrollpane = new JScrollPane(exceptionText);
            scrollpane.setBorder(EMPTY_BORDER);
            scrollpane.setPreferredSize(new Dimension(600,240));
            panel.add(scrollpane);
            messagePanel.add(panel, BorderLayout.SOUTH);            
        }
        JOptionPane.showMessageDialog(getMainFrame(), messagePanel, 
                resourceMap.getString("error.title"),
                JOptionPane.ERROR_MESSAGE);
                
    }
    
    // ----------------------- init/read/create Demos
    
    
    protected PropertyChangeListener getDemoPropertyChangeListener() {
        if (demoPropertyChangeListener == null) {
            demoPropertyChangeListener = new DemoPropertyChangeListener();
        }
        return demoPropertyChangeListener;
    }
    

    //------------------------ init/configure ui properties
    
    private void configureDefaults() {
        
        // Color palette algorithm courtesy of Jasper Potts
        Color controlColor = UIManager.getColor("control");
        
        UIManager.put(CONTROL_VERY_LIGHT_SHADOW_KEY, 
                Utilities.deriveColorHSB(controlColor, 0, 0, -0.02f));
        UIManager.put(CONTROL_LIGHT_SHADOW_KEY, 
                Utilities.deriveColorHSB(controlColor, 0, 0, -0.06f));
        UIManager.put(CONTROL_MID_SHADOW_KEY, 
                Utilities.deriveColorHSB(controlColor, 0, 0, -0.16f));
        UIManager.put(CONTROL_VERY_DARK_SHADOW_KEY, 
                Utilities.deriveColorHSB(controlColor, 0, 0, -0.5f));
        UIManager.put(CONTROL_DARK_SHADOW_KEY, 
                Utilities.deriveColorHSB(controlColor, 0, 0, -0.32f));
        
        // Calculate gradient colors for title panels
        Color titleColor = UIManager.getColor(usingNimbus()? "nimbusBase" : "activeCaption");

        // Some LAFs (e.g. GTK) don't contain "activeCaption" 
        if (titleColor == null) {
            titleColor = controlColor;
        }
        float hsb[] = Color.RGBtoHSB(
                titleColor.getRed(), titleColor.getGreen(), titleColor.getBlue(), null);
        UIManager.put(TITLE_GRADIENT_COLOR1_KEY, 
                Color.getHSBColor(hsb[0]-.013f, .15f, .85f));
        UIManager.put(TITLE_GRADIENT_COLOR2_KEY, 
                Color.getHSBColor(hsb[0]-.005f, .24f, .80f));
        UIManager.put(TITLE_FOREGROUND_KEY, 
                Color.getHSBColor(hsb[0], .54f, .40f));
        
        // Calculate highlight color for code pane
        UIManager.put(CODE_HIGHLIGHT_KEY,
                Color.getHSBColor(hsb[0]-.005f, .20f, .95f));
       
        Font labelFont = UIManager.getFont("Label.font");
        UIManager.put(TITLE_FONT_KEY, labelFont.deriveFont(Font.BOLD, labelFont.getSize()+4f));        
 
        Color panelColor = UIManager.getColor("Panel.background");
        UIManager.put(SUB_PANEL_BACKGROUND_KEY, 
                Utilities.deriveColorHSB(panelColor, 0, 0, -.06f));
        
        applyDefaults();
        
    } 
    
    protected void applyDefaults() {
        if (codeViewer != null) {
            codeViewer.setHighlightColor(UIManager.getColor(CODE_HIGHLIGHT_KEY));
        }        
    }
    

}
