diff dwtx/ui/internal/forms/widgets/FormTextModel.d @ 75:5d489b9f966c

Fix continue porting
author Frank Benoit <benoit@tionex.de>
date Sat, 24 May 2008 05:11:16 +0200
parents
children 4ac9946b9fb5
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/ui/internal/forms/widgets/FormTextModel.d	Sat May 24 05:11:16 2008 +0200
@@ -0,0 +1,782 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.ui.internal.forms.widgets.FormTextModel;
+
+import dwtx.ui.internal.forms.widgets.Paragraph;
+import dwtx.ui.internal.forms.widgets.IFocusSelectable;
+import dwtx.ui.internal.forms.widgets.ParagraphSegment;
+import dwtx.ui.internal.forms.widgets.IHyperlinkSegment;
+import dwtx.ui.internal.forms.widgets.ControlSegment;
+import dwtx.ui.internal.forms.widgets.ImageSegment;
+import dwtx.ui.internal.forms.widgets.ObjectSegment;
+
+import dwt.DWT;
+import dwtx.ui.forms.HyperlinkSettings;
+
+import dwt.dwthelper.utils;
+import dwt.dwthelper.InputStream;
+pragma(msg,"FIXME temp type "~__FILE__);
+public class FormTextModel {
+    public static const String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$
+    public this() ;
+    public Paragraph[] getParagraphs() ;
+    public String getAccessibleText() ;
+    public void parseTaggedText(String taggedText, bool expandURLs) ;
+    public void parseInputStream(InputStream is_, bool expandURLs) ;
+    public void parseRegularText(String regularText, bool convertURLs) ;
+    public HyperlinkSettings getHyperlinkSettings() ;
+    public void setHyperlinkSettings(HyperlinkSettings settings) ;
+    IFocusSelectable[] getFocusSelectableSegments() ;
+    public IHyperlinkSegment getHyperlink(int index) ;
+    public IHyperlinkSegment findHyperlinkAt(int x, int y) ;
+    public int getHyperlinkCount() ;
+    public int indexOf(IHyperlinkSegment link) ;
+    public ParagraphSegment findSegmentAt(int x, int y) ;
+    public void clearCache(String fontId) ;
+    public IFocusSelectable getSelectedSegment() ;
+    public int getSelectedSegmentIndex() ;
+    public bool linkExists(IHyperlinkSegment link) ;
+    public bool traverseFocusSelectableObjects(bool next) ;
+    public IFocusSelectable getNextFocusSegment(bool next) ;
+    public bool restoreSavedLink() ;
+    public void selectLink(IHyperlinkSegment link) ;
+    public void select(IFocusSelectable selectable) ;
+    public bool hasFocusSegments() ;
+    public void dispose() ;
+    public bool isWhitespaceNormalized() ;
+    public void setWhitespaceNormalized(bool whitespaceNormalized) ;
+}
+
+/++
+static import tango.text.xml.Document;
+import tango.util.collection.ArraySeq;
+public class FormTextModel {
+//     private static const DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+//             .newInstance();
+
+    alias tango.text.xml.Document.Document!(char) Document;
+    alias Document.NodeImpl Node;
+
+    private bool whitespaceNormalized = true;
+
+    private alias ArraySeq!(Paragraph) TArraySeqParagraph;
+    private TArraySeqParagraph paragraphs;
+
+    private IFocusSelectable[] selectableSegments;
+
+    private int selectedSegmentIndex = -1;
+
+    private int savedSelectedLinkIndex = -1;
+
+    private HyperlinkSettings hyperlinkSettings;
+
+    public static const String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$
+
+    //private static final int TEXT_ONLY_LINK = 1;
+
+    //private static final int IMG_ONLY_LINK = 2;
+
+    //private static final int TEXT_AND_IMAGES_LINK = 3;
+
+    public this() {
+        reset();
+    }
+
+    /*
+     * @see ITextModel#getParagraphs()
+     */
+    public Paragraph[] getParagraphs() {
+        if (paragraphs is null)
+            return new Paragraph[0];
+        return paragraphs
+                .toArray();
+    }
+
+    public String getAccessibleText() {
+        if (paragraphs is null)
+            return ""; //$NON-NLS-1$
+        StringBuffer sbuf = new StringBuffer();
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph paragraph = cast(Paragraph) paragraphs.get(i);
+            String text = paragraph.getAccessibleText();
+            sbuf.append(text);
+        }
+        return sbuf.toString();
+    }
+
+    /*
+     * @see ITextModel#parse(String)
+     */
+    public void parseTaggedText(String taggedText, bool expandURLs) {
+        if (taggedText is null) {
+            reset();
+            return;
+        }
+        try {
+            InputStream stream = new ByteArrayInputStream(taggedText
+                    .getBytes("UTF8")); //$NON-NLS-1$
+            parseInputStream(stream, expandURLs);
+        } catch (UnsupportedEncodingException e) {
+            DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT, e);
+        }
+    }
+
+    public void parseInputStream(InputStream is_, bool expandURLs) {
+
+        documentBuilderFactory.setNamespaceAware(true);
+        documentBuilderFactory.setIgnoringComments(true);
+
+        reset();
+        try {
+            DocumentBuilder parser = documentBuilderFactory
+                    .newDocumentBuilder();
+            InputSource source = new InputSource(is_);
+            Document doc = parser.parse(source);
+            processDocument(doc, expandURLs);
+        } catch (ParserConfigurationException e) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, e);
+        } catch (SAXException e) {
+            DWT.error(DWT.ERROR_INVALID_ARGUMENT, e);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    private void processDocument(Document doc, bool expandURLs) {
+        Node root = doc.getDocumentElement();
+        NodeList children = root.getChildNodes();
+        processSubnodes(paragraphs, children, expandURLs);
+    }
+
+    private void processSubnodes(TArraySeqParagraph plist, NodeList children, bool expandURLs) {
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() is Node.TEXT_NODE) {
+                // Make an implicit paragraph
+                String text = getSingleNodeText(child);
+                if (text !is null && !isIgnorableWhiteSpace(text, true)) {
+                    Paragraph p = new Paragraph(true);
+                    p.parseRegularText(text, expandURLs, true,
+                            getHyperlinkSettings(), null);
+                    plist.add(p);
+                }
+            } else if (child.getNodeType() is Node.ELEMENT_NODE) {
+                String tag = child.getNodeName().toLowerCase();
+                if (tag.equals("p")) { //$NON-NLS-1$
+                    Paragraph p = processParagraph(child, expandURLs);
+                    if (p !is null)
+                        plist.add(p);
+                } else if (tag.equals("li")) { //$NON-NLS-1$
+                    Paragraph p = processListItem(child, expandURLs);
+                    if (p !is null)
+                        plist.add(p);
+                }
+            }
+        }
+    }
+
+    private Paragraph processParagraph(Node paragraph, bool expandURLs) {
+        NodeList children = paragraph.getChildNodes();
+        NamedNodeMap atts = paragraph.getAttributes();
+        Node addSpaceAtt = atts.getNamedItem("addVerticalSpace"); //$NON-NLS-1$
+        bool addSpace = true;
+
+        if (addSpaceAtt is null)
+            addSpaceAtt = atts.getNamedItem("vspace"); //$NON-NLS-1$
+
+        if (addSpaceAtt !is null) {
+            String value = addSpaceAtt.getNodeValue();
+            addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$
+        }
+        Paragraph p = new Paragraph(addSpace);
+
+        processSegments(p, children, expandURLs);
+        return p;
+    }
+
+    private Paragraph processListItem(Node listItem, bool expandURLs) {
+        NodeList children = listItem.getChildNodes();
+        NamedNodeMap atts = listItem.getAttributes();
+        Node addSpaceAtt = atts.getNamedItem("addVerticalSpace");//$NON-NLS-1$
+        Node styleAtt = atts.getNamedItem("style");//$NON-NLS-1$
+        Node valueAtt = atts.getNamedItem("value");//$NON-NLS-1$
+        Node indentAtt = atts.getNamedItem("indent");//$NON-NLS-1$
+        Node bindentAtt = atts.getNamedItem("bindent");//$NON-NLS-1$
+        int style = BulletParagraph.CIRCLE;
+        int indent = -1;
+        int bindent = -1;
+        String text = null;
+        bool addSpace = true;
+
+        if (addSpaceAtt !is null) {
+            String value = addSpaceAtt.getNodeValue();
+            addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$
+        }
+        if (styleAtt !is null) {
+            String value = styleAtt.getNodeValue();
+            if (value.equalsIgnoreCase("text")) { //$NON-NLS-1$
+                style = BulletParagraph.TEXT;
+            } else if (value.equalsIgnoreCase("image")) { //$NON-NLS-1$
+                style = BulletParagraph.IMAGE;
+            } else if (value.equalsIgnoreCase("bullet")) { //$NON-NLS-1$
+                style = BulletParagraph.CIRCLE;
+            }
+        }
+        if (valueAtt !is null) {
+            text = valueAtt.getNodeValue();
+            if (style is BulletParagraph.IMAGE)
+                text = "i." + text; //$NON-NLS-1$
+        }
+        if (indentAtt !is null) {
+            String value = indentAtt.getNodeValue();
+            try {
+                indent = Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+            }
+        }
+        if (bindentAtt !is null) {
+            String value = bindentAtt.getNodeValue();
+            try {
+                bindent = Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+            }
+        }
+
+        BulletParagraph p = new BulletParagraph(addSpace);
+        p.setIndent(indent);
+        p.setBulletIndent(bindent);
+        p.setBulletStyle(style);
+        p.setBulletText(text);
+
+        processSegments(p, children, expandURLs);
+        return p;
+    }
+
+    private void processSegments(Paragraph p, NodeList children,
+            bool expandURLs) {
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            ParagraphSegment segment = null;
+
+            if (child.getNodeType() is Node.TEXT_NODE) {
+                String value = getSingleNodeText(child);
+
+                if (value !is null && !isIgnorableWhiteSpace(value, false)) {
+                    p.parseRegularText(value, expandURLs, true,
+                            getHyperlinkSettings(), null);
+                }
+            } else if (child.getNodeType() is Node.ELEMENT_NODE) {
+                String name = child.getNodeName();
+                if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$
+                    segment = processImageSegment(child);
+                } else if (name.equalsIgnoreCase("a")) { //$NON-NLS-1$
+                    segment = processHyperlinkSegment(child,
+                            getHyperlinkSettings());
+                } else if (name.equalsIgnoreCase("span")) { //$NON-NLS-1$
+                    processTextSegment(p, expandURLs, child);
+                } else if (name.equalsIgnoreCase("b")) { //$NON-NLS-1$
+                    String text = getNodeText(child);
+                    String fontId = BOLD_FONT_ID;
+                    p.parseRegularText(text, expandURLs, true,
+                            getHyperlinkSettings(), fontId);
+                } else if (name.equalsIgnoreCase("br")) { //$NON-NLS-1$
+                    segment = new BreakSegment();
+                } else if (name.equalsIgnoreCase("control")) { //$NON-NLS-1$
+                    segment = processControlSegment(child);
+                }
+            }
+            if (segment !is null) {
+                p.addSegment(segment);
+            }
+        }
+    }
+
+    private bool isIgnorableWhiteSpace(String text, bool ignoreSpaces) {
+        for (int i = 0; i < text.length(); i++) {
+            char c = text.charAt(i);
+            if (ignoreSpaces && c is ' ')
+                continue;
+            if (c is '\n' || c is '\r' || c is '\f')
+                continue;
+            return false;
+        }
+        return true;
+    }
+
+    private ImageSegment processImageSegment(Node image) {
+        ImageSegment segment = new ImageSegment();
+        processObjectSegment(segment, image, "i."); //$NON-NLS-1$
+        return segment;
+    }
+
+    private ControlSegment processControlSegment(Node control) {
+        ControlSegment segment = new ControlSegment();
+        processObjectSegment(segment, control, "o."); //$NON-NLS-1$
+        Node fill = control.getAttributes().getNamedItem("fill"); //$NON-NLS-1$
+        if (fill !is null) {
+            String value = fill.getNodeValue();
+            bool doFill = value.equalsIgnoreCase("true"); //$NON-NLS-1$
+            segment.setFill(doFill);
+        }
+        try {
+            Node width = control.getAttributes().getNamedItem("width"); //$NON-NLS-1$
+            if (width !is null) {
+                String value = width.getNodeValue();
+                int doWidth = Integer.parseInt(value);
+                segment.setWidth(doWidth);
+            }
+            Node height = control.getAttributes().getNamedItem("height"); //$NON-NLS-1$
+            if (height !is null) {
+                String value = height.getNodeValue();
+                int doHeight = Integer.parseInt(value);
+                segment.setHeight(doHeight);
+            }
+        }
+        catch (NumberFormatException e) {
+            // ignore invalid width or height
+        }
+        return segment;
+    }
+
+    private void processObjectSegment(ObjectSegment segment, Node object, String prefix) {
+        NamedNodeMap atts = object.getAttributes();
+        Node id = atts.getNamedItem("href"); //$NON-NLS-1$
+        Node align_ = atts.getNamedItem("align"); //$NON-NLS-1$
+        if (id !is null) {
+            String value = id.getNodeValue();
+            segment.setObjectId(prefix + value);
+        }
+        if (align_ !is null) {
+            String value = align_.getNodeValue().toLowerCase();
+            if (value.equals("top")) //$NON-NLS-1$
+                segment.setVerticalAlignment(ImageSegment.TOP);
+            else if (value.equals("middle")) //$NON-NLS-1$
+                segment.setVerticalAlignment(ImageSegment.MIDDLE);
+            else if (value.equals("bottom")) //$NON-NLS-1$
+                segment.setVerticalAlignment(ImageSegment.BOTTOM);
+        }
+    }
+
+    private void appendText(String value, StringBuffer buf, int[] spaceCounter) {
+        if (!whitespaceNormalized)
+            buf.append(value);
+        else {
+            for (int j = 0; j < value.length(); j++) {
+                char c = value.charAt(j);
+                if (c is ' ' || c is '\t') {
+                    // space
+                    if (++spaceCounter[0] is 1) {
+                        buf.append(c);
+                    }
+                } else if (c is '\n' || c is '\r' || c is '\f') {
+                    // new line
+                    if (++spaceCounter[0] is 1) {
+                        buf.append(' ');
+                    }
+                } else {
+                    // other characters
+                    spaceCounter[0] = 0;
+                    buf.append(c);
+                }
+            }
+        }
+    }
+
+    private String getNormalizedText(String text) {
+        int[] spaceCounter = new int[1];
+        StringBuffer buf = new StringBuffer();
+
+        if (text is null)
+            return null;
+        appendText(text, buf, spaceCounter);
+        return buf.toString();
+    }
+
+    private String getSingleNodeText(Node node) {
+        return getNormalizedText(node.getNodeValue());
+    }
+
+    private String getNodeText(Node node) {
+        NodeList children = node.getChildNodes();
+        StringBuffer buf = new StringBuffer();
+        int[] spaceCounter = new int[1];
+
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() is Node.TEXT_NODE) {
+                String value = child.getNodeValue();
+                appendText(value, buf, spaceCounter);
+            }
+        }
+        return buf.toString().trim();
+    }
+
+    private ParagraphSegment processHyperlinkSegment(Node link,
+            HyperlinkSettings settings) {
+        NamedNodeMap atts = link.getAttributes();
+        String href = null;
+        bool wrapAllowed = true;
+        String boldFontId = null;
+
+        Node hrefAtt = atts.getNamedItem("href"); //$NON-NLS-1$
+        if (hrefAtt !is null) {
+            href = hrefAtt.getNodeValue();
+        }
+        Node boldAtt = atts.getNamedItem("bold"); //$NON-NLS-1$
+        if (boldAtt !is null) {
+            boldFontId = BOLD_FONT_ID;
+        }
+        Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$
+        if (nowrap !is null) {
+            String value = nowrap.getNodeValue();
+            if (value !is null && value.equalsIgnoreCase("true")) //$NON-NLS-1$
+                wrapAllowed = false;
+        }
+        Object status = checkChildren(link);
+        if ( auto child = cast(Node)status ) {
+            ImageHyperlinkSegment segment = new ImageHyperlinkSegment();
+            segment.setHref(href);
+            segment.setWordWrapAllowed(wrapAllowed);
+            Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$
+            if (alt !is null)
+                segment.setTooltipText(alt.getNodeValue());
+            Node text = child.getAttributes().getNamedItem("text"); //$NON-NLS-1$
+            if (text !is null)
+                segment.setText(text.getNodeValue());
+            processObjectSegment(segment, child, "i."); //$NON-NLS-1$
+            return segment;
+        }  else if ( auto textObj = cast(ArrayWrapperString)status ) {
+            String text = textObj.array;
+            TextHyperlinkSegment segment = new TextHyperlinkSegment(text,
+                    settings, null);
+            segment.setHref(href);
+            segment.setFontId(boldFontId);
+            Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$
+            if (alt !is null)
+                segment.setTooltipText(alt.getNodeValue());
+            segment.setWordWrapAllowed(wrapAllowed);
+            return segment;
+        } else {
+            AggregateHyperlinkSegment parent = new AggregateHyperlinkSegment();
+            parent.setHref(href);
+            NodeList children = link.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (child.getNodeType() is Node.TEXT_NODE) {
+                    String value = child.getNodeValue();
+                    TextHyperlinkSegment ts = new TextHyperlinkSegment(
+                            getNormalizedText(value), settings, null);
+                    Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$
+                    if (alt !is null)
+                        ts.setTooltipText(alt.getNodeValue());
+                    ts.setWordWrapAllowed(wrapAllowed);
+                    parent.add(ts);
+                } else if (child.getNodeType() is Node.ELEMENT_NODE) {
+                    String name = child.getNodeName();
+                    if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$
+                        ImageHyperlinkSegment is_ = new ImageHyperlinkSegment();
+                        processObjectSegment(is_, child, "i."); //$NON-NLS-1$
+                        Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$
+                        if (alt !is null)
+                            is_.setTooltipText(alt.getNodeValue());
+                        parent.add(is_);
+                        is_.setWordWrapAllowed(wrapAllowed);
+                    }
+                }
+            }
+            return parent;
+        }
+    }
+
+    private Object checkChildren(Node node) {
+        bool text = false;
+        Node imgNode = null;
+        //int status = 0;
+
+        NodeList children = node.getChildNodes();
+        for (int i = 0; i < children.getLength(); i++) {
+            Node child = children.item(i);
+            if (child.getNodeType() is Node.TEXT_NODE)
+                text = true;
+            else if (child.getNodeType() is Node.ELEMENT_NODE
+                    && child.getNodeName().equalsIgnoreCase("img")) { //$NON-NLS-1$
+                imgNode = child;
+            }
+        }
+        if (text && imgNode is null)
+            return getNodeText(node);
+        else if (!text && imgNode !is null)
+            return imgNode;
+        else return null;
+    }
+
+    private void processTextSegment(Paragraph p, bool expandURLs,
+            Node textNode) {
+        String text = getNodeText(textNode);
+
+        NamedNodeMap atts = textNode.getAttributes();
+        Node font = atts.getNamedItem("font"); //$NON-NLS-1$
+        Node color = atts.getNamedItem("color"); //$NON-NLS-1$
+        bool wrapAllowed=true;
+        Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$
+        if (nowrap !is null) {
+            String value = nowrap.getNodeValue();
+            if (value !is null && value.equalsIgnoreCase("true")) //$NON-NLS-1$
+                wrapAllowed = false;
+        }
+        String fontId = null;
+        String colorId = null;
+        if (font !is null) {
+            fontId = "f." + font.getNodeValue(); //$NON-NLS-1$
+        }
+        if (color !is null) {
+            colorId = "c." + color.getNodeValue(); //$NON-NLS-1$
+        }
+        p.parseRegularText(text, expandURLs, wrapAllowed, getHyperlinkSettings(), fontId,
+                colorId);
+    }
+
+    public void parseRegularText(String regularText, bool convertURLs) {
+        reset();
+
+        if (regularText is null)
+            return;
+
+        regularText = getNormalizedText(regularText);
+
+        Paragraph p = new Paragraph(true);
+        paragraphs.append(p);
+        int pstart = 0;
+
+        for (int i = 0; i < regularText.length(); i++) {
+            char c = regularText.charAt(i);
+            if (p is null) {
+                p = new Paragraph(true);
+                paragraphs.append(p);
+            }
+            if (c is '\n') {
+                String text = regularText.substring(pstart, i);
+                pstart = i + 1;
+                p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(),
+                        null);
+                p = null;
+            }
+        }
+        if (p !is null) {
+            // no new line
+            String text = regularText.substring(pstart);
+            p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(), null);
+        }
+    }
+
+    public HyperlinkSettings getHyperlinkSettings() {
+        // #132723 cannot have null settings
+        if (hyperlinkSettingsisnull)
+            hyperlinkSettings = new HyperlinkSettings(SWTUtil.getStandardDisplay());
+        return hyperlinkSettings;
+    }
+
+    public void setHyperlinkSettings(HyperlinkSettings settings) {
+        this.hyperlinkSettings = settings;
+    }
+
+    private void reset() {
+        if (paragraphs is null)
+            paragraphs = new TArraySeqParagraph;
+        paragraphs.clear();
+        selectedSegmentIndex = -1;
+        savedSelectedLinkIndex = -1;
+        selectableSegments = null;
+    }
+
+    IFocusSelectable[] getFocusSelectableSegments() {
+        if (selectableSegments !is null || paragraphs is null)
+            return selectableSegments;
+        IFocusSelectable[] result;
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph p = cast(Paragraph) paragraphs.get(i);
+            ParagraphSegment[] segments = p.getSegments();
+            for (int j = 0; j < segments.length; j++) {
+                if (null !is cast(IFocusSelectable)segments[j] )
+                    result ~= segments[j];
+            }
+        }
+        selectableSegments = result;
+        return selectableSegments;
+    }
+
+    public IHyperlinkSegment getHyperlink(int index) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        if (selectables.length>index) {
+            IFocusSelectable link = selectables[index];
+            if (auto l = cast(IHyperlinkSegment)link )
+                return l;
+        }
+        return null;
+    }
+
+    public IHyperlinkSegment findHyperlinkAt(int x, int y) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        for (int i = 0; i < selectables.length; i++) {
+            IFocusSelectable segment = selectables[i];
+            if ( auto link = cast(IHyperlinkSegment)segment ) {
+                if (link.contains(x, y))
+                    return link;
+            }
+        }
+        return null;
+    }
+
+    public int getHyperlinkCount() {
+        return getFocusSelectableSegments().length;
+    }
+
+    public int indexOf(IHyperlinkSegment link) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        for (int i = 0; i < selectables.length; i++) {
+            IFocusSelectable segment = selectables[i];
+            if (auto l = cast(IHyperlinkSegment)segment ) {
+                if (link is l)
+                    return i;
+            }
+        }
+        return -1;
+    }
+
+    public ParagraphSegment findSegmentAt(int x, int y) {
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph p = cast(Paragraph) paragraphs.get(i);
+            ParagraphSegment segment = p.findSegmentAt(x, y);
+            if (segment !is null)
+                return segment;
+        }
+        return null;
+    }
+
+    public void clearCache(String fontId) {
+        for (int i = 0; i < paragraphs.size(); i++) {
+            Paragraph p = cast(Paragraph) paragraphs.get(i);
+            p.clearCache(fontId);
+        }
+    }
+
+    public IFocusSelectable getSelectedSegment() {
+        if (selectableSegments is null || selectedSegmentIndex is -1)
+            return null;
+        return selectableSegments[selectedSegmentIndex];
+    }
+
+    public int getSelectedSegmentIndex() {
+        return selectedSegmentIndex;
+    }
+
+    public bool linkExists(IHyperlinkSegment link) {
+        if (selectableSegmentsisnull)
+            return false;
+        for (int i=0; i<selectableSegments.length; i++) {
+            if (selectableSegments[i] is link)
+                return true;
+        }
+        return false;
+    }
+
+    public bool traverseFocusSelectableObjects(bool next) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        if (selectables is null)
+            return false;
+        int size = selectables.length;
+        if (next) {
+            selectedSegmentIndex++;
+        } else
+            selectedSegmentIndex--;
+
+        if (selectedSegmentIndex < 0 || selectedSegmentIndex > size - 1) {
+            selectedSegmentIndex = -1;
+        }
+        return selectedSegmentIndex !is -1;
+    }
+
+    public IFocusSelectable getNextFocusSegment(bool next) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        if (selectables is null)
+            return null;
+        int nextIndex = next?selectedSegmentIndex+1:selectedSegmentIndex-1;
+
+        if (nextIndex < 0 || nextIndex > selectables.length - 1) {
+            return null;
+        }
+        return selectables[nextIndex];
+    }
+
+    public bool restoreSavedLink() {
+        if (savedSelectedLinkIndex!is -1) {
+            selectedSegmentIndex = savedSelectedLinkIndex;
+            return true;
+        }
+        return false;
+    }
+
+    public void selectLink(IHyperlinkSegment link) {
+        if (link is null) {
+            savedSelectedLinkIndex = selectedSegmentIndex;
+            selectedSegmentIndex = -1;
+        }
+        else {
+            select(link);
+
+        }
+    }
+
+    public void select(IFocusSelectable selectable) {
+        IFocusSelectable[] selectables = getFocusSelectableSegments();
+        selectedSegmentIndex = -1;
+        if (selectables is null)
+            return;
+        for (int i = 0; i < selectables.length; i++) {
+            if (selectables[i].equals(selectable)) {
+                selectedSegmentIndex = i;
+                break;
+            }
+        }
+    }
+
+    public bool hasFocusSegments() {
+        IFocusSelectable[] segments = getFocusSelectableSegments();
+        if (segments.length > 0)
+            return true;
+        return false;
+    }
+
+    public void dispose() {
+        paragraphs = null;
+        selectedSegmentIndex = -1;
+        savedSelectedLinkIndex = -1;
+        selectableSegments = null;
+    }
+
+    /**
+     * @return Returns the whitespaceNormalized.
+     */
+    public bool isWhitespaceNormalized() {
+        return whitespaceNormalized;
+    }
+
+    /**
+     * @param whitespaceNormalized
+     *            The whitespaceNormalized to set.
+     */
+    public void setWhitespaceNormalized(bool whitespaceNormalized) {
+        this.whitespaceNormalized = whitespaceNormalized;
+    }
+}
+++/
\ No newline at end of file