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

package com.ibm.lpex.samples;

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

/**
 * Sample action <b>compose</b> - enter special characters.
 * Use this action to enter into the document special characters that are
 * not found on your default keyboard.  For example, enter a acute (&aacute;)
 * by typing a compose sequence consisting of a and apostrophe ('), and the
 * plus-minus sign (&plusmn;) by typing + and -.
 *
 * <p>Here is the ComposeAction
 * <a href="doc-files/ComposeAction.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.compose com.ibm.lpex.samples.ComposeAction</pre></li>
 *  <li>Run it from the editor command line:
 *   <pre>action compose</pre>
 *   or associate it with a key (here, <b>Alt+F1</b> in the text area):
 *   <pre>set keyAction.a-f1 compose</pre></li>
 * </ul></p>
 * See the <a href="doc-files/ComposeAction.java.html#line48">source code</a>
 * for the supported compose sequences.  To see these characters in the document,
 * you may need a Unicode font installed on your system.  The document must be
 * saved to an encoding which supports the characters composed.
 *
 * @see com.ibm.lpex.samples All the samples
 */
public class ComposeAction implements LpexAction
{
 private static final String[] _compose =
      /*----------------------------------------------------------------*/
      /*  The special characters supported and their compose sequences  */
      /*----------------------------------------------------------------*/

                   // character                compose sequence
                   // =========                ================
 {"\u00c1", "A'",  // Á  A acute               A + APOSTROPHE (')
  "\u00e1", "a'",  // á  a acute               a + APOSTROPHE (')
  "\u00c5", "A*",  // Å  A angstrom            A + ASTERISK (*)
  "\u00e5", "a*",  // å  a angstrom            a + ASTERISK (*)
  "\u00c2", "A^",  // Â  A circumflex          A + CARET (^)
  "\u00e2", "a^",  // â  a circumflex          a + CARET (^)
  "\u00c0", "A`",  // À  A grave               A + ACCENT GRAVE (`)
  "\u00e0", "a`",  // à  a grave               a + ACCENT GRAVE (`)
  "\u00c3", "A~",  // Ã  A tilde               A + TILDE (~)
  "\u00e3", "a~",  // ã  a tilde               a + TILDE (~)
  "\u00c4", "A\"", // Ä  A umlaut              A + QUOTATION MARK (")
  "\u00e4", "a\"", // ä  a umlaut              a + QUOTATION MARK (")
  "\u0102", "A&",  //    A breve               A + AMPERSAND (&)
  "\u0103", "a&",  //    a breve               a + AMPERSAND (&)
  "\u00c6", "AE",  // Æ  AE diphthong          A + E
  "\u00e6", "ae",  // æ  ae diphthong          a + e

  "\u00a6", "/|",  // ¦  broken bar            SLASH (/) + VERTICAL LINE (|)
  "\u00c7", "C,",  // Ç  C cedilla             C + COMMA (,)
  "\u00e7", "c,",  // ç  c cedilla             c + COMMA (,)
  "\u00b8", ",,",  // ¸  cedilla               COMMA (,) + COMMA (,)
  "\u00b7", "^.",  // ·  center dot            CARET (^) + PERIOD (.)

  "\u00b0", "/0",  // °  degree                SLASH (/) + 0
  "\u00f7", ":-",  // ÷  division              COLON (:) + MINUS (-)
  "\u00c9", "E'",  // É  E acute               E + APOSTROPHE (')
  "\u00e9", "e'",  // é  e acute               e + APOSTROPHE (')
  "\u00ca", "E^",  // Ê  E circumflex          E + CARET(^)
  "\u00ea", "e^",  // ê  e circumflex          e + CARET (^)
  "\u00c8", "E`",  // È  E grave               E + ACCENT GRAVE (`)
  "\u00e8", "e`",  // è  e grave               e + ACCENT GRAVE (`)
  "\u00cb", "E\"", // Ë  E umlaut              E + QUOTATION MARK (")
  "\u00eb", "e\"", // ë  e umlaut              e + QUOTATION MARK (")
  "\u2014", "--",  //    em dash               MINUS (-) + MINUS (-)

  "\u00f0", "d-",  // ð  edh Latin small       d + MINUS (-)
  "\u00d0", "D-",  // Ð  edh Latin capital     D + MINUS (-)
  "\u00aa", "a_",  // ª  feminine ordinal      a + UNDERSCORE (_), or
  "\u00aa", "A_",  //                          A + UNDERSCORE (_)
  "\u00ad", "-=",  //    hyphen                MINUS (-) + EQUAL SIGN (=)

  "\u00cd", "I'",  // Í  I acute               I + APOSTROPHE (')
  "\u00ed", "i'",  // í  i acute               i + APOSTROPHE (')
  "\u00ce", "I^",  // Î  I circumflex          I + CARET (^)
  "\u00ee", "i^",  // î  i circumflex          i + CARET (^)
  "\u00cc", "I`",  // Ì  I grave               I + ACCENT GRAVE (`)
  "\u00ec", "i`",  // ì  i grave               i + ACCENT GRAVE (`)
  "\u00cf", "I\"", // Ï  I umlaut              I + QUOTATION MARK (")
  "\u00ef", "i\"", // ï  i umlaut              i + QUOTATION MARK (")

  "\u00a1", "!!",  // ¡  inverted !            EXCLAMATION (!) + EXCLAMATION (!)
  "\u00bf", "??",  // ¿  inverted ?            QUESTION MARK (?) + QUESTION MARK (?)
  "\u00ab", "<<",  // «  left angle quotes     LESS THAN SIGN (<) + LESS THAN SIGN (<)
  "\u00ba", "O_",  // º  masculine ordinal     O + UNDERSCORE (_), or
  "\u00ba", "o_",  //                          o + UNDERSCORE (_)
  "\u00b5", "/u",  // µ  micro sign            SLASH (/) + u
  "\u00d7", "xx",  // ×  multiply sign         x + x, or
  "\u00d7", "XX",  //                          X + X

  "\u00d1", "N~",  // Ñ  N tilde               N + TILDE (~)
  "\u00f1", "n~",  // ñ  n tilde               n + TILDE (~)
  "\u00ac", "-]",  // ¬  not sign              MINUS (-) + CLOSING BRACKET (])
  "\u2260", "/=",  //    not equal             SLASH (/) + EQUAL SIGN (=)

  "\u00d3", "O'",  // Ó  O acute               O + APOSTROPHE (')
  "\u00f3", "o'",  // ó  o acute               o + APOSTROPHE (')
  "\u00d4", "O^",  // Ô  O circumflex          O + CARET (^)
  "\u00f4", "o^",  // ô  o circumflex          o + CARET (^)
  "\u00d2", "O`",  // Ò  O grave               O + ACCENT GRAVE (`)
  "\u00f2", "o`",  // ò  o grave               o + ACCENT GRAVE (`)
  "\u00d8", "O/",  // Ø  O slash               O + SLASH (/)
  "\u00f8", "o/",  // ø  o slash               o + SLASH (/)
  "\u00d5", "O~",  // Õ  O tilde               O + TILDE (~)
  "\u00f5", "o~",  // õ  o tilde               o + TILDE (~)
  "\u00d6", "O\"", // Ö  O umlaut              O + QUOTATION MARK (")
  "\u00f6", "o\"", // ö  o umlaut              o + QUOTATION MARK (")

  "\u00af", "^_",  // ¯  overline              CARET (^) + UNDERSCORE (_)
  "\u00b6", "/p",  // ¶  paragraph symbol      SLASH (/) + p
  "\u00b1", "+-",  // ±  plus-minus sign       PLUS (+) + MINUS (-)
  "\u00bb", ">>",  // »  right angle quotes    GREATER THAN (>) + GREATER THAN (>)
  "\u015e", "S,",  //    S cedilla             S + COMMA (,)
  "\u015f", "s,",  //    s cedilla             s + COMMA (,)
  "\u00a7", "/s",  // §  section sign          SLASH (/) + s
  "\u00df", "ss",  // ß  sharp s German small  s + s
  "\u0162", "T,",  //    T cedilla             T + COMMA (,)
  "\u0163", "t,",  //    t cedilla             t + COMMA (,)
  "\u00fe", "p-",  // þ  thorn Latin small     p + MINUS (-)
  "\u00de", "P-",  // Þ  thorn Latin capital   P + MINUS (-)
  "\u2122", "T0",  //    Trade mark sign       T + 0, or
  "\u2122", "t0",  //                          t + 0, or
  "\u2122", "(T)", //                          LEFT PAREN (() + T + RIGHT PAREN ()), or
  "\u2122", "(t)", //                          LEFT PAREN (() + t + RIGHT PAREN ())

  "\u00da", "U'",  // Ú  U acute               U + APOSTROPHE (')
  "\u00fa", "u'",  // ú  u acute               u + APOSTROPHE (')
  "\u00db", "U^",  // Û  U circumflex          U + CARET (^)
  "\u00fb", "u^",  // û  u circumflex          u + CARET (^)
  "\u00d9", "U`",  // Ù  U grave               U + ACCENT GRAVE (`)
  "\u00f9", "u`",  // ù  u grave               u + ACCENT GRAVE (`)
  "\u00dc", "U\"", // Ü  U umlaut              U + QUOTATION MARK (")
  "\u00fc", "u\"", // ü  u Umlaut              u + QUOTATION MARK (")
  "\u00dd", "Y'",  // Ý  Y acute               Y + APOSTROPHE (')
  "\u00fd", "y'",  // ý  y acute               y + APOSTROPHE (')
  "\u00ff", "y\"", // ÿ  y umlaut              y + QUOTATION MARK (")

  "\u00a9", "C0",  // ©  Copyright sign        C + 0, or
  "\u00a9", "c0",  //                          c + 0, or
  "\u00a9", "(C)", //                          LEFT PAREN (() + C + RIGHT PAREN ()), or
  "\u00a9", "(c)", //                          LEFT PAREN (() + c + RIGHT PAREN ())
  "\u00ae", "R0",  // ®  Registered sign       R + 0, or
  "\u00ae", "r0",  //                          r + 0, or
  "\u00ae", "(R)", //                          LEFT PAREN (() + R + RIGHT PAREN ()), or
  "\u00ae", "(r)", //                          LEFT PAREN (() + r + RIGHT PAREN ())

  "\u00a4", "X0",  // ¤  currency sign         X + 0, or
  "\u00a4", "x0",  //                          x + 0
  "\u00a2", "c|",  // ¢  cent                  c + VERTICAL LINE (|), or
  "\u00a2", "c/",  //                          c + SLASH (/), or
  "\u00a2", "C|",  //                          C + VERTICAL LINE (|), or
  "\u00a2", "C/",  //                          C + SLASH (/)
  "\u20ac", "C=",  //    Euro sign             C + EQUAL SIGN (=), or
  "\u20ac", "c=",  //                          c + EQUAL SIGN (=), or
  "\u20ac", "C-",  //                          C + MINUS (-), or
  "\u20ac", "c-",  //                          c + MINUS (-)
  "\u00a3", "L=",  // £  Pound sign            L + EQUAL SIGN (=), or
  "\u00a3", "l=",  //                          l + EQUAL SIGN (=), or
  "\u00a3", "L-",  //                          L + MINUS (-), or
  "\u00a3", "l-",  //                          l + MINUS (-)
  "\u00a5", "Y=",  // ¥  Yen sign              Y + EQUAL SIGN (=), or
  "\u00a5", "y=",  //                          y + EQUAL SIGN (=), or
  "\u00a5", "Y-",  //                          Y + MINUS (-), or
  "\u00a5", "y-",  //                          y + MINUS (-)

  "\u2070", "^0",  //    0 superscript         CARET (^) + 0
  "\u00b9", "^1",  // ¹  1 superscript         CARET (^) + 1
  "\u00b2", "^2",  // ²  2 superscript         CARET (^) + 2
  "\u00b3", "^3",  // ³  3 superscript         CARET (^) + 3
  "\u2074", "^4",  //    4 superscript         CARET (^) + 4
  "\u2075", "^5",  //    5 superscript         CARET (^) + 5
  "\u2076", "^6",  //    6 superscript         CARET (^) + 6
  "\u2077", "^7",  //    7 superscript         CARET (^) + 7
  "\u2078", "^8",  //    8 superscript         CARET (^) + 8
  "\u2079", "^9",  //    9 superscript         CARET (^) + 9
  "\u207a", "^+",  //    superscript plus      CARET (^) + PLUS (+)
  "\u207b", "^-",  //    superscript minus     CARET (^) + MINUS (-)

  "\u00bd", "1/2", // ½  one half fraction     1 + SLASH (/) + 2
  "\u2153", "1/3", //    one third fraction    1 + SLASH (/) + 3
  "\u00bc", "1/4", // ¼  one quarter fraction  1 + SLASH (/) + 4
  "\u2155", "1/5", //    one fifth fraction    1 + SLASH (/) + 5
  "\u2159", "1/6", //    one sixth fraction    1 + SLASH (/) + 6
  "\u215b", "1/8", //    one eighth fraction   1 + SLASH (/) + 8
  "\u2154", "2/3", //    two thirds fraction   2 + SLASH (/) + 3
  "\u2156", "2/5", //    two fifths fraction   2 + SLASH (/) + 5
  "\u00be", "3/4", // ¾  3 quarters fraction   3 + SLASH (/) + 4
  "\u2157", "3/5", //    3 fifths fraction     3 + SLASH (/) + 5
  "\u215c", "3/8", //    3 eighths fraction    3 + SLASH (/) + 8
  "\u2158", "4/5", //    4 fifths fraction     4 + SLASH (/) + 5
  "\u215a", "5/6", //    5 sixths fraction     5 + SLASH (/) + 6
  "\u215d", "5/8", //    5 eighths fraction    5 + SLASH (/) + 8
  "\u215e", "7/8"  //    7 eighths fraction    7 + SLASH (/) + 8
  };

 // NOTES: In order to accept *only* special characters in
 // the given sequence, enter this on an LPEX command line:
 //  set userParameter.Compose.onlySpecialCharacters on
 // In order to accept the compose sequences *only* in their
 // defined order, enter this on an LPEX command line:
 //  set userParameter.Compose.onlyDefinedOrder on


 /**
  * Command to compose special characters from a sequence of regular ones.
  * Called by the <b>input</b> editor command when prompting for the compose sequence.
  * Also used by {@link TestUserProfile}.
  */
 public static LpexCommand composeCommand = new LpexCommand() {
  public boolean doCommand(LpexView lpexView, String parameters) {
   return doComposeCommand(lpexView, parameters, _compose, "compose",
                           lpexView.queryOn("userParameter.Compose.onlyDefinedOrder"));
   }
  };

 /**
  * Handles a compose command's doCommand() using the given
  * composition / mapping table and compose command's name.
  */
 static boolean doComposeCommand(LpexView lpexView, String parameters,
                                 String[] composeTable, String composeCommandName,
                                 boolean onlyDefinedOrder)
 {
  // clear any previous error message we might have issued
  lpexView.doDefaultCommand("set messageText");

  boolean onlySpecialCharacters =
   lpexView.queryOn("userParameter.Compose.onlySpecialCharacters");

  while (parameters.length() > 0)
   {
    // look greedily for a compose sequence at the start of parameters
    int index = findSpecialCharacter(parameters, onlyDefinedOrder, composeTable);
    if (index == 0)
     {
      if (onlySpecialCharacters)
       {
        break; // not a special character, reject...
       }
     }

    // 'type in' the (special) character
    String c = (index != 0)? composeTable[index-1] : String.valueOf(parameters.charAt(0));
    if (!typeIn(lpexView, c))
     {
      // error while inserting: restate prompt with what's still left
      promptForCompose(lpexView, composeCommandName, parameters);
      return false;
     }

    // there may be more sequences to compose - drop what we've used
    parameters = parameters.substring((index != 0)? composeTable[index].length() : 1);
    if (parameters.length() == 0)
     {
      return true;
     }
   }

  // cannot compose: display error, restate prompt with what's still left
  lpexView.doCommand("set messageText Cannot compose character from " +
                     "\"" + parameters + "\".");
  promptForCompose(lpexView, composeCommandName, parameters);
  return false;
 }

 // Returns the index of the composition sequence or mapping character in the given
 // table, if any, that is found at the start of the given sequence.  Returns 0 if none.
 static int findSpecialCharacter(String sequence, boolean onlyDefinedOrder, String[] composeTable)
 {
  // possible reversed sequence - see check below
  String reverseSequence = (onlyDefinedOrder || sequence.length() < 2)? null :
   new String(new char[] { sequence.charAt(1), sequence.charAt(0) });

  for (int i = 1; i < composeTable.length; i += 2)
   {
    String composeSequence = composeTable[i];
    if (composeSequence.length() == 1)
     {
      if (composeSequence.charAt(0) == sequence.charAt(0))
       {
        return i;
       }
     }
    else
     {
      if (sequence.startsWith(composeSequence))
       {
        return i;
       }

      // for most, why not allow the two compose characters to be in any order
      // (not for the various 3-char-compose fractions, and letters-only AE ae)...
      if (!onlyDefinedOrder &&
          composeSequence.length() == 2 &&
          composeSequence.equals(reverseSequence) &&
          !composeSequence.equals("AE") && !composeSequence.equals("ae"))
       {
        return i;
       }
     }
   }

  return 0;
 }

 // Inserts the specified character (given as a one-character-long
 // string) into the document as if typed in by the user.
 static boolean typeIn(LpexView lpexView, String c)
 {
  // if any stream selection in this view, the character replaces it
  boolean inserting;
  if (lpexView.queryOn("block.inView") && "stream".equals(lpexView.query("block.type")))
   {
    lpexView.doCommand("block delete");
    inserting = true;
   }
  // otherwise go by the current insert/replace mode
  else
   {
    inserting = lpexView.queryOn("insertMode");
   }

  boolean success = lpexView.doCommand((inserting? "insertText " : "replaceText ") + c);
  // to also check "status" when LPEX supports enforcing the text limit...
  return success;
 }

 // Prompts the user for the compose sequence.
 // When user types in text and presses Enter, the composeCommand is run.
 static void promptForCompose(LpexView lpexView, String composeCommand)
 {
  promptForCompose(lpexView, composeCommand, null);
 }

 // Prompts the user for the compose sequence with an optional initial sequence.
 // When user (types in text and) presses Enter, (a new) composeCommand is run.
 static void promptForCompose(LpexView lpexView, String composeCommand, String composeSequence)
 {
  String text = (composeSequence != null)?
                 LpexStringTokenizer.addQuotes(composeSequence) : "";
  LpexView.doGlobalCommand("set status");
  lpexView.doCommand("input \"Compose:\" " + text + " \"" + composeCommand + " \"");
 }

 /**
  * Compose action that can be used when the <b>compose</b> command has been
  * already defined in the view.  It is used by {@link TestUserProfile}.
  */
 public static LpexAction composeAction = new LpexAction() {
  public void doAction(LpexView lpexView) {
   promptForCompose(lpexView, "compose");
   }
  public boolean available(LpexView lpexView) {
   return lpexView.currentElement() != 0 && !lpexView.queryOn("readonly");
   }
  };

 /**
  * Runs the action.
  * Prompts the user for a compose sequence, then enters the (special)
  * character(s) into the document.  Uses the <b>input</b> default editor
  * command, and the <b>compose</b> command defined in here.
  */
 public void doAction(LpexView lpexView)
 {
  // ensure command to handle composition is defined in the view this action runs
  if (lpexView.command("compose") != composeCommand)
   {
    lpexView.defineCommand("compose", composeCommand);
   }

  // prompt user for the composition characters, insert result
  promptForCompose(lpexView, "compose");
 }

 /**
  * 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");
 }
}