1 /******************************************************************************* 2 * Copyright (c) 2000, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Ralf M Petter<ralf.petter@gmail.com> - Bug 259846 14 * Karsten Thoms<karsten.thoms@itemis.de> - Bug 521493 15 *******************************************************************************/ 16 package org.eclipse.ui.internal.forms.widgets; 17 18 import java.io.ByteArrayInputStream; 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.nio.charset.StandardCharsets; 22 import java.util.Vector; 23 24 import javax.xml.parsers.DocumentBuilder; 25 import javax.xml.parsers.DocumentBuilderFactory; 26 import javax.xml.parsers.ParserConfigurationException; 27 28 import org.eclipse.swt.SWT; 29 import org.eclipse.ui.forms.HyperlinkSettings; 30 import org.w3c.dom.Document; 31 import org.w3c.dom.NamedNodeMap; 32 import org.w3c.dom.Node; 33 import org.w3c.dom.NodeList; 34 import org.xml.sax.ErrorHandler; 35 import org.xml.sax.InputSource; 36 import org.xml.sax.SAXException; 37 import org.xml.sax.SAXParseException; 38 39 public class FormTextModel { 40 41 /* 42 * This class prevents parse errors from being written to standard output 43 */ 44 public static class ParseErrorHandler implements ErrorHandler { 45 46 @Override error(SAXParseException arg0)47 public void error(SAXParseException arg0) throws SAXException { 48 } 49 50 @Override fatalError(SAXParseException arg0)51 public void fatalError(SAXParseException arg0) throws SAXException { 52 } 53 54 @Override warning(SAXParseException arg0)55 public void warning(SAXParseException arg0) throws SAXException { 56 } 57 } 58 59 private static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory 60 .newInstance(); 61 62 private boolean whitespaceNormalized = true; 63 64 private Vector<Paragraph> paragraphs; 65 66 private IFocusSelectable[] selectableSegments; 67 68 private int selectedSegmentIndex = -1; 69 70 private int savedSelectedLinkIndex = -1; 71 72 private HyperlinkSettings hyperlinkSettings; 73 74 public static final String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$ 75 76 //private static final int TEXT_ONLY_LINK = 1; 77 78 //private static final int IMG_ONLY_LINK = 2; 79 80 //private static final int TEXT_AND_IMAGES_LINK = 3; 81 FormTextModel()82 public FormTextModel() { 83 reset(); 84 } 85 86 /* 87 * @see ITextModel#getParagraphs() 88 */ getParagraphs()89 public Paragraph[] getParagraphs() { 90 if (paragraphs == null) 91 return new Paragraph[0]; 92 return paragraphs 93 .toArray(new Paragraph[paragraphs.size()]); 94 } 95 getAccessibleText()96 public String getAccessibleText() { 97 if (paragraphs == null) 98 return ""; //$NON-NLS-1$ 99 StringBuilder sbuf = new StringBuilder(); 100 for (Paragraph paragraph : paragraphs) { 101 String text = paragraph.getAccessibleText(); 102 sbuf.append(text); 103 } 104 return sbuf.toString(); 105 } 106 107 /* 108 * @see ITextModel#parse(String) 109 */ parseTaggedText(String taggedText, boolean expandURLs)110 public void parseTaggedText(String taggedText, boolean expandURLs) { 111 if (taggedText == null) { 112 reset(); 113 return; 114 } 115 taggedText = processAmpersandEscapes(taggedText); 116 InputStream stream = new ByteArrayInputStream(taggedText.getBytes(StandardCharsets.UTF_8)); 117 parseInputStream(stream, expandURLs); 118 } 119 processAmpersandEscapes(String pTaggedText)120 private String processAmpersandEscapes(String pTaggedText) { 121 try { 122 String taggedText = pTaggedText.replaceAll(""", """); //$NON-NLS-1$//$NON-NLS-2$ 123 taggedText = taggedText.replaceAll("'", "'"); //$NON-NLS-1$//$NON-NLS-2$ 124 taggedText = taggedText.replaceAll("<", "<"); //$NON-NLS-1$//$NON-NLS-2$ 125 taggedText = taggedText.replaceAll(">", ">"); //$NON-NLS-1$//$NON-NLS-2$ 126 taggedText = taggedText.replaceAll("&", "&"); //$NON-NLS-1$//$NON-NLS-2$ 127 return taggedText.replaceAll("&([^#])", "&$1"); //$NON-NLS-1$//$NON-NLS-2$ 128 } catch (Exception e) { 129 return pTaggedText; 130 } 131 } 132 parseInputStream(InputStream is, boolean expandURLs)133 public void parseInputStream(InputStream is, boolean expandURLs) { 134 135 documentBuilderFactory.setNamespaceAware(true); 136 documentBuilderFactory.setIgnoringComments(true); 137 138 reset(); 139 try { 140 DocumentBuilder parser = documentBuilderFactory 141 .newDocumentBuilder(); 142 parser.setErrorHandler(new ParseErrorHandler()); 143 InputSource source = new InputSource(is); 144 Document doc = parser.parse(source); 145 processDocument(doc, expandURLs); 146 } catch (ParserConfigurationException | SAXException e) { 147 SWT.error(SWT.ERROR_INVALID_ARGUMENT, e, " " + e.getMessage()); //$NON-NLS-1$ 148 } catch (IOException e) { 149 SWT.error(SWT.ERROR_IO, e); 150 } 151 } 152 processDocument(Document doc, boolean expandURLs)153 private void processDocument(Document doc, boolean expandURLs) { 154 Node root = doc.getDocumentElement(); 155 NodeList children = root.getChildNodes(); 156 processSubnodes(paragraphs, children, expandURLs); 157 } 158 processSubnodes(Vector<Paragraph> plist, NodeList children, boolean expandURLs)159 private void processSubnodes(Vector<Paragraph> plist, NodeList children, boolean expandURLs) { 160 for (int i = 0; i < children.getLength(); i++) { 161 Node child = children.item(i); 162 if (child.getNodeType() == Node.TEXT_NODE) { 163 // Make an implicit paragraph 164 String text = getSingleNodeText(child); 165 if (text != null && !isIgnorableWhiteSpace(text, true)) { 166 Paragraph p = new Paragraph(true); 167 p.parseRegularText(text, expandURLs, true, 168 getHyperlinkSettings(), null); 169 plist.add(p); 170 } 171 } else if (child.getNodeType() == Node.ELEMENT_NODE) { 172 String tag = child.getNodeName().toLowerCase(); 173 if (tag.equals("p")) { //$NON-NLS-1$ 174 Paragraph p = processParagraph(child, expandURLs); 175 if (p != null) 176 plist.add(p); 177 } else if (tag.equals("li")) { //$NON-NLS-1$ 178 Paragraph p = processListItem(child, expandURLs); 179 if (p != null) 180 plist.add(p); 181 } 182 } 183 } 184 } 185 processParagraph(Node paragraph, boolean expandURLs)186 private Paragraph processParagraph(Node paragraph, boolean expandURLs) { 187 NodeList children = paragraph.getChildNodes(); 188 NamedNodeMap atts = paragraph.getAttributes(); 189 Node addSpaceAtt = atts.getNamedItem("addVerticalSpace"); //$NON-NLS-1$ 190 boolean addSpace = true; 191 192 if (addSpaceAtt == null) 193 addSpaceAtt = atts.getNamedItem("vspace"); //$NON-NLS-1$ 194 195 if (addSpaceAtt != null) { 196 String value = addSpaceAtt.getNodeValue(); 197 addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$ 198 } 199 Paragraph p = new Paragraph(addSpace); 200 201 processSegments(p, children, expandURLs); 202 return p; 203 } 204 processListItem(Node listItem, boolean expandURLs)205 private Paragraph processListItem(Node listItem, boolean expandURLs) { 206 NodeList children = listItem.getChildNodes(); 207 NamedNodeMap atts = listItem.getAttributes(); 208 Node addSpaceAtt = atts.getNamedItem("addVerticalSpace");//$NON-NLS-1$ 209 Node styleAtt = atts.getNamedItem("style");//$NON-NLS-1$ 210 Node valueAtt = atts.getNamedItem("value");//$NON-NLS-1$ 211 Node indentAtt = atts.getNamedItem("indent");//$NON-NLS-1$ 212 Node bindentAtt = atts.getNamedItem("bindent");//$NON-NLS-1$ 213 int style = BulletParagraph.CIRCLE; 214 int indent = -1; 215 int bindent = -1; 216 String text = null; 217 boolean addSpace = true; 218 219 if (addSpaceAtt != null) { 220 String value = addSpaceAtt.getNodeValue(); 221 addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$ 222 } 223 if (styleAtt != null) { 224 String value = styleAtt.getNodeValue(); 225 if (value.equalsIgnoreCase("text")) { //$NON-NLS-1$ 226 style = BulletParagraph.TEXT; 227 } else if (value.equalsIgnoreCase("image")) { //$NON-NLS-1$ 228 style = BulletParagraph.IMAGE; 229 } else if (value.equalsIgnoreCase("bullet")) { //$NON-NLS-1$ 230 style = BulletParagraph.CIRCLE; 231 } 232 } 233 if (valueAtt != null) { 234 text = valueAtt.getNodeValue(); 235 if (style == BulletParagraph.IMAGE) 236 text = "i." + text; //$NON-NLS-1$ 237 } 238 if (indentAtt != null) { 239 String value = indentAtt.getNodeValue(); 240 try { 241 indent = Integer.parseInt(value); 242 } catch (NumberFormatException e) { 243 } 244 } 245 if (bindentAtt != null) { 246 String value = bindentAtt.getNodeValue(); 247 try { 248 bindent = Integer.parseInt(value); 249 } catch (NumberFormatException e) { 250 } 251 } 252 253 BulletParagraph p = new BulletParagraph(addSpace); 254 p.setIndent(indent); 255 p.setBulletIndent(bindent); 256 p.setBulletStyle(style); 257 p.setBulletText(text); 258 259 processSegments(p, children, expandURLs); 260 return p; 261 } 262 processSegments(Paragraph p, NodeList children, boolean expandURLs)263 private void processSegments(Paragraph p, NodeList children, 264 boolean expandURLs) { 265 for (int i = 0; i < children.getLength(); i++) { 266 Node child = children.item(i); 267 ParagraphSegment segment = null; 268 269 if (child.getNodeType() == Node.TEXT_NODE) { 270 String value = getSingleNodeText(child); 271 272 if (value != null && !isIgnorableWhiteSpace(value, false)) { 273 p.parseRegularText(value, expandURLs, true, 274 getHyperlinkSettings(), null); 275 } 276 } else if (child.getNodeType() == Node.ELEMENT_NODE) { 277 String name = child.getNodeName(); 278 if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$ 279 segment = processImageSegment(child); 280 } else if (name.equalsIgnoreCase("a")) { //$NON-NLS-1$ 281 segment = processHyperlinkSegment(child, 282 getHyperlinkSettings()); 283 } else if (name.equalsIgnoreCase("span")) { //$NON-NLS-1$ 284 processTextSegment(p, expandURLs, child); 285 } else if (name.equalsIgnoreCase("b")) { //$NON-NLS-1$ 286 String text = getNodeText(child); 287 String fontId = BOLD_FONT_ID; 288 p.parseRegularText(text, expandURLs, true, 289 getHyperlinkSettings(), fontId); 290 } else if (name.equalsIgnoreCase("br")) { //$NON-NLS-1$ 291 segment = new BreakSegment(); 292 } else if (name.equalsIgnoreCase("control")) { //$NON-NLS-1$ 293 segment = processControlSegment(child); 294 } 295 } 296 if (segment != null) { 297 p.addSegment(segment); 298 } 299 } 300 } 301 isIgnorableWhiteSpace(String text, boolean ignoreSpaces)302 private boolean isIgnorableWhiteSpace(String text, boolean ignoreSpaces) { 303 for (int i = 0; i < text.length(); i++) { 304 char c = text.charAt(i); 305 if (ignoreSpaces && c == ' ') 306 continue; 307 if (c == '\n' || c == '\r' || c == '\f') 308 continue; 309 return false; 310 } 311 return true; 312 } 313 processImageSegment(Node image)314 private ImageSegment processImageSegment(Node image) { 315 ImageSegment segment = new ImageSegment(); 316 processObjectSegment(segment, image, "i."); //$NON-NLS-1$ 317 return segment; 318 } 319 processControlSegment(Node control)320 private ControlSegment processControlSegment(Node control) { 321 ControlSegment segment = new ControlSegment(); 322 processObjectSegment(segment, control, "o."); //$NON-NLS-1$ 323 Node fill = control.getAttributes().getNamedItem("fill"); //$NON-NLS-1$ 324 if (fill!=null) { 325 String value = fill.getNodeValue(); 326 boolean doFill = value.equalsIgnoreCase("true"); //$NON-NLS-1$ 327 segment.setFill(doFill); 328 } 329 try { 330 Node width = control.getAttributes().getNamedItem("width"); //$NON-NLS-1$ 331 if (width!=null) { 332 String value = width.getNodeValue(); 333 int doWidth = Integer.parseInt(value); 334 segment.setWidth(doWidth); 335 } 336 Node height = control.getAttributes().getNamedItem("height"); //$NON-NLS-1$ 337 if (height!=null) { 338 String value = height.getNodeValue(); 339 int doHeight = Integer.parseInt(value); 340 segment.setHeight(doHeight); 341 } 342 } 343 catch (NumberFormatException e) { 344 // ignore invalid width or height 345 } 346 return segment; 347 } 348 processObjectSegment(ObjectSegment segment, Node object, String prefix)349 private void processObjectSegment(ObjectSegment segment, Node object, String prefix) { 350 NamedNodeMap atts = object.getAttributes(); 351 Node id = atts.getNamedItem("href"); //$NON-NLS-1$ 352 Node align = atts.getNamedItem("align"); //$NON-NLS-1$ 353 if (id != null) { 354 String value = id.getNodeValue(); 355 segment.setObjectId(prefix + value); 356 } 357 if (align != null) { 358 String value = align.getNodeValue().toLowerCase(); 359 switch (value) { 360 case "top": //$NON-NLS-1$ 361 segment.setVerticalAlignment(ObjectSegment.TOP); 362 break; 363 case "middle": //$NON-NLS-1$ 364 segment.setVerticalAlignment(ObjectSegment.MIDDLE); 365 break; 366 case "bottom": //$NON-NLS-1$ 367 segment.setVerticalAlignment(ObjectSegment.BOTTOM); 368 break; 369 default: 370 break; 371 } 372 } 373 } 374 appendText(String value, StringBuilder buf, int[] spaceCounter)375 private void appendText(String value, StringBuilder buf, int[] spaceCounter) { 376 if (!whitespaceNormalized) 377 buf.append(value); 378 else { 379 for (int j = 0; j < value.length(); j++) { 380 char c = value.charAt(j); 381 switch (c) { 382 case ' ': 383 case '\t': 384 // space 385 if (++spaceCounter[0] == 1) { 386 buf.append(c); 387 } 388 break; 389 case '\n': 390 case '\r': 391 case '\f': 392 // new line 393 if (++spaceCounter[0] == 1) { 394 buf.append(' '); 395 } 396 break; 397 default: 398 // other characters 399 spaceCounter[0] = 0; 400 buf.append(c); 401 break; 402 } 403 } 404 } 405 } 406 getNormalizedText(String text)407 private String getNormalizedText(String text) { 408 int[] spaceCounter = new int[1]; 409 StringBuilder buf = new StringBuilder(); 410 411 if (text == null) 412 return null; 413 appendText(text, buf, spaceCounter); 414 return buf.toString(); 415 } 416 getSingleNodeText(Node node)417 private String getSingleNodeText(Node node) { 418 String text = getNormalizedText(node.getNodeValue()); 419 if (!whitespaceNormalized) 420 return text; 421 if (text.length() > 0 && node.getPreviousSibling() == null && isIgnorableWhiteSpace(text.substring(0, 1), true)) 422 return text.substring(1); 423 if (text.length() > 1 && node.getNextSibling() == null 424 && isIgnorableWhiteSpace(text.substring(text.length() - 1), true)) 425 return text.substring(0, text.length() - 1); 426 return text; 427 } 428 getNodeText(Node node)429 private String getNodeText(Node node) { 430 NodeList children = node.getChildNodes(); 431 StringBuilder buf = new StringBuilder(); 432 int[] spaceCounter = new int[1]; 433 434 for (int i = 0; i < children.getLength(); i++) { 435 Node child = children.item(i); 436 if (child.getNodeType() == Node.TEXT_NODE) { 437 String value = child.getNodeValue(); 438 appendText(value, buf, spaceCounter); 439 } 440 } 441 if (whitespaceNormalized) { 442 return buf.toString().trim(); 443 } 444 return buf.toString(); 445 } 446 processHyperlinkSegment(Node link, HyperlinkSettings settings)447 private ParagraphSegment processHyperlinkSegment(Node link, 448 HyperlinkSettings settings) { 449 NamedNodeMap atts = link.getAttributes(); 450 String href = null; 451 boolean wrapAllowed = true; 452 String boldFontId = null; 453 454 Node hrefAtt = atts.getNamedItem("href"); //$NON-NLS-1$ 455 if (hrefAtt != null) { 456 href = hrefAtt.getNodeValue(); 457 } 458 Node boldAtt = atts.getNamedItem("bold"); //$NON-NLS-1$ 459 if (boldAtt != null) { 460 boldFontId = BOLD_FONT_ID; 461 } 462 Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$ 463 if (nowrap != null) { 464 String value = nowrap.getNodeValue(); 465 if (value != null && value.equalsIgnoreCase("true")) //$NON-NLS-1$ 466 wrapAllowed = false; 467 } 468 Object status = checkChildren(link); 469 if (status instanceof Node) { 470 Node child = (Node)status; 471 ImageHyperlinkSegment segment = new ImageHyperlinkSegment(); 472 segment.setHref(href); 473 segment.setWordWrapAllowed(wrapAllowed); 474 Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$ 475 if (alt!=null) 476 segment.setTooltipText(alt.getNodeValue()); 477 Node text = child.getAttributes().getNamedItem("text"); //$NON-NLS-1$ 478 if (text!=null) 479 segment.setText(text.getNodeValue()); 480 processObjectSegment(segment, child, "i."); //$NON-NLS-1$ 481 return segment; 482 } else if (status instanceof String) { 483 String text = (String) status; 484 TextHyperlinkSegment segment = new TextHyperlinkSegment(text, 485 settings, null); 486 segment.setHref(href); 487 segment.setFontId(boldFontId); 488 Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$ 489 if (alt!=null) 490 segment.setTooltipText(alt.getNodeValue()); 491 segment.setWordWrapAllowed(wrapAllowed); 492 return segment; 493 } else { 494 AggregateHyperlinkSegment parent = new AggregateHyperlinkSegment(); 495 parent.setHref(href); 496 NodeList children = link.getChildNodes(); 497 for (int i = 0; i < children.getLength(); i++) { 498 Node child = children.item(i); 499 if (child.getNodeType() == Node.TEXT_NODE) { 500 String value = child.getNodeValue(); 501 TextHyperlinkSegment ts = new TextHyperlinkSegment( 502 getNormalizedText(value), settings, null); 503 Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$ 504 if (alt!=null) 505 ts.setTooltipText(alt.getNodeValue()); 506 ts.setWordWrapAllowed(wrapAllowed); 507 parent.add(ts); 508 } else if (child.getNodeType() == Node.ELEMENT_NODE) { 509 String name = child.getNodeName(); 510 if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$ 511 ImageHyperlinkSegment is = new ImageHyperlinkSegment(); 512 processObjectSegment(is, child, "i."); //$NON-NLS-1$ 513 Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$ 514 if (alt!=null) 515 is.setTooltipText(alt.getNodeValue()); 516 parent.add(is); 517 is.setWordWrapAllowed(wrapAllowed); 518 } 519 } 520 } 521 return parent; 522 } 523 } 524 checkChildren(Node node)525 private Object checkChildren(Node node) { 526 boolean text = false; 527 Node imgNode = null; 528 //int status = 0; 529 530 NodeList children = node.getChildNodes(); 531 for (int i = 0; i < children.getLength(); i++) { 532 Node child = children.item(i); 533 if (child.getNodeType() == Node.TEXT_NODE) 534 text = true; 535 else if (child.getNodeType() == Node.ELEMENT_NODE 536 && child.getNodeName().equalsIgnoreCase("img")) { //$NON-NLS-1$ 537 imgNode = child; 538 } 539 } 540 if (text && imgNode == null) 541 return getNodeText(node); 542 else if (!text && imgNode != null) 543 return imgNode; 544 else return null; 545 } 546 processTextSegment(Paragraph p, boolean expandURLs, Node textNode)547 private void processTextSegment(Paragraph p, boolean expandURLs, 548 Node textNode) { 549 String text = getNodeText(textNode); 550 551 NamedNodeMap atts = textNode.getAttributes(); 552 Node font = atts.getNamedItem("font"); //$NON-NLS-1$ 553 Node color = atts.getNamedItem("color"); //$NON-NLS-1$ 554 boolean wrapAllowed=true; 555 Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$ 556 if (nowrap != null) { 557 String value = nowrap.getNodeValue(); 558 if (value != null && value.equalsIgnoreCase("true")) //$NON-NLS-1$ 559 wrapAllowed = false; 560 } 561 String fontId = null; 562 String colorId = null; 563 if (font != null) { 564 fontId = "f." + font.getNodeValue(); //$NON-NLS-1$ 565 } 566 if (color != null) { 567 colorId = "c." + color.getNodeValue(); //$NON-NLS-1$ 568 } 569 p.parseRegularText(text, expandURLs, wrapAllowed, getHyperlinkSettings(), fontId, 570 colorId); 571 } 572 parseRegularText(String regularText, boolean convertURLs)573 public void parseRegularText(String regularText, boolean convertURLs) { 574 reset(); 575 576 if (regularText == null) 577 return; 578 579 regularText = getNormalizedText(regularText); 580 581 Paragraph p = new Paragraph(true); 582 paragraphs.add(p); 583 int pstart = 0; 584 585 for (int i = 0; i < regularText.length(); i++) { 586 char c = regularText.charAt(i); 587 if (p == null) { 588 p = new Paragraph(true); 589 paragraphs.add(p); 590 } 591 if (c == '\n') { 592 String text = regularText.substring(pstart, i); 593 pstart = i + 1; 594 p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(), 595 null); 596 p = null; 597 } 598 } 599 if (p != null) { 600 // no new line 601 String text = regularText.substring(pstart); 602 p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(), null); 603 } 604 } 605 getHyperlinkSettings()606 public HyperlinkSettings getHyperlinkSettings() { 607 // #132723 cannot have null settings 608 if (hyperlinkSettings==null) 609 hyperlinkSettings = new HyperlinkSettings(SWTUtil.getStandardDisplay()); 610 return hyperlinkSettings; 611 } 612 setHyperlinkSettings(HyperlinkSettings settings)613 public void setHyperlinkSettings(HyperlinkSettings settings) { 614 this.hyperlinkSettings = settings; 615 } 616 reset()617 private void reset() { 618 if (paragraphs == null) 619 paragraphs = new Vector<>(); 620 paragraphs.clear(); 621 selectedSegmentIndex = -1; 622 savedSelectedLinkIndex = -1; 623 selectableSegments = null; 624 } 625 getFocusSelectableSegments()626 IFocusSelectable[] getFocusSelectableSegments() { 627 if (selectableSegments != null || paragraphs == null) 628 return selectableSegments; 629 Vector<ParagraphSegment> result = new Vector<>(); 630 for (Paragraph paragraph : paragraphs) { 631 ParagraphSegment[] segments = paragraph.getSegments(); 632 for (ParagraphSegment segment : segments) { 633 if (segment instanceof IFocusSelectable) 634 result.add(segment); 635 } 636 } 637 selectableSegments = result 638 .toArray(new IFocusSelectable[result.size()]); 639 return selectableSegments; 640 } 641 getHyperlink(int index)642 public IHyperlinkSegment getHyperlink(int index) { 643 IFocusSelectable[] selectables = getFocusSelectableSegments(); 644 if (selectables.length>index) { 645 IFocusSelectable link = selectables[index]; 646 if (link instanceof IHyperlinkSegment) 647 return (IHyperlinkSegment)link; 648 } 649 return null; 650 } 651 findHyperlinkAt(int x, int y)652 public IHyperlinkSegment findHyperlinkAt(int x, int y) { 653 IFocusSelectable[] selectables = getFocusSelectableSegments(); 654 for (IFocusSelectable segment : selectables) { 655 if (segment instanceof IHyperlinkSegment) { 656 IHyperlinkSegment link = (IHyperlinkSegment)segment; 657 if (link.contains(x, y)) 658 return link; 659 } 660 } 661 return null; 662 } 663 getHyperlinkCount()664 public int getHyperlinkCount() { 665 return getFocusSelectableSegments().length; 666 } 667 indexOf(IHyperlinkSegment link)668 public int indexOf(IHyperlinkSegment link) { 669 IFocusSelectable[] selectables = getFocusSelectableSegments(); 670 for (int i = 0; i < selectables.length; i++) { 671 IFocusSelectable segment = selectables[i]; 672 if (segment instanceof IHyperlinkSegment) { 673 IHyperlinkSegment l = (IHyperlinkSegment)segment; 674 if (link==l) 675 return i; 676 } 677 } 678 return -1; 679 } 680 findSegmentAt(int x, int y)681 public ParagraphSegment findSegmentAt(int x, int y) { 682 for (Paragraph paragraph : paragraphs) { 683 ParagraphSegment segment = paragraph.findSegmentAt(x, y); 684 if (segment != null) 685 return segment; 686 } 687 return null; 688 } 689 clearCache(String fontId)690 public void clearCache(String fontId) { 691 for (Paragraph paragraph : paragraphs) { 692 paragraph.clearCache(fontId); 693 } 694 } 695 getSelectedSegment()696 public IFocusSelectable getSelectedSegment() { 697 if (selectableSegments==null || selectedSegmentIndex == -1) 698 return null; 699 return selectableSegments[selectedSegmentIndex]; 700 } 701 getSelectedSegmentIndex()702 public int getSelectedSegmentIndex() { 703 return selectedSegmentIndex; 704 } 705 linkExists(IHyperlinkSegment link)706 public boolean linkExists(IHyperlinkSegment link) { 707 if (selectableSegments==null) 708 return false; 709 for (IFocusSelectable selectableSegment : selectableSegments) { 710 if (selectableSegment==link) 711 return true; 712 } 713 return false; 714 } 715 traverseFocusSelectableObjects(boolean next)716 public boolean traverseFocusSelectableObjects(boolean next) { 717 IFocusSelectable[] selectables = getFocusSelectableSegments(); 718 if (selectables == null) 719 return false; 720 int size = selectables.length; 721 if (next) { 722 selectedSegmentIndex++; 723 } else 724 selectedSegmentIndex--; 725 726 if (selectedSegmentIndex < 0 || selectedSegmentIndex > size - 1) { 727 selectedSegmentIndex = -1; 728 } 729 return selectedSegmentIndex != -1; 730 } 731 getNextFocusSegment(boolean next)732 public IFocusSelectable getNextFocusSegment(boolean next) { 733 IFocusSelectable[] selectables = getFocusSelectableSegments(); 734 if (selectables == null) 735 return null; 736 int nextIndex = next?selectedSegmentIndex+1:selectedSegmentIndex-1; 737 738 if (nextIndex < 0 || nextIndex > selectables.length - 1) { 739 return null; 740 } 741 return selectables[nextIndex]; 742 } 743 restoreSavedLink()744 public boolean restoreSavedLink() { 745 if (savedSelectedLinkIndex!= -1) { 746 selectedSegmentIndex = savedSelectedLinkIndex; 747 return true; 748 } 749 return false; 750 } 751 selectLink(IHyperlinkSegment link)752 public void selectLink(IHyperlinkSegment link) { 753 if (link == null) { 754 savedSelectedLinkIndex = selectedSegmentIndex; 755 selectedSegmentIndex = -1; 756 } 757 else { 758 select(link); 759 760 } 761 } 762 select(IFocusSelectable selectable)763 public void select(IFocusSelectable selectable) { 764 IFocusSelectable[] selectables = getFocusSelectableSegments(); 765 selectedSegmentIndex = -1; 766 if (selectables == null) 767 return; 768 for (int i = 0; i < selectables.length; i++) { 769 if (selectables[i].equals(selectable)) { 770 selectedSegmentIndex = i; 771 break; 772 } 773 } 774 } 775 hasFocusSegments()776 public boolean hasFocusSegments() { 777 IFocusSelectable[] segments = getFocusSelectableSegments(); 778 if (segments.length > 0) 779 return true; 780 return false; 781 } 782 dispose()783 public void dispose() { 784 paragraphs = null; 785 selectedSegmentIndex = -1; 786 savedSelectedLinkIndex = -1; 787 selectableSegments = null; 788 } 789 790 /** 791 * @return Returns the whitespaceNormalized. 792 */ isWhitespaceNormalized()793 public boolean isWhitespaceNormalized() { 794 return whitespaceNormalized; 795 } 796 797 /** 798 * @param whitespaceNormalized 799 * The whitespaceNormalized to set. 800 */ setWhitespaceNormalized(boolean whitespaceNormalized)801 public void setWhitespaceNormalized(boolean whitespaceNormalized) { 802 this.whitespaceNormalized = whitespaceNormalized; 803 } 804 } 805