/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package shared;

import ilog.views.gantt.action.IlvAction;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.java.Version;
import ilog.views.util.swing.IlvSwingUtil;
import ilog.views.util.swing.color.IlvJColorChooser;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.MenuDragMouseEvent;
import javax.swing.event.MenuDragMouseListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicToolTipUI;
import javax.swing.text.View;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import shared.help.HelpSystem;
import shared.help.NoHelpSystem;


/**
 * This is the abstract base class for all of the Gantt module demos. It
 * contains much of the common code for handling the menus, the status bar,
 * and so on.
 */
public abstract class AbstractExample extends JRootPane {

  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExample.class);

  // =========================================
  // Instance Variables
  // =========================================

  /**
   * The adapter that collects the status text of each menu and displays it in
   * the status bar.
   */
  private MenuInfo menuStatusAdapter;

  /**
   * The status bar used for showing information when menu items are selected.
   */
  private JLabel status;

  /**
   * The toolbar.
   */
  private JToolBar toolbar;

  /**
   * The example's top-level ancestor. This is the first ancestor of the example
   * that is a Window.
   */
  private JFrame rootAncestor;


  /**
   * The example's explicit locale. This is a duplication of the private
   * <code>locale</code> field of the <code>Component</code> superclass. It is
   * repeated here so that we can test whether the locale has been explicity
   * set or is being inherited from its parent container.
   */
  private Locale locale = null;

  /**
   * A blank icon.
   */
  private Icon blankIcon = null;

  /**
   * Indicates whether we have attempted to load the blank icon.
   */
  private boolean blankIconInit = false;

  /**
   * Indicates whether this example has permission to access the
   * local file system.
   */
  private Boolean localFileAccessAllowed;

  /**
   * The help system for the example.
   */
  private HelpSystem helpSystem;

  /**
   * Help actions.
   */
  protected IlvAction helpAction;
  protected IlvAction contextHelpAction;


  // =========================================
  // Class Constants
  // =========================================

  /**
   * The client-property lookup key for status text of each menu item.
   */
  private static final String STATUS_TEXT_KEY = "StatusText";

  /**
   * The client-property lookup key for whether a <code>JMenu</code> has any
   * menu item icons.
   */
  private static final String MENU_ITEM_ICONS_KEY = "MenuItemIcons";

  /**
   * The blank icon filename.
   */
  private static final String BLANK_ICON = "images/blank.gif";


  // =========================================
  // Instance Construction and Initialization
  // =========================================

  /**
   * Creates a new <code>AbstractExample</code>.
   */
  public AbstractExample() {
    // This will prevent the "SystemEventQueue" access message from appearing
    // in the Java console when this example is in a browser.
    getRootPane().putClientProperty("defeatSystemEventQueueCheck",
                                    Boolean.TRUE);
    menuStatusAdapter = new MenuInfo();
  }

  /**
   * Initializes the example's user interface in the specified container.
   *
   * @param container The container that the example is running in. This will
   * be the <code>JFrame</code> of the application.
   */
  public void init(Container container) {
    // If the locale has not been explicitly set, use the locale of the
    // container.
    if (locale == null) {
      setLocale(container.getLocale());
    }

    rootAncestor=(JFrame) SwingUtilities.getWindowAncestor(container);

    // Load custom L&Fs using the app's classloader. This works around JavaSoft
    // Bug #4155617, which prevents custom L&Fs from being loaded by the plugin.
    UIManager.put("ClassLoader", getClass().getClassLoader());

    // Disable the Metal tooltip L&F because, as of JDK 1.2.2, it displays
    // accelerator bindings incorrectly. See JavaSoft Bug #4306876.
    UIManager.put("ToolTipUI", "shared.AbstractExample$ToolTipUI");

    // Initialize the Help System
    initHelpSystem();
  }

  /**
   * This method is invoked after the example's user interface has been displayed
   * for the first time. You can override this method to perform customizations that
   * require a correctly positioned and laid out user interface, such as zooming to
   * fit.
   */
  public void postInit() {
  }

  /**
   * Returns the title of the example.
   *
   * @return The title of the example.
   */
  public abstract String getTitle();

  /**
   * Sets the locale of the example. This method is overridden to store a
   * locally accessible copy of the locale.
   *
   * @param locale The locale.
   */
  Override
  public void setLocale(Locale locale) {
    this.locale = locale;
    super.setLocale(locale);
  }

  /**
   * Sets the language-sensitive orientation of the example.
   * @param o The orientation.
   */
  Override
  public void setComponentOrientation(ComponentOrientation o) {
    IlvSwingUtil.updateComponentTreeOrientation(rootAncestor, o);
  }


  // =========================================
  // Application Permissions
  // =========================================

  /**
   * Returns <code>true</code> if this example has permission to exit the
   * Java VM.
   *
   * @return Whether the example has permission to exit the JVM.
   */
  protected boolean isExitAllowed() {
    SecurityManager security = System.getSecurityManager();
    if (security == null) {
      return true;
    }
    try {
      security.checkExit(0);
    } catch (SecurityException e) {
      return false;
    }
    return true;
  }

  /**
   * Returns <code>true</code> if this example should offer printing services.
   */
  protected boolean isPrintingConfigured() {
      return true;
  }

  /**
   * Returns <code>true</code> if this example has permission to print.
   *
   * @return Whether the example has permission to print.
   */
  protected boolean isPrintingAllowed() {
    SecurityManager security = System.getSecurityManager();
    if (security == null) {
      return true;
    }
    try {
      security.checkPrintJobAccess();
    } catch (SecurityException e) {
      return false;
    }
    return true;
  }

  /**
   * Returns <code>true</code> if this example has permission to access the
   * local file system. Typically, this will be <code>true</code> if the example
   * is running as an application.
   *
   * @return Whether the example has permission to access the local file system.
   */
  protected boolean isLocalFileAccessAllowed() {
    if (localFileAccessAllowed == null) {
      localFileAccessAllowed = Boolean.TRUE;
      // If there is no security manager, then we are probably running as an
      // application and everything is allowed. Otherwise, we check that we can
      // read the user's current directory.
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
        try {
          security.checkPropertyAccess("user.dir");
          String dir = System.getProperty("user.dir");
          security.checkRead(dir);
        } catch (SecurityException e) {
          localFileAccessAllowed = Boolean.FALSE;
        }
      }
    }
    return localFileAccessAllowed;
  }


  // =========================================
  // Accessing Resources
  // =========================================

  /**
   * Returns the fully qualified URL of a resource file that is specified relative to the
   * working directory of the example. Note that this is different than calling
   * <code>Class.getResource()</code>, which performs resource lookup relative to the
   * example's classpath.
   *
   * @param relativePath The path of the resource file, relative to the working directory
   * of the example.
   * @return The resource URL.
   * @throws IOException if the path cannot be resolved to a valid and existing URL.
   *
   */
  public URL getResourceURL(String relativePath)
      throws IOException {
    relativePath = relativePath.replace('\\', '/');
    URL url = null;

    // In an application context, we try to find an external file relative to the
    // current working directory. If an external file does not exist, then the file
    // may be bundled into the jar with the classes. In this case, we prepend a '/'
    // to relativePath so that we search relative to the classloader's root and not
    // relative to the packaging of the current class.

      File file = new File(relativePath);
      // If the file exists in the external file system, we return its corresponding URL.
      if (file.exists()) {
        url = file.toURI().toURL();
      }
      // Otherwise, we search for the file relative to the classloader's root. This will
      // find the file if it is packaged into a jar with the classes.
      else {
        // Prepend a '/' so that we search relative to the classloader's root and not
        // relative to the packaging of the current class.
        if (relativePath.charAt(0) != '/')  {
          relativePath = '/' + relativePath;
        }
        url = getClass().getResource(relativePath);
      }
    

    // Verify that we have a valid URL by trying to open its associated stream.
    if (url == null) {
      throw new FileNotFoundException(relativePath);
    }
    InputStream stream = url.openStream();
    stream.close();
    return url;
  }


  // =========================================
  // Status Bar
  // =========================================

  /**
   * Sets the status text of the specified component.
   *
   * @param component The component.
   * @param text The status text.
   */
  protected void setStatusText(JComponent component, String text) {
    component.putClientProperty(STATUS_TEXT_KEY, text);
    if (component instanceof JMenuItem) {
      JMenuItem menuItem = (JMenuItem) component;
      menuItem.addMenuDragMouseListener(menuStatusAdapter);
      if (!(menuItem instanceof JMenu)) {
        menuItem.addMouseMotionListener(menuStatusAdapter);
      }
    }
    if (component instanceof JMenu) {
      ((JMenu) component).addMenuListener(menuStatusAdapter);
    }
  }

  /**
   * Returns the status text of the specified component.
   *
   * @param component The component.
   * @return The status text.
   */
  protected String getStatusText(JComponent component) {
    return (String) component.getClientProperty(STATUS_TEXT_KEY);
  }

  /**
   * Creates the status bar component.
   *
   * @return The status bar.
   */
  protected JLabel createStatusBar() {
    status = new JLabel(" ");
    status.setBorder(new BevelBorder(BevelBorder.LOWERED));
    return status;
  }

  /**
   * Returns the status bar component.
   *
   * @return The status bar.
   */
  protected JLabel getStatusBar() {
    return status;
  }


  // =========================================
  // ToolBar
  // =========================================

  /**
   * Creates the toolbar component.
   *
   * @return The toolbar.
   */
  protected JToolBar createToolBar() {
    toolbar = new JToolBar();
    toolbar.setFloatable(false);
    populateToolBar(toolbar);
    return toolbar;
  }

  /**
   * Populates the specified toolbar. This default implementation does nothing.
   * Subclasses should override this method as needed.
   *
   * @param toolbar The toolbar.
   */
  protected void populateToolBar(JToolBar toolbar) {
  }

  /**
   * Returns the toolbar component.
   *
   * @return The toolbar.
   */
  protected JToolBar getToolBar() {
    return toolbar;
  }

  /**
   * Adds the specified action to a toolbar and returns the created
   * <code>JButton</code>.
   *
   * @param toolbar The toolbar.
   * @param action The action.
   * @return The button.
   */
  protected JButton addAction(JToolBar toolbar, IlvAction action) {
    // Create the button from the action. This configures the button text, icon,
    // tooltip, and mnemonic.
    JButton button = new JButton(action);
    // Only use text on buttons if there is no icon.
    if (button.getIcon() != null) {
      button.setText(null);
    }
    // Register the accelerator as a window-wide shortcut.
    KeyStroke accelerator = action.getAccelerator();
    if (accelerator != null) {
      button.registerKeyboardAction(action, accelerator,
                                    JButton.WHEN_IN_FOCUSED_WINDOW);
    }
    // Add the accerator key to the end of the tooltip.
    String shortDescription = action.getShortDescription();
    if (shortDescription != null) {
      if (accelerator != null) {
        shortDescription += ToolTipUI.TIP_DELIMITER
            + action.getAcceleratorText();
      }
      button.setToolTipText(shortDescription);
    }
    // Add the button to the toolbar.
    toolbar.add(button);
    return button;
  }


  // =========================================
  // Menus
  // =========================================

  /**
   * Sets the menu bar for the top-level ancestor of the sample (either the
   * containing <code>JFrame</code> ).
   */
  Override
  public void setJMenuBar(JMenuBar menuBar) {    
    ((JFrame)rootAncestor).setJMenuBar(menuBar);
  }
  
  /**
   * Returns the blank icon that can be used to left-align menu items that do
   * not have an icon of their own.
   *
   * @return The blank filler icon.
   */
  protected Icon getBlankIcon() {
    if (!blankIconInit) {
      try {
        Image image = IlvImageUtil.getImageFromFile(AbstractExample.class,
                                                    BLANK_ICON);
        blankIcon = new ImageIcon(image);
      } catch (Exception e) {
        System.err.println("Warning: error while reading image " + BLANK_ICON);
      }
      blankIconInit = true;
    }
    return blankIcon;
  }

  /**
   * Calculates whether the specified menu has any menu items with icons.
   *
   * @param menu The menu.
   * @return Whether the menu has any items with icons.
   */
  private boolean computeHasIcons(JMenu menu) {
    for (int i = 0; i < menu.getMenuComponentCount(); i++) {
      Component c = menu.getMenuComponent(i);
      if (c instanceof AbstractButton && ((AbstractButton) c).getIcon() != null) {
        return true;
      }
    }
    return false;
  }

  /**
   * Sets whether the specified menu has any menu items with icons.
   *
   * @param menu The menu.
   * @param flag Indicates whether the menu has any items with icons.
   */
  protected void setHasIcons(JMenu menu, boolean flag) {
    menu.putClientProperty(MENU_ITEM_ICONS_KEY, flag);
  }

  /**
   * Returns whether the specified menu has any menu items with icons.
   *
   * @param menu The menu.
   * @return Whether the menu has any items with icons.
   */
  protected boolean hasIcons(JMenu menu) {
    Boolean flag = (Boolean) menu.getClientProperty(MENU_ITEM_ICONS_KEY);
    if (flag == null) {
      flag = computeHasIcons(menu);
      menu.putClientProperty(MENU_ITEM_ICONS_KEY, flag);
    }
    return flag;
  }

  /**
   * Adds the specified action to a menu and returns the created
   * <code>JMenuItem</code>.
   *
   * @param menu The menu.
   * @param action The action.
   * @return The menu item.
   */
  protected JMenuItem addAction(JMenu menu, IlvAction action) {
    return addAction(menu, action, null);
  }

  /**
   * Adds the specified action to a menu and makes it possible to invoke the
   * action through additional accelerators. (The primary accelerator is
   * <code>action.getAccelerator()</code>.)
   * Returns the created <code>JMenuItem</code>.
   *
   * @param menu The menu.
   * @param action The action.
   * @param additionalAccelerators a set of keystrokes, or <code>null</code>.
   * @return The menu item.
   */
  protected JMenuItem addAction(JMenu menu, IlvAction action,
                                KeyStroke[] additionalAccelerators) {
    // Create the menu item from the action. This configures the item text,
    // icon, and mnemonic. Under JDK 1.4 and later, it also configures the item
    // tooltip and accelerator keystroke.
    JMenuItem menuItem = new JMenuItem(action);
    // Under JDK 1.3 and earlier we must explicitly configure the menu item's
    // accelerator from the action.
    KeyStroke accelerator = action.getAccelerator();
    if (accelerator != null) {
      menuItem.setAccelerator(accelerator);
    }
    // Enable the additional accelerators.
    if (additionalAccelerators != null && additionalAccelerators.length > 0) {
      String name = action.getName();
      // Here we assume that different actions have different names.
      for (KeyStroke ks : additionalAccelerators) {
        menu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, name);
      }
      menu.getActionMap().put(name, action);
    }
    // Under JDK 1.4 and later, explicitly disable the menu item tooltip.
    menuItem.setToolTipText(null);
    // If we are about to add the first item with an icon, set all other items
    // to have a blank icon for proper alignment.
    if (!hasIcons(menu) && menuItem.getIcon() != null) {
      for (int i = 0; i < menu.getMenuComponentCount(); i++) {
        Component c = menu.getMenuComponent(i);
        if (c instanceof AbstractButton && ((AbstractButton) c).getIcon() != null) {
          ((AbstractButton) c).setIcon(getBlankIcon());
        }
      }
      setHasIcons(menu, true);
    }
    // If we are about to add an action with no icon to a menu that has icons,
    // use a blank icon for the menu item so that it is properly aligned.
    if (hasIcons(menu) && menuItem.getIcon() == null) {
      menuItem.setIcon(getBlankIcon());
    }
    // Configure the status bar text for the menu item.
    String longDescription = action.getLongDescription();
    if (longDescription == null) {
      longDescription = " ";
    }
    setStatusText(menuItem, longDescription);
    // Add the item to the menu.
    menu.add(menuItem);
    return menuItem;
  }


  // =========================================
  // Look and Feel
  // =========================================

  /**
   * Sets the look-and-feel of the example.
   *
   * @param laf The look-and-feel.
   */
  protected void setLookAndFeel(UIManager.LookAndFeelInfo laf) {
    try {
      UIManager.setLookAndFeel(laf.getClassName());
      Container c = rootAncestor;
      SwingUtilities.updateComponentTreeUI(c);
    } catch (Exception e) {
      System.err.println("Failed to install " + laf.getName() + "L&F\n" + e);
    }
  }

  /**
   * Creates a menu that allows the user to switch between the
   * available look-and-feels.
   *
   * @return The look-and-feel menu.
   */
  protected JMenu createLAFMenu() {
    JMenu lafMenu = new JMenu("LookAndFeel");
    lafMenu.setMnemonic(KeyEvent.VK_O);
    setHelpID(lafMenu, "Menu.LAF.helpID");
    setStatusText(lafMenu, "Selects a Look-and-Feel for the example.");
    ButtonGroup lafGroup = new ButtonGroup();
    ItemListener menuListener = new ItemListener()
    {
      Override
      public void itemStateChanged(ItemEvent evt) {
        JRadioButtonMenuItem button = (JRadioButtonMenuItem) evt.getSource();
        if (button.isSelected()) {
          UIManager.LookAndFeelInfo selectedLF =
              (UIManager.LookAndFeelInfo) button.getClientProperty("UIKey");
          setLookAndFeel(selectedLF);
        }
      }
    };
    // Get the list of installed look-and-feels from the Swing UIManager.
    UIManager.LookAndFeelInfo[] installedLAFs =
        UIManager.getInstalledLookAndFeels();
    // Add the Kunststoff look-and-feel to the list.
    List<UIManager.LookAndFeelInfo> lafs = new ArrayList<UIManager.LookAndFeelInfo>();
    lafs.addAll(Arrays.asList(installedLAFs));
    lafs.add(new UIManager.LookAndFeelInfo("Kunststoff",
                                           "com.incors.plaf.kunststoff.KunststoffLookAndFeel"));
    // Populate the menu with the LAFs.
    for (UIManager.LookAndFeelInfo lafInfo : lafs) {
      String lafName = lafInfo.getName();
      String lafClassName = lafInfo.getClassName();
      boolean lafSupported = false;

      // We need to verify that the LAF actually exists and is supported.
      if (!Version.isJava9OrLater()) {
              try {
                Class<?> lafClass = Class.forName(lafClassName);
                LookAndFeel laf = (LookAndFeel) lafClass.getDeclaredConstructor().newInstance();
                lafSupported = laf.isSupportedLookAndFeel();
              } catch (Throwable t) {
            LOGGER.debug("AbstractExample.createLAFMenu: ", t);
              }
      } else {
          // For Java 9 or later, it is no longer acceptable to use core reflection to instantiate
          // LookAndFeel instances, since they are internal to the JDK.  We must instead call
          // method UIManager.createLookAndFeel. An UnsupportedLookAndFeelException being thrown
          // indicates the LookAndFeel is not supported by the underlying platform.
          // See: https://bugs.openjdk.java.net/browse/JDK-8178826
          try {
            Method method = UIManager.class.getMethod("createLookAndFeel", String.class);
                method.invoke(null, lafInfo.getName());
                lafSupported = true; // Since no UnsupportedLookAndFeelException was thrown.
          } catch (Throwable t) {
            LOGGER.debug("AbstractExample.createLAFMenu: ", t);
          }
      }
      
      if (lafSupported) {
        JRadioButtonMenuItem rb = new JRadioButtonMenuItem(lafName);
        lafMenu.add(rb);
        rb.setSelected(UIManager.getLookAndFeel().getName().equals(lafName));
        rb.putClientProperty("UIKey", lafInfo);
        rb.addItemListener(menuListener);
        lafGroup.add(rb);
      }
    }
    return lafMenu;
  }


  // =========================================
  // Help System
  // =========================================

  /**
   * An empty help properties dictionary.
   */
  private static final HashMap<String,Object> EMPTY_HELP_PROPERTIES =
    new HashMap<String,Object>();

  /**
   * Returns the dictionary of Help System properties for this example. Each key
   * and its corresponding value must be a string. This default implementation
   * returns an empty dictionary, indicating that the example has no Help System.
   * <p>
   * The dictionary should define the following keys in order to setup a
   * a basic Help System for the example:
   * <ul>
   *   <li>
   *    HelpSet.name - The name of the help set for the example. This should be
   *    the name of the .HS file, relative to the location of the example's main
   *    class file.</li>
   *   <li>
   *    HelpSet.mainTopic - The top-level help ID for the entire example.</li>
   *   <li>
   *    Gantt.helpID - The help ID for the entire Gantt chart.</li>
   *   <li>
   *    Menu.helpID - The help ID for the entire menu bar.</li>
   *   <li>
   *    Menu.LAF.helpID - The help ID for the LAF Menu.</li>
   *   <li>
   *    Menu.Help.helpID - The help ID for the Help Menu.</li>
   *   <li>
   *    Toolbar.helpID - The help ID for the entire toolbar.</li>
   *   <li>
   *    Customizer.helpID - The help ID for the entire customizer panel.</li>
   * </ul>
   *
   * @return The dictionary of help system properties.
   */
  protected Map<String,Object> getHelpProperties() {
    return EMPTY_HELP_PROPERTIES;
  }

  /**
   * Returns the value of the specified Help System property. The following
   * pre-defined keys should be used to setup a basic Help System for the
   * example:
   * <ul>
   *   <li>
   *    HelpSet.name - The name of the help set for the example. This should be
   *    the name of the .HS file, relative to the location of the example's main
   *    class file.</li>
   *   <li>
   *    HelpSet.mainTopic - The top-level help ID for the entire example.</li>
   *   <li>
   *    Gantt.helpID - The help ID for the entire Gantt chart.</li>
   *   <li>
   *    Menu.helpID - The help ID for the entire menu bar.</li>
   *   <li>
   *    Menu.LAF.helpID - The help ID for the LAF Menu.</li>
   *   <li>
   *    Menu.Help.helpID - The help ID for the Help Menu.</li>
   *   <li>
   *    Toolbar.helpID - The help ID for the entire toolbar.</li>
   *   <li>
   *    Customizer.helpID - The help ID for the entire customizer panel.</li>
   * </ul>
   *
   * @param key The help ID.
   * @return The value of the help property.
   */
  protected String getHelpProperty(String key) {
    Map<String,Object> props = getHelpProperties();
    Object objVal = props.get(key);
    return (objVal instanceof String)
        ? (String) objVal
        : null;
  }

  /**
   * Sets the helpID for a component, based upon the specified Help System
   * property.
   *
   * @param comp The component.
   * @param key The help ID.
   */
  protected void setHelpID(Component comp, String key) {
    if (!hasHelpSystem() || key == null) {
      return;
    }
    String id = getHelpProperty(key);
    if (id != null) {
      getHelpSystem().setHelpIDString(comp, id);
    }
  }

  /**
   * Sets the helpID for a menu item, based upon the specified Help System
   * property.
   *
   * @param item The menu item.
   * @param key The help ID.
   */
  protected void setHelpID(MenuItem item, String key) {
    if (!hasHelpSystem() || key == null) {
      return;
    }
    String id = getHelpProperty(key);
    if (id != null) {
      getHelpSystem().setHelpIDString(item, id);
    }
  }

  /**
   * Returns the class name of the Java Help based help system that should be
   * used if possible. The default is <code>"shared.help.JavaHelpSystem"</code>.
   * <p>
   * This method can be overridden to return the name of a subclass of
   * <code>shared.help.JavaHelpSystem</code>.
   */
  protected String getJavaHelpSystemClassName() {
    return "shared.help.JavaHelpSystem";
  }

  /**
   * Creates the help system.
   * <p>
   * This method creates an instance of the class designated by
   * <code>getJavaHelpSystemClassName()</code> if possible, or an instance of
   * <code>shared.help.NoHelpSystem</code> otherwise.
   */
  protected HelpSystem createHelpSystem() {

    String className = getJavaHelpSystemClassName();
    try {
      Class<?> clazz = Class.forName(className);
      return (HelpSystem) clazz.getDeclaredConstructor().newInstance();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (ExceptionInInitializerError e) {
      e.getException().printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (SecurityException e) {
      e.printStackTrace();
    } catch (ClassCastException e) {
      e.printStackTrace();
    } catch (NoClassDefFoundError e) {
      // Some dependency class is not found in the CLASSPATH.
      // This happens when jhbasic.jar is not in the CLASSPATH.
    } catch (LinkageError e) {
      e.printStackTrace();
    } catch (Throwable e) {
      e.printStackTrace();
    }

    return new NoHelpSystem();
  }

  /**
   * Initializes the Help System for the example.
   */
  protected void initHelpSystem() {
    String helpSetName = getHelpProperty("HelpSet.name");
    if (helpSetName != null) {
      helpSystem = createHelpSystem();
      helpSystem.init(helpSetName, getClass());
    } else {
      helpSystem = new NoHelpSystem();
    }
  }

  /**
   * Returns the help system for the example.
   *
   * @return The help system.
   */
  public HelpSystem getHelpSystem() {
    return helpSystem;
  }

  /**
   * Returns true if the example has a Help System.
   *
   * @return Whether the example has a help system.
   */
  public boolean hasHelpSystem() {
    return !(getHelpSystem() instanceof NoHelpSystem);
  }

  /**
   * Initializes the Help System actions.
   *
   * @return Whether the example has a help system that was successfully initialized.
   */
  private boolean initHelpActions() {
    if (!hasHelpSystem()) {
      return false;
    }
    if (helpAction == null) {
      final ActionListener helpActionListener =
        getHelpSystem().createShowHelpActionListener();
      helpAction = new IlvAction("Help Topics",
                                 null,
                                 KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0),
                                 "Help",
                                 "Displays help about this example.") {
        Override
        public void actionPerformed(ActionEvent event) {
          helpActionListener.actionPerformed(event);
        }
      };
      helpAction.setIcon(AbstractExample.class, "images/help.gif");
    }
    if (contextHelpAction == null) {
      final ActionListener contextHelpActionListener =
        getHelpSystem().createShowContextSensitiveHelpActionListener();
      contextHelpAction =
          new IlvAction("What's This?",
                        null,
                        KeyStroke.getKeyStroke(KeyEvent.VK_F1,
                                               KeyEvent.SHIFT_DOWN_MASK),
                        "Context Sensitive Help",
                        "Displays help about this example.")
          {
            Override
            public void actionPerformed(ActionEvent event) {
              contextHelpActionListener.actionPerformed(event);
            }
          };
      contextHelpAction.setIcon(AbstractExample.class, "images/cshelp.gif");
    }
    return true;
  }

  /**
   * Creates the Help menu. This method returns <code>null</code> if the example
   * does not have a Help System.
   *
   * @return The help menu.
   */
  public JMenu createHelpMenu() {
    if (!initHelpActions()) {
      return null;
    }
    JMenu menu = new JMenu("Help");
    menu.setMnemonic(KeyEvent.VK_H);
    setHelpID(menu, "Menu.Help.helpID");
    setStatusText(menu, "Displays help about this example.");
    JMenuItem helpItem = addAction(menu, helpAction);
    setHelpID(helpItem, "HelpSet.mainTopic");
    addAction(menu, contextHelpAction);
    return menu;
  }

  /**
   * Populates the specified toolbar with the Help System actions.
   *
   * @param toolbar The toolbar.
   */
  protected void populateToolBarHelpActions(JToolBar toolbar) {
    if (!initHelpActions()) {
      return;
    }
    toolbar.addSeparator();
    JButton helpButton = addAction(toolbar, helpAction);
    setHelpID(helpButton, "HelpSet.mainTopic");
    addAction(toolbar, contextHelpAction);
  }


  // =========================================
  // Colors
  // =========================================

  /**
   * Shows a modal color-chooser dialog and blocks until the dialog is hidden.
   * If the user presses the "OK" button, this method hides/disposes the
   * dialog and returns the selected color. If the user presses the "Cancel"
   * button or closes the dialog without pressing "OK", this method
   * hides/disposes the dialog and returns <code>null</code>.
   *
   * @param title The title of the dialog.
   * @param initialColor The initial color set when the color-chooser is shown.
   * @return The color.
   */
  protected Color chooseColor(String title, Color initialColor) {
    int alpha = initialColor.getAlpha();
    Color newColor = IlvJColorChooser.showDialog(this, title, initialColor);
    if (newColor != null) {
      if (newColor.getAlpha() != alpha) {
        newColor = new Color(newColor.getRed(),
                             newColor.getGreen(),
                             newColor.getBlue(),
                             alpha);
      }
    }
    return newColor;
  }

  /**
   * <code>MenuInfo</code> is an inner class used for displaying information on
   * menus and menu items. The status text of the menu item is displayed in the
   * status bar. This class should be registered as a
   * <code>MenuDragMouseListener</code>
   * and as a <code>MouseMotionListener</code> with <code>JMenuItem</code>s. It
   * should be registered as a <code>MenuDragMouseListener</code> and as a
   * <code>MenuListener</code> with <code>JMenu</code>s.
   */
  class MenuInfo implements MenuDragMouseListener,
      MenuListener,
      MouseMotionListener {

    // Text used to clear the status bar.
    private static final String NULL_TEXT = " ";

    // The current text being displayed.
    private String currentText = NULL_TEXT;

    // Clears the status bar.
    private void clearStatus() {
      JLabel statusBar = getStatusBar();
      if (statusBar == null) {
        return;
      }
      if (!NULL_TEXT.equals(currentText)) {
        currentText = NULL_TEXT;
        statusBar.setText(NULL_TEXT);
      }
    }

    // Sets the status bar based on the source of the specified event.
    private void setStatus(EventObject e) {
      JLabel statusBar = getStatusBar();
      if (statusBar == null) {
        return;
      }
      Object source = e.getSource();
      if (source instanceof JComponent) {
        String str = getStatusText((JComponent) source);
        if (str == null || str.equals("")) {
          clearStatus();
        } else if (!currentText.equals(str)) {
          currentText = str;
          statusBar.setText(str);
        }
      } else {
        clearStatus();
      }
    }

    // A JMenu has been cancelled. Clears the status bar.
    Override
    public void menuCanceled(MenuEvent e) {
      clearStatus();
    }

    // A JMenu has been deselected. Clears the status bar.
    Override
    public void menuDeselected(MenuEvent e) {
      clearStatus();
    }

    // A JMenu has been selected. Sets the status bar to its tooltip text,
    // if any.
    Override
    public void menuSelected(MenuEvent e) {
      setStatus(e);
    }

    // The user has moved the mouse into a menu element. Sets the status bar to
    // its tooltip text, if any.
    Override
    public void menuDragMouseEntered(MenuDragMouseEvent e) {
      setStatus(e);
    }

    // The user has moved the mouse out of a menu element. Clears the status
    // bar.
    Override
    public void menuDragMouseExited(MenuDragMouseEvent e) {
      clearStatus();
    }

    // The user has dragged the mouse within a menu element. Sets the status
    // bar to its tooltip text, if any.
    Override
    public void menuDragMouseDragged(MenuDragMouseEvent e) {
      setStatus(e);
    }

    // The user has moved the mouse within a menu element. Sets the status bar
    // to its tooltip text, if any.
    Override
    public void mouseMoved(MouseEvent e) {
      setStatus(e);
    }

    Override
    public void menuDragMouseReleased(MenuDragMouseEvent e) {
    }

    Override
    public void mouseDragged(MouseEvent e) {
    }
  }

  /**
   * <code>ToolTipUI</code> is an inner class that replaces the standard LAF
   * delegate for all application tooltips. This LAF serves the purpose of
   * displaying accelerator bindings correctly. As of JDK 1.2.2, the standard
   * Metal tooltip UI displays accelerator bindings incorrectly
   * (see JavaSoft bug #4306876).
   * First, the Metal tooltip UI attempts to derive the accelerator binding
   * automatically from the underlying component. Unfortunately, this only
   * works when the underlying component has a single keystroke binding.
   * Second, the Metal tooltip UI displays non-ASCII keystrokes incorrectly,
   * such as VK_DELETE, and so on.
   * This tooltip UI corrects the first problem by parsing the underlying
   * component's tooltip text at the first vertical bar. All text before the
   * delimiter is considered to be the normal tooltip text and all text
   * following the delimiter is considered to be the accelerator binding text.
   */
  public static class ToolTipUI extends BasicToolTipUI {

    private static ToolTipUI sharedInstance = new ToolTipUI();

    private static final int HORIZONTAL_MARGIN = 3;

    private static final int VERTICAL_MARGIN = 2;

    private static final String ACCELERATOR_FONT_KEY = "ACCEL_FONT";

    public static final String TIP_DELIMITER = "|";

    private static final int PAD_SPACE_BETWEEN_STRINGS = 15;

    /**
     * Creates a new <code>AbstractExample.ToolTipUI</code>.
     */
    public ToolTipUI() {
    }

    public static ComponentUI createUI(JComponent c) {
      return sharedInstance;
    }

    Override
    public void installUI(JComponent c) {
      super.installUI(c);
      Font f = c.getFont();
      // Store a smaller version of the tooltip font as a client property of
      // the JToolTip itself. This will be used to display the accelerator
      // text to the right of the normal tooltip text.
      Font acceleratorFont = new Font(f.getName(), f.getStyle(), f.getSize() - 2);
      c.putClientProperty(ACCELERATOR_FONT_KEY, acceleratorFont);
    }

    /**
     * Returns the font used to display the accelerator keystroke binding from
     * the specified <code>JToolTip</code> component.
     *
     * @param c The component.
     * @return The accelerator font.
     */
    public Font getAcceleratorFont(JComponent c) {
      return (Font) c.getClientProperty(ACCELERATOR_FONT_KEY);
    }

    /**
     * Returns the tooltip text from the specified <code>JToolTip</code>
     * component.
     *
     * @param c The component.
     * @return The tooltip text.
     */
    public String getTipText(JComponent c) {
      String text = ((JToolTip) c).getTipText();
      if (text == null || text.equals("")) {
        text = "";
      }
      int index = text.indexOf(TIP_DELIMITER);
      if (index >= 0) {
        text = text.substring(0, index);
      }
      return text;
    }

    /**
     * Returns the text representation of the accelerator keystroke binding
     * from the specified <code>JToolTip</code> component.
     *
     * @param c The component.
     * @return The accelerator text.
     */
    public String getAcceleratorText(JComponent c) {
      String text = ((JToolTip) c).getTipText();
      if (text == null || text.equals("")) {
        text = "";
      }
      int index = text.indexOf(TIP_DELIMITER);
      if (index < 0) {
        return "";
      }
      return text.substring(index + 1);
    }

    Override
    public void paint(Graphics g, JComponent c) {
      Font font = c.getFont();
      FontMetrics fm = c.getFontMetrics(font);
      Dimension size = c.getSize();
      Insets insets = c.getInsets();
      g.setColor(c.getBackground());
      g.fillRect(0, 0, size.width, size.height);
      g.setColor(c.getForeground());
      g.setFont(font);
      String tipText = getTipText(c);
      View v = (View) c.getClientProperty("html");
      if (v != null) {
        Rectangle paintTextR = c.getBounds();
        paintTextR.x += insets.left;
        paintTextR.y += insets.top;
        paintTextR.width -= insets.left + insets.right;
        paintTextR.height -= insets.top + insets.bottom;
        v.paint(g, paintTextR);
      } else {
        g.drawString(tipText, insets.left + HORIZONTAL_MARGIN,
                     insets.top + VERTICAL_MARGIN + fm.getAscent());
      }
      String accelText = getAcceleratorText(c);
      if (!accelText.equals("")) {
        Font acceleratorFont = getAcceleratorFont(c);
        g.setFont(acceleratorFont);
        g.drawString(accelText,
                     insets.left + HORIZONTAL_MARGIN
                     + SwingUtilities.computeStringWidth(fm, tipText)
                     + PAD_SPACE_BETWEEN_STRINGS,
                     insets.top + VERTICAL_MARGIN + fm.getAscent());
      }
    }

    Override
    public Dimension getPreferredSize(JComponent c) {
      Font font = c.getFont();
      Insets insets = c.getInsets();
      Dimension prefSize = new Dimension(insets.left + insets.right,
                                         insets.top + insets.bottom);
      String text = getTipText(c);
      if (!text.equals("")) {
        View v = (c != null) ? (View) c.getClientProperty("html") : null;
        if (v != null) {
          prefSize.width += (int) v.getPreferredSpan(View.X_AXIS);
          prefSize.height += (int) v.getPreferredSpan(View.Y_AXIS);
        } else {
          FontMetrics fm = c.getFontMetrics(font);
          prefSize.width += SwingUtilities.computeStringWidth(fm, text)
              + (2 * HORIZONTAL_MARGIN);
          prefSize.height += fm.getHeight() + (2 * VERTICAL_MARGIN);
        }
      }
      String accelText = getAcceleratorText(c);
      if (!accelText.equals("")) {
        FontMetrics accelFM = c.getFontMetrics(getAcceleratorFont(c));
        prefSize.width += SwingUtilities.computeStringWidth(accelFM, accelText)
            + PAD_SPACE_BETWEEN_STRINGS;
      }
      return prefSize;
    }

  }

}