//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// © Copyright IBM Corporation 1998, 2008
// All Rights Reserved.
//
// DESCRIPTION:
// TestUserProfile - sample user profile
//----------------------------------------------------------------------------

package com.ibm.lpex.samples;

import com.ibm.lpex.core.LpexAction;
import com.ibm.lpex.core.LpexCommand;
import com.ibm.lpex.core.LpexDocumentLocation;
import com.ibm.lpex.core.LpexView;

/**
 * Sample user profile - customize keys, commands, actions.
 * It customizes the base editor profile (if not "vi") by redefining several
 * settings and keys:
 * <ul>
 *  <li>restricts the action of the Backspace and Delete keys to the current line</li>
 *  <li>sets the Enter keys to go to the next line rather than split</li>
 *  <li>sets the Home and End keys to actions <b>contextHome</b> and <b>contextEnd</b></li>
 *  <li>sets Alt+. to action <b>zoom</b> (defined in
 *      {@link ZoomAction}) to zoom in / out a document segment</li>
 *  <li>sets Alt+F1 to action <b>compose</b> (defined in
 *      {@link ComposeAction}) to enter special characters</li>
 *  <li>redefines Alt+J (join) (see {@link CloseJoin CloseJoin})
 *      to remove extra spaces between the joined texts</li>
 *  <li>sets Alt+T to action <b>blockTransfer</b> (defined in
 *      {@link BlockTransferAction}) to transfer selection, adds it to the popup</li>
 *  <li>redefines Ctrl+Backspace (delete line) to preserve the cursor column position</li>
 *  <li>redefines Ctrl+N (find next) (see {@link FindNextSelection FindNextSelection})
 *      to search for the selected text, if any</li>
 *  <li>sets Ctrl+Shift+V to do a paste replace</li>
 *  <li>redefines mouse button 1 double-click to select the current line</li>
 *  <li>sets the default selection type to character</li>
 *  <li>redefines the <b>findText</b> command (using {@link FindTextContextCommand})
 *      to show the context for the result of find-text command and actions</li>
 *  <li>defines commands <b>detab, entab</b> (using {@link DetabCommand},
 *      {@link EntabCommand})</li>
 *  <li>defines command synonyms <b>diff</b> for <b>compare</b>, <b>k</b> for <b>calc</b>,
 *      <b>reload</b> for <b>load</b>, and a <b>pre</b> command as 'synonym' for the
 *      command-line implicit set/query of the <b>prefixArea</b> parameter</li>
 *  <li>sets new mouse button 1 drag actions to always start new selections (using
 *      {@link MouseReselect})</li>
 *  <li>adds an action to the pop-up menu of Java properties document views to
 *      compare ignoring the mnemonics ('&amp;'s).</li>
 * </ul>
 *
 * <p>Here is the TestUserProfile <a href="doc-files/TestUserProfile.java.html">source
 * code</a>.</p>
 *
 * <p>To run this sample:
 * <ul>
 *  <li>Register this user profile from the "User Profile" preference page, where
 *   available, or from the editor command line:
 *   <pre>set [default.]updateProfile.userProfile com.ibm.lpex.samples.TestUserProfile
 *updateProfile</pre></li>
 * </ul></p>
 *
 * <p>The user profile is run during the processing of the <b>updateProfile</b>
 * command.  The <b>updateProfile</b> command is normally run when a document
 * view is created, and it may be issued at any time to allow the document view
 * to reflect changes to the editor profile (for example, a change to default
 * settings done from the preference pages).</p>
 *
 * <p>A user profile is a public Java class that has a method of the form:
 * <pre>
 *   public static void userProfile(LpexView lpexView) </pre>
 * See the <b>updateProfile.userProfile</b> parameter.</p>
 *
 * @see com.ibm.lpex.samples All the samples
 */
public class TestUserProfile
{
 /**
  * Sample action <b>closeJoin</b> - join with just one space between the texts.
  * This action is similar to the <b>join</b> default editor action, but it will
  * keep one and only one space between the joined lines.
  *
  * <p>Defined as a separate, named class inside {@link TestUserProfile} (where
  * you can also see its <a href="doc-files/TestUserProfile.java.html#line79">source
  * code</a>) so that it can be easily registered by itself, when TestUserProfile
  * is not used as the user profile.  For example:
  * <pre>   set actionClass.closeJoin com.ibm.lpex.samples.TestUserProfile$CloseJoin
  *   set keyAction.a-j closeJoin</pre></p>
  */
 public static class CloseJoin implements LpexAction
 {
  /**
   * Runs the action.
   * Joins the current and next text lines, keeps one space (no more, no less)
   * between the concatenated texts.
   */
  public void doAction(LpexView lpexView)
  {
   LpexDocumentLocation joinLocation = lpexView.documentLocation();
   if (joinLocation.element > 0)
    {
     // save cursor position, may be affected by deleteWhiteSpace / insertText
     int displayPosition = lpexView.queryInt("displayPosition");
     joinLocation.position = lpexView.queryInt("length") + 1;
     lpexView.doAction(lpexView.actionId("join"));
     lpexView.doCommand(joinLocation, "action oneSpace");
     // restore original cursor position
     lpexView.doCommand("set displayPosition " + displayPosition);
    }
  }

  /**
   * Returns the availability of this action.
   * This action can be run in any visible, writable document view.
   */
  public boolean available(LpexView lpexView)
  {
   return lpexView.currentElement() != 0 && !lpexView.queryOn("readonly");
  }
 }


 /**
  * Sample action <b>findNextSelection</b> - first search for the selected text, if any.
  * This action is similar to the <b>findNext</b> built-in editor action, but will find
  * the next occurrence of the selected text if there is a selection in the current view
  * suitable as find text.  This selection is <b>not</b> recorded in the findText
  * parameters.  The found text is selected.
  *
  * <p>Defined as a separate, named class inside {@link TestUserProfile} (where
  * you can also see its <a href="doc-files/TestUserProfile.java.html#line124">source
  * code</a>) so that it can be easily registered by itself, when TestUserProfile
  * is not used as the user profile.  For example:
  * <pre>   set actionClass.findNextSelection com.ibm.lpex.samples.TestUserProfile$FindNextSelection
  *   set keyAction.c-n.t.p.c findNextSelection</pre></p>
  */
 public static class FindNextSelection implements LpexAction
 {
  /**
   * Runs the action.
   * Searches for the selected text or the regular find text.
   */
  public void doAction(LpexView lpexView)
  {
   if (lpexView.actionAvailable(lpexView.actionId("findSelection")))
    {
     // preserve current & default findText parameters (by default they are
     // changed to the selection text, no regex), select the found text
     String findText = lpexView.query("findText.findText");
     String regex = lpexView.query("findText.regularExpression");
     String defaultFindText = lpexView.query("default.findText.findText");
     String defaultRegex = lpexView.query("default.findText.regularExpression");
     String mark = lpexView.query("findText.mark");

     lpexView.doCommand("set findText.mark on");
     lpexView.doAction(lpexView.actionId("findSelection"));

     lpexView.doCommand("set findText.findText " + findText);
     lpexView.doCommand("set findText.regularExpression " + regex);
     lpexView.doCommand("set default.findText.findText " + defaultFindText);
     lpexView.doCommand("set default.findText.regularExpression " + defaultRegex);
     lpexView.doCommand("set findText.mark " + mark);
    }
   else
    {
     lpexView.doAction(lpexView.actionId("findNext"));
    }
  }

  /**
   * Returns the availability of this action.
   * This action can be run whenever either <b>findSelection</b> or <b>findNext</b> can.
   */
  public boolean available(LpexView lpexView)
  {
   return lpexView.actionAvailable(lpexView.actionId("findSelection")) ||
          lpexView.actionAvailable(lpexView.actionId("findNext"));
  }
 }


 // Private constructor, not called explicitly.
 private TestUserProfile() {}

 /**
  * This is the method in a user profile that is called by the
  * <b>updateProfile</b> editor command.
  *
  * @param lpexView the document view for which this profile is being run
  */
 public static void userProfile(LpexView lpexView)
 {
  // if the current editor profile is "vi", don't touch anything
  String baseProfile = lpexView.query("baseProfile");
  if ("vi".equals(baseProfile))
   {
    lpexView.doDefaultCommand("set messageText Base profile NOT modified by TestUserProfile.");
    return;
   }

  /*------------------------------*/
  /* define new actions, commands */
  /*------------------------------*/
  // define an action to select the current line
  lpexView.defineAction("myBlockMarkElement", new LpexAction() {
   public void doAction(LpexView view)
   {
    view.doCommand("block clear");
    view.doCommand("block set element");
   }
   public boolean available(LpexView view) { return true; }
  });

  // define a delete line action that preserves the cursor column position
  lpexView.defineAction("myDeleteLine", new LpexAction() {
   public void doAction(LpexView view)
   {
    int displayPosition = view.queryInt("displayPosition");
    if (displayPosition > 0)
     {
      view.doAction(view.actionId("deleteLine"));
      view.doCommand("set displayPosition " + displayPosition);
     }
   }
   public boolean available(LpexView view) { return true; }
  });

  // define the close join action in this view
  lpexView.defineAction("closeJoin", new CloseJoin());

  // define the find next selection action in this view
  lpexView.defineAction("findNextSelection", new FindNextSelection());

  // define a compare ignoring mnemonics action for the Java properties document parser
  lpexView.defineAction("myPropCompare", new LpexAction() {
   public void doAction(LpexView view)
   { view.doCommand("compare ignoredStyles \"a\" prompt"); }
   public boolean available(LpexView view) { return true; }
  });

  // redefine the "findText" command to show context for the found text
  lpexView.defineCommand("findText", new FindTextContextCommand());

  // define a synonym "diff" for the "compare" command
  lpexView.defineCommand("diff", new LpexCommand() {
   public boolean doCommand(LpexView view, String parameters)
   { return view.doCommand("compare " + parameters); }
  });

  // define a synonym "k" for the "calc" command
  lpexView.defineCommand("k", new LpexCommand() {
   public boolean doCommand(LpexView view, String parameters)
   { return view.doCommand("calc " + parameters); }
  });

  // define a synonym "reload" for the "load" command
  lpexView.defineCommand("reload", new LpexCommand() {
   public boolean doCommand(LpexView view, String parameters)
   { return view.doCommand("load " + parameters); }
  });

  // define "pre" as shortcut for command-line implicit access of the "prefixArea" parameter
  lpexView.defineCommand("pre", new LpexCommand() {
   public boolean doCommand(LpexView view, String parameters)
   { return (parameters.trim().length() == 0)?
      view.doCommand("set messageText prefixArea " + view.query("current.prefixArea")) :
      view.doCommand("set prefixArea " + parameters); }
  });

  /*---------------------------------------------------*/
  /* (re)register keys, mouse events, action, commands */
  /*---------------------------------------------------*/
  boolean isEmacs = "emacs".equals(baseProfile);

  // set "Delete" and "Backspace" keys to keep it inside the current line
  lpexView.doDefaultCommand("set keyAction.backSpace backSpaceInLine");
  lpexView.doDefaultCommand("set keyAction.delete deleteInLine");

  // set "Enter" keys to go to the next line (not split)
  lpexView.doDefaultCommand("set keyAction.enter newLine");
  lpexView.doDefaultCommand("set keyAction.numpadEnter newLine");

  // set smarter "Home" and "End"
  lpexView.doDefaultCommand("set keyAction.home contextHome");
  lpexView.doDefaultCommand("set keyAction.end contextEnd");

  // set "Alt+." key to zoom in/out a document segment (com.ibm.lpex.samples.ZoomAction)
  lpexView.defineAction("zoom", new ZoomAction());
  lpexView.doDefaultCommand("set keyAction.a-period zoom");

  // set "Alt+F1" key to compose special characters (com.ibm.lpex.samples.ComposeAction)
  lpexView.defineCommand("compose", ComposeAction.composeCommand);
  lpexView.defineAction("compose", ComposeAction.composeAction);
  lpexView.doDefaultCommand("set keyAction.a-f1 compose");

  // set "Alt+J" to join next line's text with just one space in-between
  lpexView.doDefaultCommand("set keyAction.a-j closeJoin");

  // set "Alt+T" to do a block transfer (com.ibm.lpex.samples.BlockTransferAction)
  lpexView.doDefaultCommand("set actionClass.blockTransfer " +
                            "com.ibm.lpex.samples.BlockTransferAction");
  if (!isEmacs)
     lpexView.doDefaultCommand("set keyAction.a-t blockTransfer");

  // set "Ctrl+Backspace" to delete and preserve the cursor column position
  lpexView.doDefaultCommand("set keyAction.c-backSpace.t.p.c myDeleteLine");

  // set "Ctrl+N" to first search for the selected text, if any
  if (!isEmacs)
     lpexView.doDefaultCommand("set keyAction.c-n.t.p.c findNextSelection");

  // set "Ctrl+Shift+V" to do a paste replace, like "Alt+Z" does a block replace
  lpexView.doDefaultCommand("set keyAction.c-s-v.t pasteOverlay");

  // set mouse button1 double-click to select the line (not word)
  lpexView.doDefaultCommand("set mouseAction.1-pressed.2 myBlockMarkElement");

  // set default selection type to character (rather than the default stream)
  lpexView.doDefaultCommand("set block.defaultType character");

  // register commands "detab", "entab" (com.ibm.lpex.samples.DetabCommand, .EntabCommand)
  lpexView.doDefaultCommand("set commandClass.detab com.ibm.lpex.samples.DetabCommand");
  lpexView.doDefaultCommand("set commandClass.entab com.ibm.lpex.samples.EntabCommand");

  // set new mouse button 1 drag to start a new selection
  MouseReselect.install(lpexView);

  /*--------------------*/
  /* redefine the popup */
  /*--------------------*/
  // add block-transfer action to the pop-up (context) menu, submenu "Selected"
  StringBuilder sb = new StringBuilder(lpexView.query("current.popup"));
  String transferSubmenu = " \"&Transfer selection\" blockTransfer ";
  int i = sb.indexOf("popup.blockDelete ");
  if (i >= 0)
   {
    sb.insert(i, transferSubmenu);
   }
  else
   {
    sb.append(" separator" + transferSubmenu);
   }

  // if properties file, add option to compare ignoring mnemonics on submenu "Source"
  if ("prop".equals(parser(lpexView)))
   {
    String compareSubmenu = " \"Compare w/o &mnemonics...\" myPropCompare ";
    i = sb.indexOf("popup.sourceMenu");
    if (i >= 0)
     {
      sb.insert(i + "popup.sourceMenu".length(), compareSubmenu);
     }
    else
     {
      sb.append(" separator beginSubmenu popup.sourceMenu" + compareSubmenu + "endSubmenu");
     }
   }

  lpexView.doDefaultCommand("set popup " + sb.toString());

  // indicate this profile has run
  lpexView.doDefaultCommand("set messageText * TestUserProfile settings in effect.");
 }

 /**
  * Determines the document parser which will run after this user profile completes.
  *
  * @param lpexView the document view for which this profile is being run
  * @return the document parser name, or <code>null</code> if none
  */
 public static String parser(LpexView lpexView)
 {
  String parserName = null;
  if (lpexView != null && !lpexView.queryOn("current.updateProfile.noParser"))
   {
    parserName = lpexView.query("current.updateProfile.parser");

    // if "associated", determine the parser via the document name / name extension
    if ("associated".equals(parserName))
     {
      parserName = null;

      // 1.- try the particular document name
      String baseName = baseName(lpexView);
      if (baseName != null)
       {
        if (baseName.indexOf('.') == -1)
         {
          baseName += '.'; // e.g., "MAKEFILE."
         }
        parserName = lpexView.query("current.updateProfile.parserAssociation." + baseName);
       }

      // 2.- try just the document-name extension
      if (parserName == null || parserName.length() == 0)
       {
        String extension = nameExtension(lpexView);
        if (extension != null)
         {
          parserName = lpexView.query("current.updateProfile.parserAssociation." + extension);
         }
       }
     }
   }

  return parserName;
 }

 /**
  * Returns the base document name (e.g., "test.java"), or
  * <code>null</code> if untitled.
  */
 private static String baseName(LpexView lpexView)
 {
  String baseName = lpexView.query("name");
  if (baseName != null)
   {
    // extract file name from path (NB assumes [canonical] doc name on current platform)
    int i = baseName.lastIndexOf(System.getProperty("file.separator"));
    if (i >= 0 && i < baseName.length()-1)
     {
      baseName = baseName.substring(i+1); // "test.java"
     }
   }
  return baseName;
 }

 /**
  * Returns the document name's extension (e.g., "java"), or
  * <code>null</code> if untitled or no extension.
  */
 private static String nameExtension(LpexView lpexView)
 {
  String extension = lpexView.query("name");
  if (extension != null)
   {
    int i = extension.lastIndexOf('.');
    extension = (i >= 0)? extension.substring(i+1) : null;
   }
  return extension;
 }
}