javauis/lcdui_qt/src/javax/microedition/lcdui/StringItemLayouter.java
branchRCL_3
changeset 65 ae942d28ec0e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javauis/lcdui_qt/src/javax/microedition/lcdui/StringItemLayouter.java	Tue Aug 31 15:09:22 2010 +0300
@@ -0,0 +1,789 @@
+/*
+* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description:
+*
+*/
+package javax.microedition.lcdui;
+
+import java.util.Vector;
+
+import org.eclipse.ercp.swt.mobile.HyperLink;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+
+/**
+ * Layouter for StringItems.
+ */
+class StringItemLayouter extends ItemLayouter
+{
+
+    /**
+     * Key name for hyperlink filter.
+     */
+    private static final String HYPERLINK_FILTER = "hyperlink";
+
+    private static final String LINE_CUT_INDICATOR = "...";
+
+    private HyperLinkListener hLinkListener = new HyperLinkListener();
+
+    /**
+     * Constructor.
+     *
+     * @param aFormLayouter FormLayouter used for layouting.
+     */
+    StringItemLayouter(FormLayouter aFormLayouter)
+    {
+        super(aFormLayouter);
+    }
+
+    /**
+     * Creates the eSWT control. This method creates only StringItems of type
+     * BUTTON and those items must have at least one command. Otherwise
+     * StringItem is displayed as PLAIN and that case is handled elsewhere.
+     *
+     * @param parent where to create
+     * @param item on which it is based. Must be StringItem of type BUTTON and
+     *            at least one command in it.
+     * @return Control
+     */
+    Control eswtGetControl(Composite parent, Item item)
+    {
+        StringItem stringItem = (StringItem) item;
+        if(stringItem.getAppearanceMode() == Item.BUTTON
+                && item.getNumCommands() > 0)
+        {
+            Button button = new Button(parent, SWT.PUSH);
+            button.setFont(Font.getESWTFont(stringItem.getFont()));
+            int areaWidth = 0;
+            String line = stringItem.getText();
+            if((areaWidth = item.getLockedPreferredWidth()) == -1)
+            {
+                areaWidth = getMaximumItemWidth(item);
+            }
+            line = truncateIfNewLine(line);
+            if(stringItem.getFont().stringWidth(line) > areaWidth)
+            {
+                // Wrap string to find out how many lines it would take:
+                Vector strings = StringWrapper.wrapString(line,
+                                 stringItem.getFont(), true, areaWidth, areaWidth);
+                if(strings != null && strings.size() > 1)
+                {
+
+                    line = (String) strings.elementAt(0);
+                    line = line.substring(0, line.length())
+                           + LINE_CUT_INDICATOR;
+                }
+            }
+            button.setText(line);
+            return button;
+        }
+        return null;
+    }
+
+    /**
+     * Add listeners to Layouter specific control.
+     */
+    void eswtAddSpecificListeners(Item item, Control control)
+    {
+        super.eswtAddSpecificListeners(item, control);
+        Logger.method(this, "eswtAddSpecificListener");
+    }
+
+    /**
+     * Remove listeners from Layouter specific control.
+     */
+    void eswtRemoveSpecificListeners(Item item, Control control)
+    {
+        super.eswtRemoveSpecificListeners(item, control);
+        Logger.method(this, "eswtRemoveSpecificListener");
+    }
+
+    /**
+     * Returns true if this eSWT control is Layouter specific.
+     */
+    boolean eswtIsSpecificControl(Item item, Control control)
+    {
+        if(item.getNumCommands() > 0)
+        {
+            int am = ((StringItem) item).getAppearanceMode();
+            if(am == Item.BUTTON)
+            {
+                if(control instanceof Button)
+                {
+                    return true;
+                }
+                return false;
+            }
+            else if(am == Item.HYPERLINK)
+            {
+                return true;
+            }
+        }
+        // Item is plain:
+        return true;
+    }
+
+    /**
+     * Updates the values of StringItem.
+     */
+    void eswtUpdateItem(Item item, Control control, int reason, Object param)
+    {
+        // No implementation needed.
+    }
+
+    /**
+     * Layout Item in a row.
+     *
+     * @param row current Row
+     * @param item Item to layout
+     */
+    void eswtLayoutItem(Row row, Item item)
+    {
+        StringItem stringItem = (StringItem) item;
+        if(stringItem.getNumCommands() > 0)
+        {
+            if(stringItem.getAppearanceMode() == StringItem.BUTTON)
+            {
+                // BUTTON
+                LayoutObject lo = formLayouter.getLayoutObject(item);
+                formLayouter.eswtAddNewLayoutObject(lo == null ? 
+					new LayoutObject(item, eswtGetCaptionedControl(item)) : lo);
+            }
+            else
+            {
+                // HYPERLINK
+                eswtLayoutPlainStringItem(row, stringItem, true);
+                if(item.isFocused())
+                {
+                    // Highlight hyperlink if it is focused:
+                    eswtHighlightHyperlinkItem(item, true);
+                }
+            }
+        }
+        else
+        {
+            // PLAIN
+            eswtLayoutPlainStringItem(row, stringItem, false);
+        }
+    }
+
+    /**
+     * Layout StringItem in a row.
+     *
+     * @param row current Row
+     * @param item StringItem to layout
+     * @param isHyperlink If true, StringItem of type Hyperlink is created.
+     *            Otherwise creates plain StringItem.
+     */
+    private void eswtLayoutPlainStringItem(Row row, StringItem item,
+                                           boolean isHyperlink)
+    {
+        if(item.isSizeLocked())
+        {
+            eswtLayoutLockedPlainStringItem(item, isHyperlink);
+            return;
+        }
+
+        String label = item.getLabel();
+
+        Vector strings = StringWrapper.wrapString(item.getText(),
+                         item.getFont(), formLayouter.getForm().getLeftRightLanguage(),
+                         row.getRowWidth(), row.getFreeSpace());
+
+        if(strings != null)
+        {
+            for(int i = 0; i < strings.size(); i++)
+            {
+                // create primitive StringItem
+                Control control = eswtCreateLabeledPrimitiveStringItem(
+                                      formLayouter.getForm().getFormComposite(),
+                                      (String) strings.elementAt(i),
+                                      label, item.getFont(), isHyperlink,
+                                      getMaximumItemWidth(item));
+
+                // because add label to first primitive only:
+                label = null;
+
+                // layoutObject which represent primitive StringItem
+                LayoutObject lo = formLayouter.getLayoutObject(item);
+                formLayouter.eswtAddNewLayoutObject((lo == null ?
+                    new LayoutObject(item, control) : lo), i != 0);
+            }
+        }
+    }
+
+    /**
+     * Layout StringItem which height and/or width is locked.
+     * This kind of stringitems are always layouted to rectangular
+     * area. If the area is not large enough to display all content
+     * of the StringItem then the method adds three dots to the end of the
+     * last visible line.
+     *
+     * @param item StringItem to layout
+     * @param isHyperlink If true, StringItem of type Hyperlink is
+     *      created. Otherwise creates plain StringItem.
+     */
+    private void eswtLayoutLockedPlainStringItem(StringItem item,
+            boolean isHyperlink)
+    {
+
+        int width = item.getPreferredWidth();
+        int height = item.getPreferredHeight();
+
+        Vector strings = StringWrapper.wrapString(item.getText(),
+                         item.getFont(), formLayouter.getForm().getLeftRightLanguage(),
+                         width, width);
+
+        // Create composite which will contain the lines of locked stringitem:
+        Composite comp = new Composite(formComposite, SWT.NONE);
+        comp.setSize(width, height);
+
+        if(strings != null)
+        {
+            // layout labels in vertical rows
+            comp.setLayout(new RowLayout(SWT.VERTICAL));
+
+            // Calculate line height:
+            String label = item.getLabel();
+            int lineHeight = StringItemLayouter.calculateMinimumBounds(item).y;
+
+            // Calculate the index of last visible line:
+            int spaceForRows = height;
+            if(item.hasLabel())
+            {
+                spaceForRows -= getLabelSize(LINE_CUT_INDICATOR).y;
+            }
+            int indexOfLastVisibleRow =
+                Math.max(spaceForRows / lineHeight - 1, 0);
+
+            boolean lastLineReached = false;
+            for(int i = 0; !lastLineReached && i < strings.size(); i++)
+            {
+                String line = (String) strings.elementAt(i);
+
+                // If there are rows left after this row but no more space ...
+                if((i < strings.size() - 1) && (i == indexOfLastVisibleRow))
+                {
+                    // ... display three dots at the end of the line:
+                    if(line.length() >= LINE_CUT_INDICATOR.length())
+                    {
+                        line = line.substring(0, line.length()
+                                              - LINE_CUT_INDICATOR.length())
+                               + LINE_CUT_INDICATOR;
+                    }
+                    else
+                    {
+                        line = LINE_CUT_INDICATOR;
+                    }
+                    lastLineReached = true;
+                }
+
+                // create primitive StringItem
+                eswtCreateLabeledPrimitiveStringItem(comp, line, label, item
+                                                     .getFont(), isHyperlink,width);
+
+                // because add label to first primitive only:
+                label = null;
+            }
+        }
+		LayoutObject lo = formLayouter.getLayoutObject(item);
+        formLayouter.eswtAddNewLayoutObject((lo == null ? new LayoutObject(item, comp) : lo), false);
+    }
+
+    /**
+     * Create labeled 'primitive' one-row StringItems.
+     *
+     * @param parent parent used to layout eSWT control.
+     * @param txt text for that primitive StringItem.
+     * @param label header of StringItem.
+     * @param font Item's font.
+     * @param hyperlink If true, StringItem of type Hyperlink is created.
+     *            Otherwise creates plain StringItem.
+     * @return Control created.
+     */
+    private Control eswtCreateLabeledPrimitiveStringItem(Composite parent,
+            String txt, String label, Font font, boolean hyperlink,
+            int availableWidth)
+    {
+        if(label == null || label.length()<1)
+        {
+            Control text = eswtCreatePrimitiveStringItem(parent, txt, font, hyperlink);
+            if(hyperlink)
+            {
+                ((HyperLink) text).addSelectionListener(hLinkListener);
+            }
+            return text;
+        }
+        else
+        {
+            Composite comp = new Composite(parent, SWT.NONE);
+
+            Label header = new Label(comp, SWT.WRAP);
+            header.setText(label);
+            header.pack();
+
+            int headerWidth = header.getBounds().width;
+            if(headerWidth > availableWidth)
+            {
+                Point size = header.computeSize(availableWidth,SWT.DEFAULT);
+                header.setBounds(0,0,size.x,size.y);
+                headerWidth = header.getBounds().width;
+            }
+
+            Control text = eswtCreatePrimitiveStringItem(comp, txt, font,
+                           hyperlink);
+            if(hyperlink)
+            {
+                ((HyperLink) text).addSelectionListener(hLinkListener);
+            }
+            int textWidth = text.getBounds().width;
+            int objectWidth = Math.max(textWidth, headerWidth);
+
+            header.setLocation(ItemLayouter.getXLocation(objectWidth,
+                               headerWidth, formLayouter.getLanguageSpecificLayoutDirective()), 0);
+
+            text.setLocation(ItemLayouter.getXLocation(objectWidth, textWidth,
+                             formLayouter.getLanguageSpecificLayoutDirective()),
+                             header.getBounds().height);
+
+            comp.pack();
+            return comp;
+        }
+    }
+
+    /**
+     * Create primitive StringItem.
+     *
+     * @param parent
+     * @param txt
+     * @param font
+     * @param isHyperlink
+     * @return
+     */
+    private static Control eswtCreatePrimitiveStringItem(Composite parent, String txt,
+            Font font, boolean isHyperlink)
+    {
+        Control text = null;
+        if(isHyperlink)
+        {
+            text = new HyperLink(parent, SWT.NONE, HyperLink.URL);
+            ((HyperLink) text).setText(txt);
+        }
+        else
+        {
+            text = new Label(parent, SWT.NONE);
+            ((Label) text).setText(txt);
+        }
+        text.setFont(Font.getESWTFont(font));
+        text.pack();
+        return text;
+    }
+
+    /**
+     * Returns the minimum area needed to display a StringItem with specified
+     * font.
+     *
+     * @param stringItem StringItem object
+     * @return Minimum area needed to display StringItem with specified Font.
+     */
+    static Point calculateMinimumBounds(final StringItem stringItem)
+    {
+        final Point minSize = new Point(0, 0);
+        ESWTUIThreadRunner.syncExec(new Runnable()
+        {
+            public void run()
+            {
+                Point size = null;
+                if(stringItem.getNumCommands() > 0)
+                {
+                    if(stringItem.getAppearanceMode() == StringItem.BUTTON)
+                    {
+                        // BUTTON:
+                        Button but = new Button(eswtGetStaticShell(), SWT.PUSH);
+                        but.setFont(Font.getESWTFont(stringItem.getFont()));
+                        but.setText(LINE_CUT_INDICATOR);
+                        but.pack();
+                        size = but.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+                        but.dispose();
+                        applyMinMargins(stringItem, size);
+                    }
+                    else
+                    {
+                        // HYPERLINK:
+                        HyperLink hl = new HyperLink(eswtGetStaticShell(),
+                                                     SWT.NONE, HyperLink.URL);
+                        hl.setFont(Font.getESWTFont(stringItem.getFont()));
+                        hl.setText(LINE_CUT_INDICATOR);
+                        hl.pack();
+                        size = hl.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+                        hl.dispose();
+                        if(stringItem.hasLabel())
+                        {
+                            Label header = new Label(eswtGetStaticShell(),SWT.NONE);
+                            header.setText(LINE_CUT_INDICATOR);
+                            header.pack();
+                            size.y += (header.computeSize(SWT.DEFAULT,SWT.DEFAULT)).y;
+                            header.dispose();
+                        }
+                    }
+                }
+                else
+                {
+                    Label text = new Label(eswtGetStaticShell(), SWT.NONE); // SWT.LEFT
+                    text.setFont(Font.getESWTFont(stringItem.getFont()));
+                    text.setText(LINE_CUT_INDICATOR);
+                    text.pack();
+                    size = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+                    text.dispose();
+                    if(stringItem.hasLabel())
+                    {
+                        Label header = new Label(eswtGetStaticShell(),SWT.NONE);
+                        header.setText(LINE_CUT_INDICATOR);
+                        header.pack();
+                        size.y += (header.computeSize(SWT.DEFAULT,SWT.DEFAULT)).y;
+                        header.dispose();
+                    }
+                }
+                minSize.x = size.x;
+                minSize.y = size.y;
+            }
+        });
+        return minSize;
+    }
+
+    /**
+     * Returns the preferred area needed to display an Item.
+     *
+     * @param item Item.
+     * @return Preferred size needed to display Item. x is width and y is
+     *         height.
+     */
+    static Point calculatePreferredBounds(Item item)
+    {
+        StringItem stringItem = (StringItem) item;
+        if(stringItem.getNumCommands() > 0)
+        {
+            if(stringItem.getAppearanceMode() == StringItem.BUTTON)
+            {
+                // BUTTON
+                return calculateButtonPreferredBounds(stringItem);
+            }
+            else
+            {
+                // HYPERLINK
+                return calculateStringItemPreferredBounds(stringItem, true);
+            }
+        }
+        else
+        {
+            // PLAIN
+            return calculateStringItemPreferredBounds(stringItem, false);
+        }
+    }
+
+    /**
+     * Calculates unlocked preferred size for StringItem which is displayed as
+     * button.
+     *
+     * @param item Item.
+     * @return Preferred size.
+     */
+    private static Point calculateButtonPreferredBounds(final StringItem item)
+    {
+        final Point prefSize = new Point(0, 0);
+        ESWTUIThreadRunner.syncExec(new Runnable()
+        {
+            public void run()
+            {
+                Button button = new Button(eswtGetStaticShell(), SWT.PUSH);
+                button.setFont(Font.getESWTFont(item.getFont()));
+
+                int areaWidth = 0;
+                String line = item.getText();
+                if((areaWidth = item.getLockedPreferredWidth()) == -1)
+                {
+                    areaWidth = getMaximumItemWidth(item);
+                }
+                line = truncateIfNewLine(line);
+                if(item.getFont().stringWidth(line) < areaWidth)
+                {
+                    areaWidth = SWT.DEFAULT;
+                }
+                button.setText(line);
+                button.pack();
+                Point size = button.computeSize(areaWidth, SWT.DEFAULT);
+                button.dispose();
+                prefSize.x = size.x;
+                prefSize.y = size.y;
+                applyPrefMargins(item, prefSize);
+            }
+        });
+        return prefSize;
+    }
+
+    /**
+     * Calculates preferred size for StringItem which is displayed as plain or
+     * as hyperlink. If there are many lines in item and item's width is
+     * unlocked, then the returned width is form's content area's width. If
+     * width is locked, then returned width is same as locked width and height
+     * grows so that text fits to item's area. Locked height is handled during
+     * layouting.
+     *
+     * @param item Item.
+     * @param isHyperlink True if item is hyperlink, false if plain.
+     * @return Preferred size.
+     */
+    private static Point calculateStringItemPreferredBounds(
+        final StringItem item, final boolean isHyperlink)
+    {
+        final Point preferredSize = new Point(0, 0);
+        ESWTUIThreadRunner.syncExec(new Runnable()
+        {
+            public void run()
+            {
+                // Find out the width of the area where the StringItem should
+                // be layouted:
+                int areaWidth = 0;
+                if(item.getLockedPreferredWidth() != -1)
+                {
+                    areaWidth = item.getLockedPreferredWidth();
+                }
+                else
+                {
+                    areaWidth = getMaximumItemWidth(item);
+                }
+
+                // Wrap string to find out how many lines it would take:
+                Vector strings = StringWrapper.wrapString(item.getText(),
+                                 item.getFont(), true, areaWidth, areaWidth);
+
+                if(strings != null && strings.size() > 0)
+                {
+                    // Calculate the size of the first line:
+                    Control ctrl = eswtCreatePrimitiveStringItem(
+                                       eswtGetStaticShell(),
+                                       (String) strings.elementAt(0),
+                                       item.getFont(),
+                                       isHyperlink);
+                    Point size = ctrl.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+                    ctrl.dispose();
+
+                    // If there are more than one line, then the width would
+                    // be form's content area width or locked width and the
+                    // height would be the height of a line times number of
+                    // lines:
+                    if(strings.size() > 1)
+                    {
+                        size.x = areaWidth;
+                        size.y *= strings.size();
+                    }
+                    if(item.hasLabel())
+                    {
+                        Label header = new Label(eswtGetStaticShell(),SWT.NONE);
+                        header.setText(item.getLabel());
+                        header.pack();
+                        size.y += (header.computeSize(areaWidth,SWT.DEFAULT)).y;
+                        header.dispose();
+                    }
+                    preferredSize.x = size.x;
+                    preferredSize.y = size.y;
+                }
+            }
+        });
+        return preferredSize;
+    }
+
+    /**
+     * Returns if this Item is hyperlink.
+     */
+    private boolean isHyperlink(Item item)
+    {
+        if(item.getNumCommands() > 0
+                && item instanceof StringItem
+                && (((StringItem) item).getAppearanceMode() != Item.BUTTON))
+        {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Called when item gains focus.
+     */
+    void eswtFocusGained(Item item, int dir)
+    {
+        super.eswtFocusGained(item, dir);
+        if(isHyperlink(item))
+        {
+            // Highlight hyperlink:
+            eswtHighlightHyperlinkItem(item, true);
+        }
+    }
+
+    /**
+     * Called when item losts focus.
+     */
+    void eswtFocusLost(Item item)
+    {
+        super.eswtFocusLost(item);
+        if(isHyperlink(item))
+        {
+            // Remove hyperlink highlighting:
+            eswtHighlightHyperlinkItem(item, false);
+        }
+    }
+
+    /**
+     * Set highlight on hyperlink.
+     *
+     * @param item highlight StringItem
+     * @param highlight enable or disable
+     */
+    private void eswtHighlightHyperlinkItem(Item item, boolean highlight)
+    {
+        LayoutObject lo = null;
+        Control c = null;
+        while((lo = formLayouter.getNextLayoutObjectOfItem(lo, item)) != null)
+        {
+            if((c = lo.getControl()) != null)
+            {
+                eswtHighlightHyperlink(c, highlight);
+            }
+        }
+    }
+
+    private static Color fgColor;
+    private static Color bgColor;
+
+    private static Color getFgColor()
+    {
+        if(fgColor == null)
+        {
+            fgColor = new Color(ESWTUIThreadRunner.getInstance().getDisplay(),
+                                0x99, 0x00, 0x00);
+            ESWTUIThreadRunner.ds.addObject(fgColor);
+        }
+        return fgColor;
+    }
+
+    private static Color getBgColor()
+    {
+        if(bgColor == null)
+        {
+            bgColor = new Color(ESWTUIThreadRunner.getInstance().getDisplay(),
+                                0xee, 0xee, 0xee);
+            ESWTUIThreadRunner.ds.addObject(bgColor);
+        }
+        return bgColor;
+    }
+
+    /**
+     * Highlights hyperlink. this should be called when hyperlink receives
+     * focus. Method adds highlighting to provided Control and all of its
+     * children.
+     *
+     * @param c Control where to start highlighting.
+     */
+    private void eswtHighlightHyperlink(Control c, boolean highlight)
+    {
+        // TODO: eSWT support required - hyperlink highlighting.
+        if(c instanceof Composite)
+        {
+            Control[] children = ((Composite) c).getChildren();
+            for(int i = 0; i < children.length; i++)
+            {
+                Control child = children[i];
+                if(child instanceof Composite)
+                {
+                    eswtHighlightHyperlink(child, highlight);
+                }
+            }
+        }
+        if(highlight)
+        {
+            c.setBackground(getBgColor());
+            c.setForeground(getFgColor());
+        }
+        else
+        {
+            c.setBackground(null);
+            c.setForeground(null);
+        }
+    }
+
+    /**
+     * Checks if there's new line character(s) in string. If one or more is
+     * found, truncates string so that the characters beginning from first
+     * newline character are replaced with three dots. Supported newline
+     * characters are:
+     * <ul>
+     * <li>CRLF (\r\n)
+     * <li>LF (\n)
+     * <li>CR (\r)
+     * <li>LS (\u2028)
+     * </ul>
+     *
+     * @param text String to be checked.
+     * @return truncated String if newline characters found. Otherwise returns
+     *         the string provided as parameter.
+     */
+    private static String truncateIfNewLine(String text)
+    {
+        int indexOfFirstNewLineChar = -1;
+        for(int i = 0; i < text.length(); i++)
+        {
+            if((text.charAt(i) == '\r')
+                    || (text.charAt(i) == '\n')
+                    || (text.charAt(i) == '\u2028'))
+            {
+                indexOfFirstNewLineChar = i;
+                break;
+            }
+        }
+        if(indexOfFirstNewLineChar != -1)
+        {
+            return text.substring(0, indexOfFirstNewLineChar)
+                   + LINE_CUT_INDICATOR;
+        }
+        return text;
+    }
+
+    /**
+     * Filter that blocks events if current selected item is a StringItem of
+     * type HYPERLINK (or plain with commands).
+     */
+    class HyperLinkListener implements SelectionListener
+    {
+
+        public void widgetDefaultSelected(SelectionEvent e)
+        {
+            Logger.method(this, "widgetDefaultSelected");
+        }
+
+        public void widgetSelected(SelectionEvent e)
+        {
+            Logger.method(this, "widgetSelected");
+            e.doit = false;
+            Item item = formLayouter.getCurrentSelectedItem();
+            item.callCommandAction(item.getMSKCommand());
+        }
+    }
+
+}