//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// © Copyright IBM Corporation 2007, 2008
// All Rights Reserved.
//
// DESCRIPTION:
// KeyReferenceAction - sample user-defined action (ref)
//----------------------------------------------------------------------------

package com.ibm.lpex.samples;

import com.ibm.lpex.core.LpexAction;
import com.ibm.lpex.core.LpexActionConstants;
import com.ibm.lpex.core.LpexBaseAction;
import com.ibm.lpex.core.LpexResources;
import com.ibm.lpex.core.LpexStringTokenizer;
import com.ibm.lpex.core.LpexView;

import java.util.Iterator;
import java.util.TreeSet;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

import org.eclipse.jface.dialogs.PopupDialog;

import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.help.IWorkbenchHelpSystem;
import org.eclipse.ui.keys.IBindingService;

/**
 * Sample action <b>ref</b> - display a key reference for the current view.
 * Use this action to display a dialog with the editor actions available in
 * the current document view, and their associated keys.
 *
 * <p>Here is the KeyReferenceAction
 * <a href="doc-files/KeyReferenceAction.java.html">source code</a>.</p>
 *
 * <p>To run this sample:
 * <ul>
 *  <li>Define this user action via an editor preference page, where available,
 *   or from the editor command line:
 *   <pre>set actionClass.ref com.ibm.lpex.samples.KeyReferenceAction</pre></li>
 *  <li>Run it from the editor command line:
 *   <pre>action ref</pre>
 *   or associate it with a key (here, <b>Ctrl+Shift+L</b> in all contexts):
 *   <pre>set keyAction.c-s-l.t.c.p ref</pre></li>
 * </ul></p>
 *
 * @see com.ibm.lpex.samples All the samples
 */
public class KeyReferenceAction implements LpexAction
{
 /**
  * Runs the action.
  * Displays the key reference dialog.
  */
 public void doAction(LpexView lpexView)
 {
  KeyReferenceDialog keyReferenceDialog = new KeyReferenceDialog(lpexView);
  // without a parent shell, keyReferenceDialog's shell won't close on Esc...
  if (keyReferenceDialog.getShell() == null)
   {
    keyReferenceDialog.setParentShell(lpexView.window().getShell());
   }
  keyReferenceDialog.open();
 }
 /**
  * Returns the availability of this action.
  * This action can run in any view that has an associated window.
  */
 public boolean available(LpexView lpexView)
 {
  return lpexView.window() != null;
 }
}


/**
 * Dialog that displays the LPEX actions and their associated keys
 * available in a view.  Transient dialog, based on PopupDialog.
 */
class KeyReferenceDialog extends PopupDialog
{
 // associated LPEX document view
 LpexView _lpexView;

 // are we running inside the Eclipse workbench?
 IWorkbench _workbench;

 // table with the actions and keys of the current view
 Table _table;

 // list of actions that we don't need to show in this dialog
 static final String[] hiddenActions =
  { "appendToActionArgument", "eclipseCopy", "eclipseCut", "eclipseDelete", "eclipsePaste",
    "eclipseRedo", "eclipseUndo", "nullAction", "saveToWriter" };


 /**
  * Creates an instance of KeyReferenceDialog.
  *
  * @param lpexView the associated LPEX document view
  */
 KeyReferenceDialog(LpexView lpexView)
 {
  super((Shell)null, PopupDialog.INFOPOPUP_SHELLSTYLE /*can get focus*/,
        true, false, false, false, null, null);
  _lpexView = lpexView;
  try
   {
    _workbench = PlatformUI.getWorkbench();
   }
  catch(Exception x) {}

  // indicate the current editor profile and document parser
  String parser = _lpexView.query("parser");
  if (parser != null)
   { // LPEX Key Reference - profile: "{0}", parser: "{1}"
    setInfoText(LpexResources.message("keyReferencePP", _lpexView.query("baseProfile"), parser));
   }
  else
   {
    setInfoText(LpexResources.message("keyReferenceP", _lpexView.query("baseProfile")));
   }
 }

 /**
  * Opens the KeyReferenceDialog.
  */
 public int open()
 {
  Shell shell = getShell();
  if (shell != null)
   {
    close(); // close any previously-opened dialog
   }

  // create dialog, set its bounds, open it
  create();
  setLocationAndSize();
  return super.open();
 }

 // public boolean close() {
 // // TEST: click other window > minimize it > focus not quite there in orig LPEX window
 // // _lpexView.window().setFocus(); BUT leaves cheese when click on orig LPEX window...
 // return super.close();
 // }

 /**
  * Allows the user of this dialog to set a parent shell.
  */
 protected void setParentShell(Shell newParentShell)
 {
  super.setParentShell(newParentShell);
 }

 /**
  * Creates the fills a table with the actions and keys.
  */
 protected Control createDialogArea(Composite parent)
 {
  // do we need a hosting Composite?!
  _table = new Table(parent, SWT.FULL_SELECTION | SWT.SINGLE);
  // _table.setLayoutData(new GridData(GridData.FILL_BOTH));
  _table.setBackground(parent.getBackground());
  _table.setLinesVisible(true);

  TableColumn actionNameColumn = new TableColumn(_table, SWT.LEFT, 0);
  TableColumn actionKeyColumn = new TableColumn(_table, SWT.LEFT, 1);

  // fill table with the editor actions and their keys
  populateTableWithActionKeys();

  // pack the columns for their contents
  actionNameColumn.pack();
  actionKeyColumn.pack();

  // listen to user selecting a table item
  _table.addListener(SWT.DefaultSelection, new Listener() {
   public void handleEvent(Event event)
   { selectionEvent(); }
  });

  // listen to mouse hovering over a table item
  _table.addListener(SWT.MouseHover, new Listener() {
   public void handleEvent(Event event)
   { hoverEvent(event); }
  });

  // listen to F1 (Help) key
  if (_workbench != null)
   {
    _table.addListener(SWT.Help, new Listener() {
     public void handleEvent(Event event)
     { helpEvent(); }
    });
   }

  return _table;
 }

 /**
  * Positions the dialog in the bottom right corner of the workbench or screen.
  */
 private void setLocationAndSize()
 {
  int x, y;
  Shell shell = getShell();
  int width  = shell.getSize().x;
  int height = shell.getSize().y;

  if (_workbench != null)
   {
    Rectangle workbenchBounds = _workbench.getWorkbenchWindows()[0].getShell().getBounds();
    int maxHeight = workbenchBounds.height / 2;
    if (height > maxHeight)
     {
      height = maxHeight;
     }
    x = workbenchBounds.x + workbenchBounds.width - width - 10;
    y = workbenchBounds.y + workbenchBounds.height - height - 10;
   }
  else
   {
    Rectangle displayBounds = shell.getDisplay().getClientArea();
    int maxHeight = displayBounds.height / 2;
    if (height > maxHeight)
     {
      height = maxHeight;
     }
    x = displayBounds.width - width - 10;
    y = displayBounds.height - height - 10;
   }

  shell.setBounds(x, y, width, height);
 }

 /**
  * Fills table with all LPEX actions available in the associated view.
  */
 private void populateTableWithActionKeys()
 {
  // use a sorted set (action) for the contents of the table
  TreeSet<String> treeSet = new TreeSet<String>();

  // put the default and user-defined editor actions in the set
  LpexStringTokenizer st = new LpexStringTokenizer(_lpexView.query("defaultActions")+" "+
                                                   _lpexView.query("actions"));
  while (st.hasMoreTokens())
   {
    String actionName = st.nextToken();
    if (showAction(actionName))
     {
      // SPECIAL CASE: when running LpexAbstractTextEditor (inside Eclipse's
      // IDE), bring up Eclipse's own Key Assist dialog on 2nd Ctrl+Shift+L
      if (_workbench != null && actionName.equals("cslAction"))
       {
        actionName = "Show Eclipse key assist";
       }

      treeSet.add(actionName);
     }
   }

  // populate table from the set
  Color unavailableColor = _table.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
  Iterator<String> iterator = treeSet.iterator();
  while (iterator.hasNext())
   {
    String actionName = iterator.next();
    int actionId = _lpexView.actionId(actionName);
    String keyText = _lpexView.actionKeyText(actionId);

    // create a new table item for the action
    TableItem item = new TableItem(_table, SWT.NULL);
    item.setText(0, actionName);                      // 1st column - action
    item.setText(1, (keyText != null)? keyText : ""); // 2nd column - key
    item.setData(actionName);

    // gray out item if it's an editor action not available in the current context
    // (hmm, this may cause a gray left margin for the entire table!?)
    if (actionId != LpexActionConstants.ACTION_INVALID && !_lpexView.actionAvailable(actionId))
     {
      item.setForeground(unavailableColor);
     }
   }

  // TODO add key-bindings to dialog, so can run action by pressing its key!?
  // TODO add available editor commands!?
 }

 /**
  * Returns whether we should show the given action in the dialog.
  */
 private boolean showAction(String actionName)
 {
  for (int i = 0; i < hiddenActions.length; i++)
   {
    if (hiddenActions[i].equals(actionName))
     {
      return false;
     }
   }
  return true;
 }

 /**
  * Returns the table as the control to get focus when dialog displayed.
  */
 protected Control getFocusControl()
 {
  return _table;
 }

 /**
  * Runs the action specified in the selected table item.
  */
 private void selectionEvent()
 {
  // get the action of the item selected in the table
  int selectedIndex = _table.getSelectionIndex();
  if (selectedIndex >= 0)
   {
    String actionName = (String)_table.getItem(selectedIndex).getData();

    // SPECIAL CASE: when running LpexAbstractTextEditor (inside Eclipse's
    // IDE), bring up Eclipse's own Key Assist dialog on 2nd Ctrl+Shift+L
    if (_workbench != null && actionName.equals("Show Eclipse key assist"))
     {
      super.close();
      ((IBindingService)_workbench.getService(IBindingService.class))
       .openKeyAssistDialog();
      return;
     }

    int actionId = _lpexView.actionId(actionName);
    // if the action is available, run it
    if (_lpexView.actionAvailable(actionId))
     {
      // close dialog before running action, so LPEX window is restored focus
      super.close();
      _lpexView.triggerAction(actionId);
     }
   }
 }

 /**
  * Displays tooltip information, if available, for the hovered table item.
  * @param event the event that triggered this table-listener notification
  */
 private void hoverEvent(Event event)
 {
  String tooltip = null;
  TableItem item = _table.getItem(new Point(event.x, event.y));
  if (item != null)
   {
    String actionName = (String)item.getData();
    LpexAction action = _lpexView.action(actionName);

    // try to get an acceptable tooltip
    if (action != null && action instanceof LpexBaseAction)
     {
      tooltip = ((LpexBaseAction)action).getToolTipText(_lpexView);
     }
    if (tooltip == null)
     {
      tooltip = LpexResources.message("popup." + actionName + ".description");
     }
   }

  _table.setToolTipText(tooltip);
 }

 /**
  * Displays help, if available, for the selected table item.
  */
 private void helpEvent()
 {
  // get the action of the item selected in the table
  int selectedIndex = _table.getSelectionIndex();
  if (selectedIndex >= 0)
   {
    String actionName = (String)_table.getItem(selectedIndex).getData();
    LpexAction action = _lpexView.action(actionName);

    // try to get a context help id
    String contextHelpId = null;
    if (action != null && action instanceof LpexBaseAction)
     {
      contextHelpId = ((LpexBaseAction)action).getHelpId(_lpexView);
     }
    if (contextHelpId == null)
     {
      contextHelpId = "com.ibm.lpex.popup_" + actionName + "_context";
     }

    super.close();
    // no help will display if contextHelpId not defined in LPEX Editor plug-in
    _workbench.getHelpSystem().displayHelp(contextHelpId);
   }
 }
}