/*
 * 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 ganttviewer;

import ganttviewer.StyleManager.Style;
import ganttviewer.actions.GanttViewerDeleteConstraintAction;
import ganttviewer.actions.GanttViewerDeleteRowAction;
import ganttviewer.actions.GanttViewerEditPropertiesAction;
import ganttviewer.actions.GanttViewerInsertConstraintAction;
import ganttviewer.actions.GanttViewerInsertRowAction;
import ganttviewer.actions.GanttViewerMagnifyAction;
import ganttviewer.actions.GanttViewerPanAction;
import ganttviewer.actions.GanttViewerRowDownAction;
import ganttviewer.actions.GanttViewerRowExpandCollapseAction;
import ganttviewer.actions.GanttViewerRowIndentAction;
import ganttviewer.actions.GanttViewerRowOutdentAction;
import ganttviewer.actions.GanttViewerRowUpAction;
import ganttviewer.actions.GanttViewerSelectAction;
import ganttviewer.actions.GanttViewerZoomInAction;
import ganttviewer.actions.GanttViewerZoomOutAction;
import ganttviewer.actions.GanttViewerZoomToFitAction;
import ganttviewer.exceptions.ApplyStyleException;
import ganttviewer.model.GanttViewerConstraint;
import ganttviewer.util.CustomExample;
import ganttviewer.util.GanttViewerLogger;
import ganttviewer.util.GanttViewerProperties;
import ganttviewer.util.ImageUtilities;
import ganttviewer.util.WindowSizeUtils;
import ganttviewer.util.help.HelpPanel;
import ganttviewer.viewer.View;
import ganttviewer.viewer.Viewer;
import ganttviewer.viewer.ViewerAbstractAction;
import ganttviewer.viewer.event.CurrentViewChangedEvent;
import ganttviewer.viewer.event.CurrentViewListener;
import ganttviewer.viewer.exceptions.AttachModelException;
import ganttviewer.viewer.exceptions.ViewException;
import ganttviewer.views.GanttTableView;
import ganttviewer.views.GanttView;
import ganttviewer.views.MonthlyCalendarView;
import ganttviewer.views.PERTView;
import ganttviewer.views.ResourceUsageView;
import ganttviewer.views.ScheduleTableView;
import ilog.views.gantt.IlvActivity;
import ilog.views.gantt.IlvActivityFactory;
import ilog.views.gantt.IlvConstraint;
import ilog.views.gantt.IlvConstraintFactory;
import ilog.views.gantt.IlvConstraintType;
import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvTimeInterval;
import ilog.views.gantt.IlvTimeScrollController;
import ilog.views.gantt.action.IlvAction;
import ilog.views.gantt.model.IlvDefaultGanttModel;
import ilog.views.gantt.model.general.IlvGeneralActivity;
import ilog.views.gantt.model.general.IlvGeneralConstraint;
import ilog.views.gantt.xml.IlvGanttDocumentReader;
import ilog.views.gantt.xml.IlvGeneralActivityReader;
import ilog.views.gantt.xml.IlvGeneralConstraintReader;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.swing.IlvSwingUtil;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.filechooser.FileFilter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import shared.AbstractExample;
import shared.GanttCommand;
import shared.swing.ExampleFrame;

/**
 * <code>GanttViewer</code> is the main class of the sample.
 */
SuppressWarnings("serial")
public class GanttViewer extends CustomExample {

  private static int ACTIVITY_ID_INCR = 0;

  /**
   * Available actions property.
   */
  public static final String ACTIONS_SUFFIX = ".Actions";

  /**
   * Calendar view ID.
   */
  public static final String CALENDAR_VIEW = "View.Calendar";

  /**
   * Gantt Chart view ID.
   */
  public static final String GANTT_CHART_VIEW = "View.GanttChart";

  /**
   * Resource Sheet view ID.
   */
  public static final String RESOURCE_SHEET_VIEW = "View.ResourceSheet";

  /**
   * Resource Usage view ID.
   */
  public static final String RESOURCE_USAGE_VIEW = "View.ResourceUsage";

  /**
   * Task Sheet view ID.
   */
  public static final String TASK_SHEET_VIEW = "View.TaskSheet";

  /**
   * PERT View view ID.
   */
  public static final String PERT_VIEW = "View.PERTDiagram";

  /**
   * The frame icon.
   */
  private static final String ICON_IMAGE = "ilog32.gif";

  /**
   * View icon property.
   */
  private static final String CHANGE_VIEW_ICON_SUFFIX = ".Icon";

  /**
   * View short description property.
   */
  private static final String CHANGE_VIEW_SHORT_DESCRIPTION = "Action.ChangeView.Tooltip";

  /**
   * View long description property.
   */
  private static final String CHANGE_VIEW_LONG_DESCRIPTION = "Action.ChangeView.Description";

  /**
   * The viewer.
   */
  private Viewer viewer = null;

  /**
   * Activity factory.
   */
  private final IlvActivityFactory activityFactory = new ActivityFactory();

  /**
   * Constraint factory.
   */
  private final IlvConstraintFactory constraintFactory = new ConstraintFactory();

  /**
   * Handle lag time management.
   */
  private LagTimeHandler lagTimeHandler;

  /**
   * List of available views.
   */
  private JComponent viewBar = null;

  /**
   * Button updater.
   */
  private final ButtonUpdater buttonUpdater = new ButtonUpdater();

  /**
   * MenuItem updater.
   */
  private final MenuItemUpdater menuUpdater = new MenuItemUpdater();

  /**
   * Style manager.
   */
  private final StyleManager styleManager = new StyleManager();

  /**
   * Changes view actions.
   */
  private final Map<String, ChangeViewAction> changeViewActions = new HashMap<String, ChangeViewAction>();

  /**
   * Show/Hide Help Panel action.
   */
  private final ShowHideHelpAction showHideHelpAction = new ShowHideHelpAction();

  /**
   * The actions that set styles.
   */
  private Set<SetStyleAction> setStyleActions = new HashSet<SetStyleAction>();

  /**
   * Help panel.
   */
  private HelpPanel helpPanel;

  /**
   * Show/Hide help button.
   */
  private JToggleButton helpButton;

  /**
   * Data set change action.
   */
  private final ActionListener dataSetChangesHandler = new ActionListener() {
    Override
    public void actionPerformed(ActionEvent event) {
      AbstractButton button = (AbstractButton) event.getSource();
      String dataSet = button.getActionCommand();
      try {
        // set Gantt model
        setGanttModel(createGanttModel(dataSet, true));
      } catch (AttachModelException e) {
        GanttViewerLogger.error(e);
      }
    }
  };

  /**
   * Synchronizes visible time intervals.
   */
  private final IlvTimeScrollController timeScrollController = new IlvTimeScrollController();

  /**
   * Action Description Suffix.
   */
  public static final String DESCRIPTION_SUFFIX = ".Description";

  /**
   * Action Tooltip Suffix.
   */
  public static final String TOOLTIP_SUFFIX = ".Tooltip";

  /**
   * Action Icon Suffix.
   */
  public static final String ICON_SUFFIX = ".Icon";

  /**
   * Builds a <code>GanttViewer</code>.
   */
  public GanttViewer() {
    createHelpPanel();
  }

  /**
   * Creates the help panel.
   */
  protected void createHelpPanel() {
    helpPanel = new HelpPanel(GanttViewerProperties.getString("Help.Title"),
        GanttViewer.class.getResource(GanttViewerProperties
            .getString("Help.HtmlFileName")), GanttViewerProperties
            .getString("Help.BackToHomePage"), ImageUtilities.getIcon(
            GanttViewer.class, "help/images/home.gif"), GanttViewerProperties
            .getString("Help.BackToHomePage.Description"));
    helpPanel.addWindowListener(new WindowAdapter() {

      Override
      public void windowClosing(WindowEvent e) {
        helpButton.setSelected(false);
      }

      Override
      public void windowIconified(WindowEvent e) {
        helpButton.setSelected(false);
      }

      Override
      public void windowDeiconified(WindowEvent e) {
        helpButton.setSelected(true);
      }
    });
    helpPanel.setIconImage(ImageUtilities.getImage(
        shared.swing.ExampleFrame.class, GanttViewer.ICON_IMAGE));
  }

  /**
   * Returns the help panel.
   * 
   * @return The help panel.
   */
  Override
  public HelpPanel getHelpPanel() {
    return helpPanel;
  }

  /**
   * Initializes the user interface of the sample in the specified container.
   * This will be the <code>JFrame</code> of the application.
   */
  Override
  public void init(Container container) {
    // This sample uses JViews Gantt features. When deploying an
    // application that includes this code, you need to be in possession
    // of a Perforce JViews Gantt Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(
        IlvProductUtil.JViews_Gantt_Deployment);

    // This sample uses JViews Diagrammer features, for the PERT diagram.
    // When deploying an application that includes this code, you need to
    // be in possession of a Perforce JViews Diagrammer Deployment license.
    IlvProductUtil.DeploymentLicenseRequired(
        IlvProductUtil.JViews_Diagrammer_Deployment);

    super.init(container);
    // creates a lag time manager.
    lagTimeHandler = new LagTimeHandler();
    // create the viewer.
    viewer = this.createViewer();
    viewerCreated();
    // set Gantt model
    IlvGanttModel ganttModel = null;
    try {
      ganttModel = createGanttModel(GanttViewerProperties
          .getString("DataSets.Default"), true);
      setGanttModel(ganttModel);
    } catch (AttachModelException e) {
      GanttViewerLogger.error(e);
    }
    // initialize the GUI.
    initializeGUI(container);
    // sets the current view.
    getViewer().setCurrentView(getViewer().getView(GANTT_CHART_VIEW));
    // set preferred size
    container.setPreferredSize(WindowSizeUtils.getMainWindowSize());
    // styling
    try {
      this.styleManager.applyStyle(getViewer());
    } catch (ApplyStyleException e) {
      GanttViewerLogger.error(e);
    }
    // display the help panel
    if (showHelp) {
      SwingUtilities.invokeLater(new Runnable() {
        Override
        public void run() {
          helpButton.getAction().actionPerformed(
              new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
        }
      });
    }
  }

  /**
   * Sets the Gantt data model to the viewer.
   * 
   * @param ganttModel
   *                The Gantt model.
   */
  private void setGanttModel(IlvGanttModel ganttModel)
      throws AttachModelException {
    // computation on styles before
    // count the number of activities and resources
    int count = 0;
    Iterator<?> it = ganttModel.activityPreorderIterator(ganttModel
        .getRootActivity());
    for (; it.hasNext(); it.next()) {
      count++;
    }
    it = ganttModel.resourcePreorderIterator(ganttModel.getRootResource());
    for (; it.hasNext(); it.next()) {
      count++;
    }
    if (count >= 1000) {
      // if there are between 1000 and 5000 elements, prefer simple and medium complexity styles
      // if there are more than 5000 elements, prefer simple styles
      Style aStyle = null;
      for (SetStyleAction action : setStyleActions) {
        if ((count >= 5000 && action.getStyle().getComplexity() == StyleManager.COMPLEXITY_SIMPLE)
            || (count < 5000 && action.getStyle().getComplexity() != StyleManager.COMPLEXITY_HIGH)) {
          if (aStyle == null) {
            aStyle = action.getStyle();
          }
        }
      }
      if (aStyle != null
          && (count >= 5000 && styleManager.getCurrentStyle().getComplexity() != StyleManager.COMPLEXITY_SIMPLE)
          || (count < 5000 && styleManager.getCurrentStyle().getComplexity() == StyleManager.COMPLEXITY_HIGH)) {
        try {
          this.styleManager.applyStyle(getViewer(), aStyle);
        } catch (ApplyStyleException e) {
          GanttViewerLogger.error(e);
        }
      }
    } else {
      for (SetStyleAction action : setStyleActions) {
        action.setEnabled(true);
      }
    }
    viewer.setModel(ganttModel);
    // handles lag time management
    lagTimeHandler.setGanttModel(ganttModel);
    // initial selection.
    viewer.setSelected(ganttModel.getRootActivity(), true);
    viewer.setSelected(ganttModel.getRootResource(), true);
    // initial zoom-to-fit if there isn't default transformer associated
    // with the current view
    if (viewer.getCurrentView().getDefaultTransformer() == null) {
      SwingUtilities.invokeLater(new Runnable() {
        Override
        public void run() {
          viewer.getAction(GanttViewerZoomToFitAction.ID).actionPerformed(
              new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
        }
      });
    }
  }

  /**
   * Initializes the GUI.
   * 
   * @param container
   *                The graphical container.
   */
  protected void initializeGUI(Container container) {
    container.setLayout(new BorderLayout());
    // create and install the menu bar.
    JMenuBar menuBar = createMenuBar();
    setJMenuBar(menuBar);
    // create the status bar.
    JLabel status = createStatusBar();
    // create the toolbar.
    JToolBar toolbar = createToolBar();
    container.add(toolbar, BorderLayout.NORTH);
    container.add(viewer, BorderLayout.CENTER);
    container.add(status, BorderLayout.SOUTH);
    // size
    setSize(WindowSizeUtils.getMainWindowSize());
    // register GUI components updaters
    getViewer().addCurrentViewListener(menuUpdater);
    getViewer().addCurrentViewListener(buttonUpdater);
    // create the viewbar
    viewBar = createViewBar();
    container.add(viewBar, BorderLayout.LINE_START);
  }

  /**
   * Once the viewer is created, actions are created to change the current
   * view.
   */
  protected void viewerCreated() {
    for (View view : getViewer().views()) {
      Object[] args = { view.getName() };
      Icon icon = ImageUtilities.getIcon(GanttViewer.class,
          GanttViewerProperties.getString(view.getID().concat(
              GanttViewer.CHANGE_VIEW_ICON_SUFFIX)));
      String shortDescription = MessageFormat.format(GanttViewerProperties
          .getString(GanttViewer.CHANGE_VIEW_SHORT_DESCRIPTION), args);
      String longDescription = MessageFormat.format(GanttViewerProperties
          .getString(GanttViewer.CHANGE_VIEW_LONG_DESCRIPTION), args);
      changeViewActions.put(view.getID(), new ChangeViewAction(view, icon,
          shortDescription, longDescription));
    }
  }


  /**
   * Creates the menu bar.
   */
  public JMenuBar createMenuBar() {
    JMenuBar menubar = new JMenuBar();
    menubar.add(createFileMenu(), 0);
    menubar.add(createEditMenu(), 1);
    menubar.add(createStyleMenu(), 2);
    menubar.add(createViewMenu(), 3);
    return menubar;
  }

  /**
   * Returns the Gantt Viewer title.
   * 
   * @return The title.
   */
  Override
  public String getTitle() {
    return GanttViewerProperties.getString("Demo.Title");
  }

  /**
   * Registers the actions.
   * 
   * @param viewer
   *                The viewer.
   */
  protected void registerActions(Viewer viewer) {
    viewer.registerAction(new GanttViewerZoomInAction(viewer));
    viewer.registerAction(new GanttViewerZoomOutAction(viewer));
    viewer.registerAction(new GanttViewerZoomToFitAction(viewer));
    viewer.registerAction(new GanttViewerRowUpAction(viewer));
    viewer.registerAction(new GanttViewerRowDownAction(viewer));
    viewer.registerAction(new GanttViewerRowIndentAction(viewer));
    viewer.registerAction(new GanttViewerRowOutdentAction(viewer));
    viewer.registerAction(new GanttViewerInsertRowAction(viewer));
    viewer.registerAction(new GanttViewerInsertConstraintAction(viewer));
    viewer.registerAction(new GanttViewerEditPropertiesAction(viewer));
    viewer.registerAction(new GanttViewerDeleteRowAction(viewer));
    viewer.registerAction(new GanttViewerDeleteConstraintAction(viewer));
    viewer.registerAction(new GanttViewerRowExpandCollapseAction(viewer));
    viewer.registerAction(new GanttViewerSelectAction(viewer));
    viewer.registerAction(new GanttViewerPanAction(viewer));
    viewer.registerAction(new GanttViewerMagnifyAction(viewer));
  }

  /**
   * Registers view actions.
   * 
   * @param view
   *                The view.
   * @param actionsStr
   *                A string indicating the actions to associate with.
   */
  protected void registerActions(View view, String actionsStr) {
    // list available actions from the given string and create a controller.
    if (!"".equals(actionsStr)) {
      String[] actionsArray = actionsStr
          .split(GanttViewerProperties.PROPERTIES_SEPARATOR);
      for (String element : actionsArray) {
        view.registerAction(view.getViewer().getAction(element.trim()));
      }
    }
  }

  /**
   * Creates the viewer and the views.
   * 
   * @return The new <code>GanttViewerChart</code>.
   */
  protected Viewer createViewer() {
    String[] viewActions = new String[] {
        GanttViewer.GANTT_CHART_VIEW.concat(GanttViewer.ACTIONS_SUFFIX),
        GanttViewer.CALENDAR_VIEW.concat(GanttViewer.ACTIONS_SUFFIX),
        GanttViewer.RESOURCE_USAGE_VIEW.concat(GanttViewer.ACTIONS_SUFFIX),
        GanttViewer.TASK_SHEET_VIEW.concat(GanttViewer.ACTIONS_SUFFIX),
        GanttViewer.RESOURCE_SHEET_VIEW.concat(GanttViewer.ACTIONS_SUFFIX),
        GanttViewer.PERT_VIEW.concat(GanttViewer.ACTIONS_SUFFIX) };
    Viewer viewer = new Viewer();
    // register actions
    registerActions(viewer);
    // create views
    try {
      new GanttView(viewer, GanttViewer.GANTT_CHART_VIEW, GanttViewerProperties
          .getString(GanttViewer.GANTT_CHART_VIEW), timeScrollController,
          activityFactory, constraintFactory);
      new MonthlyCalendarView(viewer, GanttViewer.CALENDAR_VIEW,
          GanttViewerProperties.getString(GanttViewer.CALENDAR_VIEW));
      new ResourceUsageView(viewer, GanttViewer.RESOURCE_USAGE_VIEW,
          GanttViewerProperties.getString(GanttViewer.RESOURCE_USAGE_VIEW),
          timeScrollController, activityFactory, constraintFactory);
      new GanttTableView(viewer, GanttViewer.TASK_SHEET_VIEW,
          GanttViewerProperties.getString(GanttViewer.TASK_SHEET_VIEW),
          activityFactory, constraintFactory);
      new ScheduleTableView(viewer, GanttViewer.RESOURCE_SHEET_VIEW,
          GanttViewerProperties.getString(GanttViewer.RESOURCE_SHEET_VIEW),
          activityFactory, constraintFactory);
      new PERTView(viewer, GanttViewer.PERT_VIEW, GanttViewerProperties
          .getString(GanttViewer.PERT_VIEW));
      // associate actions
      int k = 0;
      for (View view : viewer.views()) {
        registerActions(view, GanttViewerProperties.getString(viewActions[k++]));
      }
    } catch (ViewException e) {
      GanttViewerLogger.error(e);
    }
    return viewer;
  }

  /**
   * Returns the viewer.
   * 
   * @return The viewer.
   */
  public Viewer getViewer() {
    return viewer;
  }

  /**
   * Creates the Gantt data model.
   * 
   * @param dataSet
   *                The dataSet to load.
   * @param resolve
   *                Resolves the data set location.
   * @return The Gantt data model.
   */
  protected IlvGanttModel createGanttModel(String dataSet, boolean resolve) {
    // model creation
    InputSource source;
    IlvGanttDocumentReader docReader;
    try {
      String path = dataSet;
      if (resolve) {
        path = getClass().getResource(dataSet).toString();
      }
      // create an InputSource
      source = new InputSource(path);
      docReader = new IlvGanttDocumentReader();
      docReader.setActivityReader(new IlvGeneralActivityReader() {
        Override
        protected IlvActivity createActivity(String id, String name,
            Date start, Date end) {
          IlvActivity activity = activityFactory
              .createActivity(new IlvTimeInterval(start, end));
          activity.setID(id);
          activity.setName(name);
          return activity;
        }
      });
      docReader.setConstraintReader(new IlvGeneralConstraintReader() {
        Override
        protected IlvConstraint createConstraint(IlvActivity from,
            IlvActivity to, IlvConstraintType type) {
          return constraintFactory.createConstraint(from, to, type);
        }
      });
    } catch (Exception e) {
      IlvSwingUtil.showErrorDialog(this, e);
      return null;
    }
    // create the document
    Document document;
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      document = builder.parse(source);
    } catch (Exception ex) {
      IlvSwingUtil.showErrorDialog(this, ex);
      return null;
    }
    // create a default Gantt model
    IlvGanttModel model = new IlvDefaultGanttModel();
    // read the document into the Gantt model.
    try {
      docReader.readGanttModel(document, model);
    } catch (Exception ex) {
      IlvSwingUtil.showErrorDialog(this, ex);
      return null;
    }
    return model;
  }

  /**
   * Creates the File menu.
   * 
   * @return The File menu.
   */
  public JMenu createFileMenu() {
    JMenu menu = new JMenu(GanttViewerProperties.getString("Menu.File"));
    menu.setMnemonic(KeyEvent.VK_F);
    setStatusText(menu, GanttViewerProperties
        .getString("Menu.File.Description"));
    // classic open file dialog in standalone mode
    addAction(menu, new OpenFileAction());
    if (isExitAllowed()) {
      menu.addSeparator();
      addAction(menu, new GanttCommand.ExitAction());
    }
    return menu;
  }

  /**
   * Creates the menu for selecting the data set.
   * 
   * @param dataSetMenu
   *                The parent menu item.
   */
  protected void createDataSetSelectorMenu(JMenu dataSetMenu) {
    String dataSetsProperty = GanttViewerProperties.getString("DataSets")
        .trim();
    String defaultDataSet = GanttViewerProperties.getString("DataSets.Default")
        .trim();
    ButtonGroup buttonGroup = new ButtonGroup();
    if (!"".equals(dataSetsProperty)) {
      String[] dataSetsArray = dataSetsProperty
          .split(GanttViewerProperties.PROPERTIES_SEPARATOR);
      for (String element : dataSetsArray) {
        String dataSet = element.trim();
        JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(dataSet,
            dataSet.equals(defaultDataSet));
        menuItem.setActionCommand(dataSet);
        menuItem.addActionListener(this.dataSetChangesHandler);
        dataSetMenu.add(menuItem);
        buttonGroup.add(menuItem);
      }
    }
  }

  /**
   * Adds the specified action to a toolbar as a button and returns the
   * button. <code>AbstractButton</code>.
   * 
   * @param toolbar
   *                The toolbar where the button is to be added.
   * @param action
   *                The action to launch.
   * @param toggle
   *                Add a toggle button?
   */
  protected AbstractButton addAction(JToolBar toolbar,
      final ViewerAbstractAction action, boolean toggle) {
    final AbstractButton button;
    if (!toggle) {
      button = new JButton(action);
    } else {
      button = new JToggleButton(action);
      // synchronize selection state
      action.addPropertyChangeListener(new PropertyChangeListener() {
        Override
        public void propertyChange(PropertyChangeEvent evt) {
          if (evt.getPropertyName().equals(ViewerAbstractAction.SELECTED_KEY)) {
            button.setSelected((Boolean) evt.getNewValue());
          }
        }
      });
      button.addItemListener(new ItemListener() {
        Override
        public void itemStateChanged(ItemEvent e) {
          boolean value = e.getStateChange() == ItemEvent.SELECTED;
          action.putValue(ViewerAbstractAction.SELECTED_KEY, value);
        }
      });
    }
    if (button.getIcon() != null) {
      button.setText(null);
    }
    // populate the action map
    ActionMap actionMap = button.getActionMap();
    if (actionMap != null) {
      actionMap.put(action, action);
    }
    // register accelerator if there is one
    KeyStroke accelerator = action.getAccelerator();
    if (accelerator != null) {
      InputMap inputMap = button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
      inputMap.put(accelerator, action);
    }
    String shortDescription = action.getShortDescription();
    if (shortDescription != null) {
      if (accelerator != null) {
        shortDescription += ToolTipUI.TIP_DELIMITER
            + action.getAcceleratorText();
      }
      button.setToolTipText(shortDescription);
    }
    toolbar.add(button);
    buttonUpdater.registerButton(button);
    return button;
  }

  /**
   * Overrides <code>addAction()</code> to listen for current view changes.
   */
  protected JMenuItem addAction(JMenu menu, ViewerAbstractAction action) {
    JMenuItem menuItem = super.addAction(menu, action);
    menuUpdater.registerMenuItem(menuItem);
    return menuItem;
  }

  /**
   * Creates the Edit menu.
   * 
   * @return The edit menu.
   */
  public JMenu createEditMenu() {
    JMenu menu = new JMenu(GanttViewerProperties.getString("Menu.Edit"));
    menu.setMnemonic(KeyEvent.VK_E);
    setStatusText(menu, GanttViewerProperties
        .getString("Menu.Edit.Description"));
    Viewer viewer = getViewer();
    addAction(menu, viewer.getAction(GanttViewerInsertRowAction.ID));
    addAction(menu, viewer.getAction(GanttViewerInsertConstraintAction.ID));
    menu.addSeparator();
    addAction(menu, viewer.getAction(GanttViewerDeleteRowAction.ID));
    addAction(menu, viewer.getAction(GanttViewerDeleteConstraintAction.ID));
    menu.addSeparator();
    addAction(menu, viewer.getAction(GanttViewerEditPropertiesAction.ID));
    return menu;
  }

  /**
   * Creates the Style menu.
   * 
   * @return The Style menu.
   */
  public JMenu createStyleMenu() {
    JMenu menu = new JMenu(GanttViewerProperties.getString("Menu.Style"));
    menu.setMnemonic(KeyEvent.VK_S);
    setStatusText(menu, GanttViewerProperties
        .getString("Menu.Style.Description"));
    final Map<Style, JRadioButtonMenuItem> index = new HashMap<Style, JRadioButtonMenuItem>();
    ButtonGroup styleGroup = new ButtonGroup();
    for (Object element : styleManager.styles()) {
      StyleManager.Style style = (StyleManager.Style) element;
      SetStyleAction action = new SetStyleAction(style);
      setStyleActions.add(action);
      JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(action);
      menu.add(menuItem);
      styleGroup.add(menuItem);
      index.put(style, menuItem);
    }
    styleManager.addCurrentStyleListener(new CurrentStyleListener() {
      Override
      public void currentStyleChanged() {
        index.get(styleManager.getCurrentStyle()).setSelected(true);
      }
    });
    return menu;
  }

  /**
   * Creates the View menu.
   * 
   * @return The View menu.
   */
  public JMenu createViewMenu() {
    JMenu menu = new JMenu(GanttViewerProperties.getString("Menu.View"));
    menu.setMnemonic(KeyEvent.VK_V);
    setStatusText(menu, GanttViewerProperties
        .getString("Menu.View.Description"));
    // group of items
    final ButtonGroup group = new ButtonGroup();
    for (View view : getViewer().views()) {
      ChangeViewAction changeAction = changeViewActions.get(view.getID());
      JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(changeAction);
      menuItem.setToolTipText(null);
      menuItem.setIcon(null);
      setStatusText(menuItem, changeAction.getLongDescription());
      group.add(menuItem);
      menu.add(menuItem);
    }
    // listen to view changes
    getViewer().addCurrentViewListener(new CurrentViewListener() {
      Override
      public void currentViewChanged(CurrentViewChangedEvent event) {
        for (Enumeration<AbstractButton> e = group.getElements(); e
            .hasMoreElements();) {
          AbstractButton button = e.nextElement();
          if (button.getAction().equals(
              changeViewActions.get(getViewer().getCurrentView().getID()))) {
            group.setSelected(button.getModel(), true);
            break;
          }
        }
      }
    });
    return menu;
  }

  /**
   * Creates the list of available views.
   * 
   * @return The toolbar.
   */
  public JComponent createViewBar() {
    JToolBar viewBar = new JToolBar(SwingConstants.VERTICAL);
    viewBar.setBackground(UIManager.getColor("controlShadow"));
    viewBar.setFloatable(false);
    // group of items
    final ButtonGroup group = new ButtonGroup();
    // create menu items
    Dimension max = new Dimension(0, 0);
    for (View view : getViewer().views()) {
      ChangeViewAction changeAction = changeViewActions.get(view.getID());
      JToggleButton button = new JToggleButton(changeAction);
      button.setHorizontalTextPosition(SwingConstants.CENTER);
      button.setVerticalTextPosition(SwingConstants.BOTTOM);
      button.setToolTipText(changeAction.getShortDescription());
      group.add(button);
      viewBar.add(button);
      if (button.getMaximumSize().getWidth() > max.getWidth()) {
        max = button.getMaximumSize();
      }
    }
    // set button sizes
    for (Enumeration<AbstractButton> e = group.getElements(); e
        .hasMoreElements();) {
      e.nextElement().setMaximumSize(max);
    }
    // listen to view changes
    getViewer().addCurrentViewListener(new CurrentViewListener() {
      Override
      public void currentViewChanged(CurrentViewChangedEvent event) {
        for (Enumeration<AbstractButton> e = group.getElements(); e
            .hasMoreElements();) {
          AbstractButton button = e.nextElement();
          if (button.getAction().equals(
              changeViewActions.get(getViewer().getCurrentView().getID()))) {
            group.setSelected(button.getModel(), true);
            break;
          }
        }
      }
    });
    viewBar.setBorder(BorderFactory.createEtchedBorder());
    return viewBar;
  }

  /**
   * Overrides <code>setLookAndFeel</code>.
   * 
   * @param laf
   *                The look-and-feel object.
   */
  Override
  protected void setLookAndFeel(LookAndFeelInfo laf) {
    super.setLookAndFeel(laf);
    if (viewBar != null) {
      viewBar.setBackground(UIManager.getColor("controlShadow"));
    }
  }

  /**
   * Populates the toolbar.
   * 
   * @param toolbar
   *                The toolbar to populate.
   */
  Override
  protected void populateToolBar(JToolBar toolbar) {
    Viewer viewer = getViewer();
    addAction(toolbar, viewer.getAction(GanttViewerInsertRowAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerInsertConstraintAction.ID),
        false);
    toolbar.addSeparator();
    addAction(toolbar, viewer.getAction(GanttViewerDeleteRowAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerDeleteConstraintAction.ID),
        false);
    toolbar.addSeparator();
    addAction(toolbar, viewer.getAction(GanttViewerEditPropertiesAction.ID),
        false);
    toolbar.addSeparator();
    addAction(toolbar, viewer.getAction(GanttViewerRowExpandCollapseAction.ID),
        false);
    addAction(toolbar, viewer.getAction(GanttViewerRowOutdentAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerRowIndentAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerRowUpAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerRowDownAction.ID), false);
    toolbar.addSeparator();
    // add interactions
    addAction(toolbar, viewer.getAction(GanttViewerSelectAction.ID), true)
        .setSelected(true);
    addAction(toolbar, viewer.getAction(GanttViewerPanAction.ID), true);
    addAction(toolbar, viewer.getAction(GanttViewerMagnifyAction.ID), true);
    toolbar.addSeparator();
    addAction(toolbar, viewer.getAction(GanttViewerZoomInAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerZoomOutAction.ID), false);
    addAction(toolbar, viewer.getAction(GanttViewerZoomToFitAction.ID), false);
    // help
    toolbar.addSeparator();
    helpButton = new JToggleButton(showHideHelpAction);
    helpButton.setText(null);
    toolbar.add(helpButton);
    toolbar.setBorder(BorderFactory.createEtchedBorder());
  }

  /**
   * View change action of the chart.
   */
  class ChangeViewAction extends IlvAction {

    /**
     * The view.
     */
    private View relatedView;

    /**
     * Builds a <code>ChangeViewAction</code>.
     */
    public ChangeViewAction(View relatedView, Icon icon,
        String shortDescription, String longDescription) {
      super(relatedView.getName(), icon, null, shortDescription,
          longDescription);
      this.relatedView = relatedView;
    }

    /**
     * Performs the action.
     * 
     * @param event
     *                The event.
     */
    Override
    public void actionPerformed(ActionEvent event) {
      relatedView.getViewer().setCurrentView(relatedView);
    }

    /**
     * Returns the related view.
     * 
     * @return The view.
     */
    public View getRelatedView() {
      return relatedView;
    }

  }

  /**
   * The show/hide help action.
   */
  class ShowHideHelpAction extends IlvAction {

    /**
     * ID of the action.
     */
    public static final String ID = "Action.ShowHideHelp";

    /**
     * Builds a <code>ShowHideHelpAction</code>.
     */
    public ShowHideHelpAction() {
      super(GanttViewerProperties.getString(ShowHideHelpAction.ID),
          ImageUtilities.getIcon(shared.AbstractExample.class,
              GanttViewerProperties.getString(ShowHideHelpAction.ID
                  .concat(".Icon"))), null, GanttViewerProperties
              .getString(ShowHideHelpAction.ID.concat(".Tooltip")),
          GanttViewerProperties.getString(ShowHideHelpAction.ID
              .concat(".Description")));
    }

    /**
     * Shows/Hides help panel.
     */
    Override
    public void actionPerformed(ActionEvent arg0) {
      helpPanel.setVisible(helpButton.isSelected());
      if (helpButton.isSelected()) {
        helpPanel.setState(Frame.NORMAL);
        helpPanel.toFront();
      }
    }

  }

  /**
   * Represents the open file action.
   * 
   * @since JViews 8.5
   */
  class OpenFileAction extends IlvAction {

    /**
     * The ID of the action.
     */
    public static final String ID = "Action.OpenFile";

    /**
     * The file chooser.
     */
    private JFileChooser fileChooser;

    /**
     * Builds an <code>OpenFileAction</code> object.
     */
    public OpenFileAction() {
      super(
          GanttViewerProperties.getString(OpenFileAction.ID),
          null,
          null,
          GanttViewerProperties.getString(OpenFileAction.ID.concat(".Tooltip")),
          GanttViewerProperties.getString(OpenFileAction.ID
              .concat(".Description")));
      fileChooser = new JFileChooser(".");
      fileChooser.setMultiSelectionEnabled(false);
      fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      fileChooser.setFileFilter(new FileFilter() {

        Override
        public boolean accept(File f) {
          return f.isDirectory() || f.getName().toLowerCase().endsWith(".sdxl");
        }

        Override
        public String getDescription() {
          return "*.sdxl";
        }

      });
    }

    /**
     * Opens a file.
     */
    Override
    public void actionPerformed(ActionEvent evt) {
      if (fileChooser.showOpenDialog(GanttViewer.this) == JFileChooser.APPROVE_OPTION) {
        File file = fileChooser.getSelectedFile();
        try {
          // set Gantt model
          setGanttModel(createGanttModel(file.toURI().toURL().toString(), false));
        } catch (AttachModelException e) {
          GanttViewerLogger.error(e);
        } catch (MalformedURLException e) {
          GanttViewerLogger.error(e);
        }
      }
    }

  }

  /**
   * Represents the change style action.
   * 
   * @since JViews 8.5
   */
  class SetStyleAction extends IlvAction {

    /**
     * The ID of the action.
     */
    public static final String ID = "Action.SetStyle";

    /**
     * The style.
     */
    private Style style;

    /**
     * Builds a new <code>SetStyleAction</code> object.
     * @param style The style.
     */
    public SetStyleAction(Style style) {
      super(style.getName(), null, null);
      this.style = style;
    }

    /**
     * Returns the style.
     * @return The current style.
     */
    public Style getStyle() {
      return style;
    }

    /**
     * Sets the style.
     * @param evt Used to set the style.
     */
    Override
    public void actionPerformed(ActionEvent evt) {
      try {
        styleManager.applyStyle(GanttViewer.this.getViewer(), style);
      } catch (ApplyStyleException e) {
        GanttViewerLogger.error(e);
      }
    }

  }

  /**
   * Custom activity factory.
   */
  protected class ActivityFactory implements IlvActivityFactory {

    /**
     * The factory method.
     * 
     * @param interval
     *                The given time interval.
     */
    Override
    public IlvActivity createActivity(IlvTimeInterval interval) {
      IlvGeneralActivity activity = new IlvGeneralActivity("New Activity "
          + (GanttViewer.ACTIVITY_ID_INCR++), "New Activity", interval);
      return activity;
    }

  }

  /**
   * Custom constraint factory.
   */
  protected class ConstraintFactory implements IlvConstraintFactory {

    /**
     * The factory method.
     * 
     * @param interval
     *                The given time interval.
     */
    Override
    public IlvConstraint createConstraint(IlvActivity from, IlvActivity to,
        IlvConstraintType type) {
      IlvGeneralConstraint constraint = new GanttViewerConstraint(from, to,
          type);
      constraint
          .setProperty(LagTimeHandler.CONSTRAINT_SATISFIED_PROPERTY, true);
      return constraint;
    }

  }

  /**
   * Entry Point of the Gantt Viewer.
   * 
   * @param args
   */
  public static void main(String[] args) {
    ExampleFrame.createAndShowGUI(GanttViewer.class,
        new ExampleFrame.FrameInitialLocationPolicy() {
          Override
          public void initPosition(JFrame frame, AbstractExample example) {
            frame.setLocation(WindowSizeUtils.getMainWindowLocation());
          }
        });
  }

}