/*
 * Copyright (c) 1995-1997 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */
/*
 * @(#)CollateDemo.java	1.1 97/5/23
 *
 * (C) Copyright Taligent, Inc. 1996,1997 - All Rights Reserved
 * (C) Copyright IBM Corp. 1996 - All Rights Reserved
 *
 * Portions copyright (c) 1996 Sun Microsystems, Inc. All Rights Reserved.
 *
 *   The original version of this source code and documentation is copyrighted
 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
 * materials are provided under terms of a License Agreement between Taligent
 * and Sun. This technology is protected by multiple US and International
 * patents. This notice and attribution to Taligent may not be removed.
 *   Taligent is a registered trademark of Taligent, Inc.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 *
 */

import java.applet.Applet;
import java.awt.*;
import java.util.Vector;
import java.util.Locale;
import java.text.NumberFormat;
import java.text.Collator;
import java.text.RuleBasedCollator;
import java.text.ParseException;
import java.text.CollationElementIterator;
import java.text.BreakIterator;

/**
 * Concrete class for demonstrating language sensitive collation.
 * The following is the instruction on how to run the collation demo.
 * <p>
 * ===================
 * <H3>Customization</H3>
 * You can produce a new collation by adding to or changing an existing
 * one.
 * <H4>To show...</H4>
 * <BLOCKQUOTE>You can modify an existing collation to show how this works.
 * By adding items at the end of a collation you override earlier
 * information.
 * Watch how you can make the letter P sort at the end of the
 * alphabet.</BLOCKQUOTE>
 * <H4>Do...</H4>
 * <BLOCKQUOTE>1. Scroll to the end of the Sequence field. After the Z,
 * type
 * &quot;&lt; p , P&quot;. This will put the letter P (with both of its
 * case
 * variants) at the end of the alphabet. Hit the Set Rule button. This creates
 * a new collation with the name &quot;Custom-1&quot; (you could give it a
 * different name by typing in the Collator Name field). When you now look
 * at the Text field, you will see that you have changed the sequence to put
 * <I>Pat</I>
 * at the end. (If you did not have Sort Ascending on, click it
 * now.)</BLOCKQUOTE>
 * <BR>
 * Making P sort at the end may not seem terribly useful, but it is used to
 * make modifications in the sorting sequence for different languages.
 * <H4>To show...</H4>
 * <BLOCKQUOTE>For example, you can add CH as a single letter after C, as
 * in
 * traditional Spanish sorting.</BLOCKQUOTE>
 * <H4>Do...</H4>
 * <BLOCKQUOTE>Enter in the following after Z; &quot;&amp; C < ch , cH, Ch,
 * CH&quot;.
 * Hit the Set Rule button, type in test words in the Text field (such as
 * &quot;czar&quot;,
 * &quot;churo&quot; and &quot;darn&quot;), and select Sort Ascending to
 * see
 * the resulting sort order.</BLOCKQUOTE>
 * <H4>To show...</H4>
 * <BLOCKQUOTE>You can also add other sequences to the collation rules,
 * such as sorting symbols with their alphabetic equivalents.</BLOCKQUOTE>
 * <H4>Do...</H4>
 * <BLOCKQUOTE>1. Scroll to the end of the Sequence field. After the end,
 * type the following list (you can just select this text in your browser and
 * paste it in, to avoid typing). Now type lines in the Text field with these
 * symbols on them, and select Sort Ascending to see the resulting sort
 * order.</BLOCKQUOTE>
 * <UL>
 *   <UL>
 *     <LI>&amp; Asterisk ; *
 *     <LI>&amp; Question-mark ; ?
 *     <LI>&amp; Hash-mark ; #
 *     <LI>&amp; Exclamation-mark ; !
 *     <LI>&amp; Dollar-sign ; $
 *     <LI>&amp; Ampersand ; '&amp';
 *   </UL>
 * </UL>
 * <H4>Details</H4>
 * If you are an advanced user and interested in trying out more rules,
 * here is a brief explanation of how they work. The sequence is a list of
 * rules. Each rule is of two forms:
 * <UL>
 *   <LI>&lt;modifier&gt;
 *   <LI>&lt;relation&gt; &lt;text-argument&gt;
 *   <LI>&lt;reset&gt; &lt;text-argument&gt;
 * </UL>
 * <H5>Modifier</H5>
 * <BLOCKQUOTE>@ Indicates that accents are sorted backwards, as in
 * French</BLOCKQUOTE>
 * <H5>Text-argument</H5>
 * The text can be any number of characters (if you want to include special
 * characters, such as space, use single-quotes around them).
 * <H5>Relation</H5>
 * The relations are the following:
 * <DL>
 *   <DD>&lt;	Greater, as a letter difference (primary)
 *   <DD>;	Greater, as an accent difference (secondary)
 *   <DD>,	Greater, as a case difference (tertiary)
 *   <DD>=	Equal
 *   <DD>&amp;	Reset previous comparison.
 * </DL>
 * <H5>Reset</H5>
 * The &quot;&amp;&quot; is special in that does not put the text-argument
 * into the sorting sequence; instead, it indicates that the <I>next</I>
 * rule is with respect to where the text-argument <I>would be</I> sorted.
 * This sounds more complicated than it is in practice. For example, the
 * following are equivalent ways of expressing the same thing:
 * <UL>
 *   <LI>a &lt; b &lt; c
 *   <LI>a &lt; b &amp; b &lt; c
 *   <LI>a &lt; c &amp; a &lt; b
 * </UL>
 * Notice that the order is important, since the subsequent item goes
 * <I>immediately</I>
 * after the text-argument. The following are <I>not</I> equivalent:
 * <UL>
 *   <LI>a &lt; b &amp; a &lt; c
 *   <LI>a &lt; c &amp; a &lt; b
 * </UL>
 * The text-argument must already be present in the sequence, or some
 * initial substring of the text-argument must be present. (e.g. &quot;a &lt;
 * b&amp; ae &lt; e&quot; is valid since &quot;a&quot; is present in the
 * sequence<I>before</I> &quot;ae&quot; is reset). In this latter case,
 * &quot;ae&quot;
 * is <B><I>not</I></B> entered and treated as a single character; instead,
 * &quot;e&quot; is sorted as if it were expanded to two characters:
 * &quot;a&quot;
 * followed by an &quot;e&quot;.<BR>
 * This difference appears in natural languages: in traditional Spanish
 * &quot;ch&quot;
 * is treated as though it <I>contracts</I> to a single character
 * (expressed
 * as &quot;c &lt; ch &lt; d&quot;), while in traditional German
 * &quot;&auml;&quot;
 * (a-umlaut) is treated as though it <I>expands</I> to two characters
 * (expressed
 * as &quot;a &amp; ae ; &auml; &lt; b&quot;).<BR>
 * <H5>Ignorable Characters</H5>
 * The first rule must start with a relation (the examples we have used are
 * fragments; &quot;a &lt; b&quot; really should be &quot;&lt; a &lt;
 * b&quot;).
 * If, however, the first relation is not &quot;&lt;&quot;, then all the
 * all
 * text-arguments up to the first &quot;&lt;&quot; are ignorable. For
 * example,
 * &quot;, - &lt; a &lt; b&quot; makes &quot;-&quot; an ignorable
 * character,
 * as we saw earlier in the word &quot;black-birds&quot;.<BR>
 * <H5>Accents</H5>
 * The Collator automatically normalizes text internally to separate
 * accents
 * from base characters where possible. So, if you type in an
 * &quot;&auml;&quot;
 * (a-umlaut), after you reset the collation you will see
 * &quot;a\u0308&quot;
 * in the sequence, where \u0308 is the Java syntax for umlaut. The
 * demonstration
 * program uses this syntax instead of just showing the umlaut since many
 * browsers are unable to display the umlaut yet.<BR>
 * <H4>Errors</H4>
 * The following are errors:
 * <UL>
 *   <LI>Two relations in a row (e.g. &quot;a &lt; , b&quot;
 *   <LI>Two text arguments in a row (e.g. &quot;a &lt; b c &lt; d&quot;)
 *   <LI>A reset where the text-argument is not already in the sequence
 * (e.g.&quot;a &lt; b &amp; e &lt; f&quot;)
 * </UL>
 * If you produce one of these errors, then the demonstration will beep at
 * you, and select the offending text (note: on some browsers, the
 * selection will not appear correctly).
 * @version    1.1 11/23/96
 * @author     Kathleen Wilson, Helena Shih
 * @see        java.util.Collator
 * @see        java.util.RuleBasedCollator
 * @see        java.demos.utilities.DemoApplet
 */
public class CollateDemo extends DemoApplet
{
    /**
     * The main function which defines the behavior of the CollateDemo applet
     * when an applet is started.
     */
    public static void main(String argv[]) {
        DemoApplet.showDemo(new CollateFrame(null));
    }

    /**
     * This creates a CollateFrame for the demo applet.
     */
    public Frame createDemoFrame(DemoApplet applet) {
        return new CollateFrame(applet);
    }
}

/**
 * A Frame is a top-level window with a title. The default layout for a frame
 * is BorderLayout.  The CollateFrame class defines the window layout of
 * CollateDemo.
 */
class CollateFrame extends Frame
{


    /**
     * Constructs a new CollateFrame that is initially invisible.
     */
    public CollateFrame(DemoApplet applet)
    {
        super("Collator Demo");
        this.applet = applet;
        init();
    }

    /**
     * Initializes the applet. You never need to call this directly, it
     * is called automatically by the system once the applet is created.
     */
    public void init()
    {
        theLocale = Locale.US;
        theCollation = Collator.getInstance(theLocale);
        buildGUI();
    }

    /**
     * Handles the event. Returns true if the event is handled and should not
     * be passed to the parent of this component. The default event handler
     * calls some helper methods to make life easier on the programmer.
     */
    public boolean handleEvent(Event evt) {

        if (evt.target == searchTextEntry &&
           (evt.id == Event.KEY_PRESS || evt.id == Event.KEY_ACTION)) {
           switch (evt.key) {
             case 0x3ef:
                return handleFindNext();
             case 0x3ee:
                return handleFindPrevious();
            }
        }

        if (evt.id == Event.MOUSE_UP 
            && tabsAndCards.didCardChange() == true) {            
             if (searchPanel.isShowing() == true) {
               isCustomizing = false;
               setSearchMsg("Use arrow keys to find next and previous.");
             }
             else if (sortPanel.isShowing() == true) {
               isCustomizing = false;
             }
             else if (customPanel.isShowing() == true) {
               isCustomizing = true;
               errorText("");
               return handleLocale(); //to display current locale rules
             }
        }

        if (evt.id == Event.ACTION_EVENT) {
             errorMsg.setVisible(false);
             if (evt.target == sortAscending)
                    return handleSort(true, false);
             else if (evt.target == sortDescending)
                    return handleSort(false, true);
             else if (evt.target == localeChoice) {
                    handleLocale();
                    return handleSort(sortAscending.getState(),
                                      sortDescending.getState());
             }
             else if (evt.target == decompChoice) {
                return handleSort(sortAscending.getState(),
                                  sortDescending.getState());
             }
             else if (evt.target == strengthChoice) {
                    return handleSort(sortAscending.getState(),
                                      sortDescending.getState());
             }
             else if (evt.target == collateRulesButton) {
                    collateRulesButton.setLabel("setting");
                    handleSetRules();
                    collateRulesButton.setLabel("Set Rules");
                    return handleSort(sortAscending.getState(),
                                      sortDescending.getState());
             }
        }
        else if (evt.id == Event.KEY_PRESS) {
             if (evt.target == textentry) {
                checkboxes.setSelectedCheckbox(noSort);
             }
        }

        else if (evt.id == Event.WINDOW_DESTROY && evt.target == this) {
               this.setVisible(false);
               this.dispose();

                if (applet != null) {
                   applet.demoClosed();
                } else System.exit(0);

               return true;
        }

        return super.handleEvent(evt);
    }

    /**
     * This function is called when you press the "SortDescend" button.
     * It does an extremely simple sort, using the Collator.compare function.
     * To improve performance, you could transform the strings into
     * a sort key which is a series of characters that can be
     * compared using bit-wise comparison.  In addition, you can also use
     * collation keys to compare two strings based on the collation rules.
     * @see java.util.Collator#compare
     * @see java.util.Collator#getSortKey
     * @see java.util.Collator#getCollationKey
     */

    public boolean handleSort(boolean ascending, boolean descending) {
        if (ascending == descending)
            return true;
        int exchangeResult = ascending ? -1 : 1;

        InitializeListVector();

        String targetItem;
        String sourceItem;
        byte compareResult;


        String strengthName = strengthChoice.getSelectedItem();
        if (strengthName.equals(tertiary)) {
             theCollation.setStrength(Collator.TERTIARY);
        }else if (strengthName.equals(secondary)) {
             theCollation.setStrength(Collator.SECONDARY);
        } else theCollation.setStrength(Collator.PRIMARY);

        int decompItem = decompChoice.getSelectedIndex();
        if (decompItem == 0) {
             theCollation.setDecomposition(Collator.NO_DECOMPOSITION);
        }else if (decompItem == 1) {
             theCollation.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
        } else theCollation.setDecomposition(Collator.FULL_DECOMPOSITION);

        int numItems = textList.size();
        for (int sourceIndex = 1; sourceIndex < numItems; sourceIndex++)
        {
           sourceItem = (String) textList.elementAt(sourceIndex);
           for (int targetIndex = 0; targetIndex < sourceIndex; targetIndex++)
           {
                 targetItem = (String) textList.elementAt(targetIndex);
              compareResult = (byte) theCollation.compare(sourceItem, targetItem);
              if (compareResult == exchangeResult)
              {
                textList.removeElementAt(sourceIndex);
                textList.insertElementAt(sourceItem, targetIndex);
                break;
              }
           }
        }
        resetTextArea();
        return true;
    }

    /**
     * This function is called when you press the Locale button.
     * It sets the locale that is to be used to create the collation object.
     * @see java.util.Collator#getInstance
     */
    public boolean handleLocale()
    {
        int index = localeChoice.getSelectedIndex();
        theCollation = collations[index];

        if (isCustomizing == true && theCollation != null) {
             String theRules = ruleStrings[index];

             if (theRules == null) {
                theRules = createUnicodeString(
                            ((RuleBasedCollator)theCollation).getRules());
                ruleStrings[index] = theRules;
             }
            
             ruleEntry.setText(theRules);
         }

        return true;
    }

    /**
     * This function is called when you press the "Set Rules" button.
     * It sets the rules that are to be used by the current collation object.
     * @see java.util.RuleBasedCollator#getRules
     */
    public void handleSetRules()
    {
        int index = localeChoice.getSelectedIndex();

        String rules = ruleEntry.getText();
        if ((rules.equals(ruleStrings[index]) == false)
             && (theCollation instanceof RuleBasedCollator) )
        {
            int idx = 0;
            try
            {
                String collName = collationName.getText();
                for (int n = 0; n < localeChoice.getItemCount(); n++)
                {
                    if (collName.equals(localeChoice.getItem(n)))
                    {
                        theCollation = new
                            RuleBasedCollator(convertStringToRules(rules));
                        collations[n] = theCollation;
                        idx = n;
                        break;
                    }
                }
                if (idx == 0)
                {
                    if (localeChoice.getItemCount() < MAX_COLLATIONS)
                    {
                        idx = localeChoice.getItemCount();
                        theCollation = new
                            RuleBasedCollator(convertStringToRules(rules));

                        collations[idx] = theCollation;
                        localeChoice.addItem( collName );
                    }
                    else
                    {
                        throw new ParseException("Max # exceeded!" +
                            "Please replace an existing one.", 0);
                    }

                    if (collName.startsWith("custom-"))
                    {
                        untitledIndex++;
                    }
                }
                localeChoice.select(idx);
                ruleStrings[idx] = null; //force recalculation of locale rule string
                handleLocale();
                if (localeChoice.getItemCount() < MAX_COLLATIONS)
                    collationName.setText("custom-" + untitledIndex);
            }
            catch (ParseException foo)
            {
                collateRulesButton.setLabel("Set Rules");
                errorText("Error: " + foo.getMessage());
            }
        }
    }


    /**
     * Must be called after the array of locales has been initialized.
     */

    public void loadCollationTables()
    {
        MAX_COLLATIONS = locales.length + 10;
        collations = new Collator[MAX_COLLATIONS];

        for (int i = 0; i < locales.length; i++)
        {
           collations[i] = Collator.getInstance(locales[i]);
        }
    }

    //------------------------------------------------------------
    // package private
    //------------------------------------------------------------

    Panel makePanel(Component demo, Component code) {
        Panel temp = new Panel();

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints constraints = new GridBagConstraints();
        temp.setLayout(gridbag);

        constraints.anchor = GridBagConstraints.NORTHWEST;
           constraints.gridwidth = GridBagConstraints.REMAINDER; //end row
        gridbag.setConstraints(demo, constraints);

           constraints.gridheight = GridBagConstraints.REMAINDER; //end column
        gridbag.setConstraints(code, constraints);

        temp.add(demo);
        temp.add(code);
        return temp;
    }

    void addWithFont(Container container, Component foo, Font font) {
        if (font != null)
            foo.setFont(font);
        container.add(foo);
    }


    void buildGUI()
    {
        setBackground(Utility.bgColor);
        setLayout(new BorderLayout());

    //SORT, SEARCH, CUSTOMIZE

        tabsAndCards = new TabPanel();
        tabsAndCards.add("Sort",createSortPanel());
        tabsAndCards.add("Search",createSearchPanel());
        tabsAndCards.add("Customize",createCustomPanel());

    // TITLE

        Label title=new Label("Collator Demo", Label.CENTER);
        title.setFont(Utility.titleFont);

        Panel titlePanel = new Panel();
        titlePanel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
        titlePanel.add(title);
        add("North", titlePanel);

    // LOCALE
        Locale myLocale = new Locale("","","");

        Label localeLabel = new Label("Locale:   ", Label.LEFT);
        localeLabel.setFont(Utility.labelFont);

        localeChoice = new Choice();
        localeChoice.setBackground(Utility.choiceColor);

        //Get all locales for debugging, but only get G7 locales for demos.
        if (DEBUG == true)
             locales = Collator.getAvailableLocales();
        else locales = Utility.getG7Locales();

        int defaultLoc = 0;
        for (int i = 0; i < locales.length; i++) {
            if (locales[i].getCountry().length() > 0) {
                localeChoice.addItem
                    (locales[i].getDisplayName());
                if (locales[i].equals(theLocale)) {
                    defaultLoc = i;
                }
            }
        }
        localeChoice.setFont(Utility.choiceFont);

        //must be called after the array of locales has been initialized
        loadCollationTables();
        localeChoice.select(defaultLoc);

        Label decompLabel = new Label("Decomposition Mode:", Label.LEFT);
        decompLabel.setFont(Utility.labelFont);
        decompChoice = new Choice();
        decompChoice.setBackground(Utility.choiceColor);
        decompChoice.addItem(no_decomposition);
        decompChoice.addItem(canonical_decomposition);
        decompChoice.addItem(full_decomposition);
        decompChoice.select(canonical_decomposition);
        decompChoice.setFont(Utility.choiceFont);

        Label strengthLabel = new Label("Strength:", Label.LEFT);
        strengthLabel.setFont(Utility.labelFont);

        strengthChoice = new Choice();
        strengthChoice.setBackground(Utility.choiceColor);
        strengthChoice.addItem(tertiary);
        strengthChoice.addItem(secondary);
        strengthChoice.addItem(primary);
        strengthChoice.setFont(Utility.choiceFont);

        Panel infoPanel = new Panel();
        infoPanel.add(localeLabel);
        infoPanel.add(localeChoice);
        infoPanel.add(decompLabel);
        infoPanel.add(decompChoice);
        infoPanel.add(strengthLabel);
        infoPanel.add(strengthChoice);
        Utility.fixGrid(infoPanel,1);

        Panel centerPanel = new Panel();
        centerPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 15, 0));
    	centerPanel.add(tabsAndCards);
        centerPanel.add(infoPanel);
        add("Center",centerPanel);


        //COPYRIGHTS

        Panel bottomPanel = new Panel();
        bottomPanel.setLayout(new GridLayout(2,1,0,0));

        addWithFont (bottomPanel,
                     new Label(Utility.copyright1, Label.LEFT),
                     Utility.creditFont);
        addWithFont (bottomPanel,
                     new Label(Utility.copyright2, Label.LEFT),
                     Utility.creditFont);
        Utility.fixGrid(bottomPanel,1);

        add("South", bottomPanel);
    }


    Panel createSortPanel()
    {
        sortPanel = new Panel();
        sortPanel.setLayout(new BorderLayout());

    // CHECKBOXES

        checkboxes= new CheckboxGroup();

        sortAscending = new Checkbox("Sort Ascending",checkboxes, false);
        sortAscending.setFont(Utility.labelFont);
        sortAscending.setSize(100, 20);

        sortDescending = new Checkbox("Sort Descending",checkboxes, false);
        sortDescending.setSize(100, 20);
        sortDescending.setFont(Utility.labelFont);

        noSort = new Checkbox("Not Sorted",checkboxes, true);
        noSort.setFont(Utility.labelFont);
        noSort.setSize(100, 20);

        Panel bPanel = new Panel();
        bPanel.setLayout(new GridLayout(3,1,0,0));
        bPanel.add(sortAscending);
        bPanel.add(sortDescending);
        bPanel.add(noSort);

        Panel buttonPanel = new Panel();
        buttonPanel.setLayout(new BorderLayout());
        buttonPanel.add("North",bPanel);

        // TEXT

        Panel tePanel = new Panel();
        tePanel.setLayout(new BorderLayout());

        textentry.setFont(editFont);
        textentry.setText("black-birds\n" +
                          "Pat\n" +
                          "\u00c4hre\n" +
                          "p\u00E9ch\u00E9\n" +
                          "p\u00EAche\n" +
                          "\u00c5rhus\n" +
                          "p\u00E9cher\n" +
                          "p\u00EAcher\n" +
                          "Tod\n" +
                          "T\u00F6ne\n" +
                          "Tofu\n" +
                          "blackbirds\n" +
                          "Ton\n" +
                          "PAT\n" +
                          "blackbird\n" +
                          "\u00d8re\n" +
                          "black-bird\n" +
                          "pat\n");


        Label sortLabel = new Label("Text to Sort:", Label.LEFT);
        sortLabel.setFont(Utility.labelFont);
        tePanel.add("North", sortLabel);
        tePanel.add("Center", textentry);

        // PUT ALL TOGETHER

        Panel middlePanel = new Panel();
        middlePanel.setLayout(new FlowLayout(FlowLayout.LEFT,0,0));
        middlePanel.add(buttonPanel);
        middlePanel.add(tePanel);
        sortPanel.add("Center", middlePanel);

        return sortPanel;
    }


    Panel createCustomPanel()
    {
        customPanel = new Panel();
        customPanel.setLayout(new BorderLayout());

        //RULE ENTRY AREA
        Panel ruleEntryPanel = new Panel();

        Panel rulePanel = new Panel();

        ruleEntry.setFont(ruleFont);

        Panel namePanel = new Panel();
        namePanel.setLayout(new FlowLayout(FlowLayout.LEFT,0,2));
        collateRulesButton = new Button("Set Rules");
        collationName = new TextField(10);
        collationName.setText("custom-" + untitledIndex);
        collationName.setFont(Utility.choiceFont);

        Label colLabel = new Label("Collator Name: ", Label.LEFT);
        colLabel.setFont(Utility.labelFont);
        namePanel.add(colLabel);
        namePanel.add(collationName);

        Label rulesLabel = new Label("Collator Rules:", Label.LEFT);
        rulesLabel.setFont(Utility.labelFont);
        ruleEntryPanel.add(rulesLabel);
        ruleEntryPanel.add(ruleEntry);
        ruleEntryPanel.add(namePanel);
        ruleEntryPanel.add(collateRulesButton);

        errorMsg.setFont(Utility.labelFont);
        errorMsg.setText("This is where error messages go.............");
        ruleEntryPanel.add(errorMsg);

        Utility.fixGrid(ruleEntryPanel,1);

        customPanel.add("Center", ruleEntryPanel);

        return customPanel;
    }


    Panel createSearchPanel() 
    {
        searchPanel = new Panel();
        searchPanel.setLayout(new BorderLayout());

        //SEARCH DEMO

        Panel searchStringPanel = new Panel();
        searchStringPanel.setLayout(new FlowLayout(FlowLayout.LEFT,20,0));

        Label searchLabel = new Label("Search String:", Label.RIGHT);
        searchLabel.setFont(Utility.labelFont);
        searchStringPanel.add(searchLabel);
        searchString.setText("T\u00F6ne");
        searchString.setFont(Utility.choiceFont);
        searchStringPanel.add(searchString);

        searchTextEntry.setFont(editFont);
        searchTextEntry.setText("black-birds Pat p\u00E9ch\u00E9 \n" +
            "p\u00EAche p\u00E9cher p\u00EAcher \n" +
            "Tod T\u00F6ne Tofu blackbirds \n" +
            "Ton PAT blackbird \n" +
            "black-bird pat ");

        Panel findTextPanel = new Panel();
        findTextPanel.add(searchStringPanel);
        findTextPanel.add(searchTextEntry);

        searchByWord.setState(false);
	searchByWord.setFont(Utility.labelFont);
        findTextPanel.add(searchByWord);

        searchMsg.setFont(Utility.labelFont);
        setSearchMsg("Use arrow keys to find next and previous.");
        findTextPanel.add(searchMsg);

        Utility.fixGrid(findTextPanel,1);
 
        searchPanel.add("Center", findTextPanel);
   
        return searchPanel;
    }


    //------------------------------------------------------------
    // TextArea and TextField bug work-around
    //------------------------------------------------------------

    /**
     * This function fixes a bug in the TextArea and TextField which 
     * messes up the high byte of Unicode characters when you get the
     * text out.
     */
     private String getFixedString(TextComponent theArea)
     {
        String composite = theArea.getText();

        char compositeArray[] = composite.toCharArray();
        for (int i = 0; i < compositeArray.length; i++)
        {
           compositeArray[i] = (char) (compositeArray[i] & 0xFF);
        }

        return new String(compositeArray);
      }

    //------------------------------------------------------------
    // SEARCH DEMO
    //------------------------------------------------------------

    private void setSearchMsg(String s)
    {
        if (!s.equals(searchMsg.getText()))
        {
            searchMsg.setText(s);
        }
    }

    /**
     * This function is called when you press the "->" key.
     * It searches for the next string meeting the criteria of matching
     * to the search string according the the current locale and strength.
     * @see java.text.CollationElementIterator
     */
    public boolean handleFindNext()
    {
        if (searchByWord.getState() == true) {
            return handleFindNextWord();
        }

        String searchStr = getFixedString(searchString);
        String textStr = getFixedString(searchTextEntry);

        int searchOffset = searchTextEntry.getSelectionEnd();
        boolean matchFound = false;

        int strength = Collator.PRIMARY;
        String strengthName = strengthChoice.getSelectedItem();
        if (strengthName.equals(tertiary)) {
             strength = Collator.TERTIARY;
             theCollation.setStrength(Collator.TERTIARY);
        }else if (strengthName.equals(secondary)) {
             strength = Collator.SECONDARY;
             theCollation.setStrength(Collator.SECONDARY);
        } else theCollation.setStrength(Collator.PRIMARY);

        //make sure you only go through the text once
        for (int i = 0; i < textStr.length() + 1; i++) {

            if (searchOffset >= textStr.length()) 
               searchOffset = 0;

            String sub = textStr.substring(searchOffset, textStr.length());
            
            if (stringsMatch(searchStr, sub, strength) == true) {
              matchFound = true;
              break;
            }

            searchOffset++;
        }


        if (matchFound == true)
        {
           selectMatchingText(searchStr, textStr, searchOffset);
        }else {
           setSearchMsg("No Match Found");
           searchTextEntry.select(0, 0);
        }

        return matchFound;
    }


    /**
     * This function is called when you press the "->" key and
     * you have selected to find whole words only.
     * It searches for the next whole word meeting the criteria of matching
     * to the search string according the the current locale and strength.
     * @see java.text.CollationElementIterator
     */
    public boolean handleFindNextWord()
    {
        int strength = Collator.PRIMARY;
        String strengthName = strengthChoice.getSelectedItem();
        if (strengthName.equals(tertiary)) {
             strength = Collator.TERTIARY;
             theCollation.setStrength(Collator.TERTIARY);
        }else if (strengthName.equals(secondary)) {
             strength = Collator.SECONDARY;
             theCollation.setStrength(Collator.SECONDARY);
        } else theCollation.setStrength(Collator.PRIMARY);

        //Strip the leading and trailing spaces off of
        //the search string.
        String searchStr = getFixedString(searchString);
        int ssStart = 0;
        while (Character.isWhitespace(searchStr.charAt(ssStart))) {
            ssStart++;
        }
        int ssEnd = searchStr.length();
        searchStr = searchStr.substring(ssStart,ssEnd);

        int searchOffset = searchTextEntry.getSelectionEnd();
        boolean matchFound = false;

        String textStr = getFixedString(searchTextEntry);
        wordIterator.setText(textStr);
        int wordEnd = 0;

        //make sure you only go through the text once
        for (int i = 0; i < textStr.length() + 1;) {

            if (searchOffset >= textStr.length()) 
               searchOffset = 0;

            wordEnd = wordIterator.following(searchOffset);

            if (theCollation.equals(searchStr,
                      textStr.substring(searchOffset,wordEnd))) {
              matchFound = true;
              break;
            }

            i += wordEnd - searchOffset;
            searchOffset = wordEnd;
        }


        if (matchFound == true)
        {
           searchTextEntry.select(searchOffset, wordEnd);
           setSearchMsg("Found Match: \"" 
                     + textStr.substring(searchOffset,wordEnd)
                     + "\" at index " + String.valueOf(searchOffset));
        }else {
           setSearchMsg("No Match Found");
           searchTextEntry.select(0, 0);
        }

        return matchFound;
    }

    /**
     * This function is called when you press the "<-" key.
     * It searches for the previous string meeting the criteria of matching
     * to the search string according the the current locale and strength.
     * @see java.text.CollationElementIterator
     */
    public boolean handleFindPrevious()
    {
        if (searchByWord.getState() == true) {
            return handleFindPreviousWord();
        }

        String searchStr = getFixedString(searchString);
        String textStr = getFixedString(searchTextEntry);

        int searchOffset = searchTextEntry.getSelectionStart() - searchStr.length();
        boolean matchFound = false;

        int strength = Collator.PRIMARY;
        String strengthName = strengthChoice.getSelectedItem();
        if (strengthName.equals(tertiary)) {
             strength = Collator.TERTIARY;
             theCollation.setStrength(Collator.TERTIARY);
        }else if (strengthName.equals(secondary)) {
             strength = Collator.SECONDARY;
             theCollation.setStrength(Collator.SECONDARY);
        } else theCollation.setStrength(Collator.PRIMARY);

        //make sure you only go through the text once
        for (int i = 0; i < textStr.length() + 1; i++) {

            if (searchOffset < 0)
               searchOffset = textStr.length() - 1; 

            String sub = textStr.substring(searchOffset, textStr.length());
            
            if (stringsMatch(searchStr, sub, strength) == true) {
              matchFound = true;
              break;
            }

            searchOffset--;
        }


        if (matchFound == true)
        {
           selectMatchingText(searchStr, textStr, searchOffset);
        }else {
           setSearchMsg("No Match Found");
           searchTextEntry.select(0, 0);
        }

        return matchFound;
    }


    /**
     * This function is called when you press the "<-" key and
     * you have selected to find whole words only.
     * It searches for the previous whole word meeting the criteria of matching
     * to the search string according the the current locale and strength.
     * @see java.text.CollationElementIterator
     */
    public boolean handleFindPreviousWord()
    {
        int strength = Collator.PRIMARY;
        String strengthName = strengthChoice.getSelectedItem();
        if (strengthName.equals(tertiary)) {
             strength = Collator.TERTIARY;
             theCollation.setStrength(Collator.TERTIARY);
        }else if (strengthName.equals(secondary)) {
             strength = Collator.SECONDARY;
             theCollation.setStrength(Collator.SECONDARY);
        } else theCollation.setStrength(Collator.PRIMARY);

        //Strip the leading and trailing spaces off of
        //the search string.
        String searchStr = getFixedString(searchString);
        int ssStart = 0;
        while (Character.isWhitespace(searchStr.charAt(ssStart))) {
            ssStart++;
        }
        int ssEnd = searchStr.length();
        searchStr = searchStr.substring(ssStart,ssEnd);

        String textStr = getFixedString(searchTextEntry);
        wordIterator.setText(textStr);

        int searchOffset = searchTextEntry.getSelectionEnd();
        int wordEnd;

        if (searchOffset == textStr.length()) {
           wordIterator.last();
        } else wordIterator.following(searchOffset);

        if (searchTextEntry.getSelectionStart() == 
            searchTextEntry.getSelectionEnd()) {
           wordEnd = wordIterator.current();
        } else wordEnd = wordIterator.previous();

        searchOffset = wordIterator.previous();

        boolean matchFound = false;

        //make sure you only go through the text once
        for (int i = 0; i < textStr.length() + 1;) {

            if (searchOffset <= 0) {
               wordEnd = wordIterator.last();
            } else {
               wordEnd = searchOffset;
           }

            searchOffset = wordIterator.previous();

            if (theCollation.equals(searchStr,
                      textStr.substring(searchOffset,wordEnd))) {
              matchFound = true;
              break;
            }

            i += wordEnd - searchOffset;
        }


        if (matchFound == true)
        {
           searchTextEntry.select(searchOffset, wordEnd);
           setSearchMsg("Found Match: \"" 
                     + textStr.substring(searchOffset,wordEnd)
                     + "\" at index " + String.valueOf(searchOffset));
        }else {
           setSearchMsg("No Match Found");
           searchTextEntry.select(0, 0);
        }

        return matchFound;
    }


    /**
     * This function is used by handleFindNext();
     * Determine if the textToSearch begins with the text in searchStrIter.
     */
    private boolean stringsMatch(String searchStr, String textToSearch, int strength)
    {
       CollationElementIterator searchStringIter = 
             ((RuleBasedCollator)theCollation).getCollationElementIterator(searchStr);
       CollationElementIterator textToSearchIter = 
         ((RuleBasedCollator)theCollation).getCollationElementIterator(textToSearch);

       boolean result = true;

       int ssOrder = searchStringIter.next();
       int ttsOrder = textToSearchIter.next();

       while (ssOrder != CollationElementIterator.NULLORDER
              && ttsOrder != CollationElementIterator.NULLORDER)
       {

          //Ignore the ignorable characters
          while (mapOrder(ssOrder, strength) == 0) {
             ssOrder = searchStringIter.next();
          }
          if (ssOrder == CollationElementIterator.NULLORDER) {
                return result;
          } 
          while (mapOrder(ttsOrder, strength) == 0) {
             ttsOrder = textToSearchIter.next();
          }

          if (collationOrdersEqual(ssOrder, ttsOrder, strength) == false) {
               result = false;
               break;
           }

          ssOrder = searchStringIter.next();
          ttsOrder = textToSearchIter.next();
       }

       return result;
    }


    /**
     * Given a complete order, return the order for the given strength.
     */
    private int mapOrder(int order, int strength)
    {
       if (strength == Collator.TERTIARY) 
           return order & 0xFFFFFFFF;

       if (strength == Collator.PRIMARY) 
           return order & 0xFFFF0000;

       return order & 0xFFFFFF00;
    }

    /**
     * This function is used by handleFindNext();
     * Select the text in the target string that matches the search string.  
     * Ignore leading and trailing white space.
     */
    private void selectMatchingText(String searchStr, String targetStr, 
                                    int positionInTarget) 
    {
       //skip white space at beginning and end of search string.
       int ssStart = 0;
       while (Character.isWhitespace(searchStr.charAt(ssStart))) {
           ssStart++;
       }
       int ssEnd = searchStr.length();
    
       String searchSubStr = searchStr.substring(ssStart, ssEnd);

       //skip white space at beginning of target string
       while (Character.isWhitespace(targetStr.charAt(positionInTarget))) {
           positionInTarget++;
       }

       int endIndex = positionInTarget;
       for (; endIndex < targetStr.length(); endIndex++) {
         if (theCollation.equals(searchSubStr, 
                      targetStr.substring(positionInTarget,endIndex)))
         {
           break;
         }
       }

       searchTextEntry.select(positionInTarget, endIndex);
       setSearchMsg("Found Match: \"" 
                     + targetStr.substring(positionInTarget,endIndex)
                     + "\" at index " + String.valueOf(positionInTarget));
    }

    /**
     * This function is used by handleFindNext();
     * Given a collation strength and two collation orders, resulting from 
     * calling CollationElementIterator.next(), determine if the two
     * collation orders are equivalent for the given strength.
     */
    private boolean collationOrdersEqual(int order1, int order2, int strength)
    {
       if (order1 == order2) return true;

       if (CollationElementIterator.primaryOrder(order1)
           != CollationElementIterator.primaryOrder(order2)) return false;
       if (strength == Collator.PRIMARY) return true;

       if (CollationElementIterator.secondaryOrder(order1)
           != CollationElementIterator.secondaryOrder(order2)) return false;
       if (strength == Collator.SECONDARY) return true;

       return false;
    }


    //------------------------------------------------------------
    // private
    //------------------------------------------------------------
    /**
     * This function is called by handleSortAscend and handleSortDescend
     * to seperate each line of the textArea into individual strings,
     * and add them to the vector to be sorted.
     */
    private void InitializeListVector()
    {
        textList.removeAllElements();

        String composite = getFixedString(textentry);

        String substr;

        int startOffset = 0;
        int endOffset = 0;
        for (; endOffset < composite.length(); endOffset++)
        {
            char ch = composite.charAt(endOffset);
            if (ch == '\n' || ch == '\r')
            {
                if (endOffset > startOffset)
                {
                    substr = composite.substring(startOffset, endOffset);
                    textList.addElement(substr);
                }
                startOffset = endOffset + 1;
            }
        }

        if (startOffset < composite.length())
        {
            substr = composite.substring(startOffset, composite.length());
            textList.addElement(substr);
        }

    }


    /**
     * This function is called by handleSortAscend and handleSortDescend
     * to reset the text area based on the sort results stored as a vector
     * of substrings.
     */
    private void resetTextArea()
    {
        String composite = new String();

        int i = 0;
        for (; i < textList.size() - 1; i++)
        {
            composite = composite.concat((String) textList.elementAt(i));
            composite = composite.concat("\n");
        }
        //don't add the \n to the end
           composite = composite.concat((String) textList.elementAt(i));

        textentry.setText(composite);
        textList.removeAllElements();
    }



    private void errorText(String s) {
        errorMsg.setText(s);
        errorMsg.setVisible(true);
    }

    private static StringBuffer oneChar = new StringBuffer(7);
    private String makeDisplayString(char ch) {
        oneChar.setLength(0);
        if (ch < 0x0020 ||
            (ch > 0x007D && ch < 0x00A0) ||
            ch > 0x00FF) {
            String temp = Integer.toString((int)ch,16).toUpperCase();
            oneChar.append(temp);
            if (temp.length() < 4)
                oneChar.append(zeros.substring(0,4-temp.length()));
        } else {
            oneChar.append(ch);
        }
        return oneChar.toString();
    }

     /**
    Changes a string into a representation that can be pasted into a program.
    Uses \ t, \ uxxxx, etc. to display characters outside of Latin1.
    Others are themselves.
    */
    private String createUnicodeString(String source) {
        StringBuffer result = new StringBuffer(source.length());
        int len = 0;
        int i = 0;
        while (i < source.length()) {
            char ch = source.charAt(i);
            switch(ch) {
            case '\'':
                int mytemp = i;
                int n = 0;
                result.append(ch);
                ch = source.charAt(++i);
                while((ch != '\'') && (i < source.length() -1))
                {
                    String inquote = makeDisplayString(ch);
                    n++; i++;
                    if (inquote.length() > 1) {
                        result.append("\\u");
                        result.append(inquote);
                        len += 6;
                    } else {
                        result.append(inquote);
                        len++;
                    }
                    ch = source.charAt(i);
                }
                if (n == 0) {
                    result.append('\'');
                    n++; i++;
                    ch = source.charAt(i);
                }
                result.append(ch);
                len += n;
                break;
            case ';': case '=':
                if (len > 15)
                {
                    result.append('\n');
                    len = 0;
                }
                result.append(' ');
                result.append(ch);
                len += 2;
                break;
            case ',':
                result.append(' ');
                result.append(ch);
                result.append(' ');
                len += 3;
                break;
            case '<': case '&':
                result.append('\n');
                result.append(ch);
                result.append(' ');
                len = 0;
                len += 2;
                break;

            case '\n':
                break;
            default:
                if( isSpecialChar(ch) ) {
                    String dspString = makeDisplayString(ch);
                    if (dspString.length() > 1) {
                        result.append("'\\u");
                        result.append(dspString);
                        result.append("'");
                        len += 8;
                    } else if( ch != '\u0020' ){
                        result.append("'");
                        result.append(dspString);
                        result.append("'");
                        len += 3;
                    }
                }
                else {
                    String dspString = makeDisplayString(ch);
                    if (dspString.length() > 1) {
                        result.append("\\u");
                        result.append(dspString);
                        len += 6;
                    } else {
                        result.append(dspString);
                        len++;
                    }
                }
                break;
            }
            i++;
        }
        return result.toString();
    }

    private boolean isSpecialChar(char ch) {
        return (((ch <= '\u002F') && (ch >= '\u0020')) ||
                (ch == '\u003A') ||
                ((ch <= '\u0060') && (ch >= '\u005B')) ||
                ((ch <= '\u007E') && (ch >= '\u007B')));
    }

    private static final String zeros = "0000";

    private String convertStringToRules(String source)
    {
    StringBuffer result = new StringBuffer(source.length());
    for (int i = 0; i < source.length(); ++i) {
        //hack around TextArea bug
        char ch = (char) (source.charAt(i) & 0xFF);
        switch(ch) {
        case '\n':
            break;
        case '\'':
          if ( (i+6 < source.length()) && (source.charAt(i+2) == 'u') )
          {
            String temp = source.substring(i+3,i+7);
            if (temp.equals("005c")) {
                //have to handle backslash differently since 
                //it is a special character in Java
                result.append("'\\u005c'");
            } else
                result.append((char)(Integer.parseInt(temp,16)));
            i = i + 7;
           }
           else result.append(ch);

            break;
        case '\\':
          if ( (i+5 < source.length()) && (source.charAt(i+1) == 'u') )
          {
            String temp = source.substring(i+2,i+6);
            result.append((char)(Integer.parseInt(temp,16)));
            i = i + 6;
           }
           else result.append(ch);

            break;
        default:
            result.append(ch);
            break;
        }
    }
    return result.toString();
    }

    // HSYS : fix
    private static int MAX_COLLATIONS = 12;
    private int untitledIndex = 1;
    private static final int FIELD_COLUMNS = 45;
    private static final Font editFont = new Font("TimesRoman",Font.PLAIN,18);
    private static final Font ruleFont = new Font("TimesRoman",Font.BOLD,14);

    private static final boolean DEBUG = false;

    private TextArea textentry = new TextArea(13, 15);
    private TextArea ruleEntry = new TextArea(10, 30);
    private Vector textList = new Vector(15, 10);

    private Choice localeChoice;

    private static final String no_decomposition = "None";
    private static final String canonical_decomposition = "Canonical";
    private static final String full_decomposition = "Full";
    private Choice decompChoice;

    private static final String tertiary = "Tertiary - a vs. A";
    private static final String secondary = "Secondary - a vs. \u00E0";
    private static final String primary = "Primary - a vs. b";
    private Choice strengthChoice;

    private Locale theLocale = null;
    private Collator theCollation = null;

    private Locale[] locales;
    private Collator[] collations;
    private String[] ruleStrings = new String[MAX_COLLATIONS];
    private Label errorMsg = new Label("", Label.LEFT);

    CheckboxGroup checkboxes;
    Checkbox sortAscending;
    Checkbox sortDescending;
    Checkbox noSort;

    Button collateRulesButton;
    TextField collationName;

    private DemoApplet applet;

    private TabPanel tabsAndCards;
    private Panel sortPanel;
    private Panel searchPanel;
    private Panel customPanel;

    private boolean isCustomizing = false;

    //SEARCH DEMO
    private TextField searchString = new TextField(12);
    private TextArea searchTextEntry = new TextArea(10,30);
    private Label searchMsg = new Label("", Label.LEFT);
    private Checkbox searchByWord = new Checkbox("Match Whole Word Only");
    private BreakIterator wordIterator = BreakIterator.getWordInstance();
}

