You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TextPaneCanvas.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /*
  2. * Copyright (c) 2006-2014 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.ui_swing.textpane;
  23. import com.dmdirc.addons.ui_swing.UIUtilities;
  24. import com.dmdirc.addons.ui_swing.textpane.LineRenderer.RenderResult;
  25. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  26. import com.dmdirc.interfaces.config.ConfigChangeListener;
  27. import com.dmdirc.ui.messages.IRCDocument;
  28. import com.dmdirc.ui.messages.IRCTextAttribute;
  29. import com.dmdirc.ui.messages.LinePosition;
  30. import com.dmdirc.util.StringUtils;
  31. import com.dmdirc.util.collections.ListenerList;
  32. import java.awt.Cursor;
  33. import java.awt.Graphics;
  34. import java.awt.Graphics2D;
  35. import java.awt.Point;
  36. import java.awt.Rectangle;
  37. import java.awt.Toolkit;
  38. import java.awt.event.AdjustmentEvent;
  39. import java.awt.event.AdjustmentListener;
  40. import java.awt.event.ComponentEvent;
  41. import java.awt.event.ComponentListener;
  42. import java.awt.event.InputEvent;
  43. import java.awt.event.MouseEvent;
  44. import java.awt.font.TextHitInfo;
  45. import java.awt.font.TextLayout;
  46. import java.awt.geom.Rectangle2D;
  47. import java.text.AttributedCharacterIterator;
  48. import java.util.HashMap;
  49. import java.util.Map;
  50. import javax.swing.JPanel;
  51. import javax.swing.SwingUtilities;
  52. import javax.swing.ToolTipManager;
  53. import javax.swing.event.MouseInputListener;
  54. /** Canvas object to draw text. */
  55. class TextPaneCanvas extends JPanel implements MouseInputListener,
  56. ComponentListener, AdjustmentListener, ConfigChangeListener {
  57. /** A version number for this class. */
  58. private static final long serialVersionUID = 8;
  59. /** Hand cursor. */
  60. private static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR);
  61. /** Single Side padding for textpane. */
  62. private static final int SINGLE_SIDE_PADDING = 3;
  63. /** Both Side padding for textpane. */
  64. private static final int DOUBLE_SIDE_PADDING = SINGLE_SIDE_PADDING * 2;
  65. /** IRCDocument. */
  66. private final IRCDocument document;
  67. /** parent textpane. */
  68. private final TextPane textPane;
  69. /** Position -> LineInfo. */
  70. private final Map<LineInfo, Rectangle2D.Float> lineAreas;
  71. /** TextLayout -> Line numbers. */
  72. private final Map<LineInfo, TextLayout> lineLayouts;
  73. /** Start line. */
  74. private int startLine;
  75. /** Selection. */
  76. private LinePosition selection;
  77. /** First visible line (from the top). */
  78. private int firstVisibleLine;
  79. /** Last visible line (from the top). */
  80. private int lastVisibleLine;
  81. /** Config Manager. */
  82. private final AggregateConfigProvider manager;
  83. /** Quick copy? */
  84. private boolean quickCopy;
  85. /** Mouse click listeners. */
  86. private final ListenerList listeners = new ListenerList();
  87. /** Renderer to use for lines. */
  88. private final LineRenderer lineRenderer;
  89. /**
  90. * Creates a new text pane canvas.
  91. *
  92. * @param parent parent text pane for the canvas
  93. * @param document IRCDocument to be displayed
  94. */
  95. public TextPaneCanvas(final TextPane parent, final IRCDocument document) {
  96. this.document = document;
  97. textPane = parent;
  98. this.manager = parent.getWindow().getContainer().getConfigManager();
  99. this.lineRenderer = new BasicTextLineRenderer(textPane, this, document);
  100. startLine = 0;
  101. setDoubleBuffered(true);
  102. setOpaque(true);
  103. lineLayouts = new HashMap<>();
  104. lineAreas = new HashMap<>();
  105. selection = new LinePosition(-1, -1, -1, -1);
  106. addMouseListener(this);
  107. addMouseMotionListener(this);
  108. addComponentListener(this);
  109. manager.addChangeListener("ui", "quickCopy", this);
  110. updateCachedSettings();
  111. ToolTipManager.sharedInstance().registerComponent(this);
  112. }
  113. /**
  114. * Paints the text onto the canvas.
  115. *
  116. * @param graphics graphics object to draw onto
  117. */
  118. @Override
  119. public void paintComponent(final Graphics graphics) {
  120. final Graphics2D g = (Graphics2D) graphics;
  121. final Map<?, ?> desktopHints = (Map<?, ?>) Toolkit.getDefaultToolkit().
  122. getDesktopProperty("awt.font.desktophints");
  123. if (desktopHints != null) {
  124. g.addRenderingHints(desktopHints);
  125. }
  126. paintOntoGraphics(g);
  127. }
  128. /**
  129. * Re calculates positions of lines and repaints if required.
  130. */
  131. protected void recalc() {
  132. if (isVisible()) {
  133. repaint();
  134. }
  135. }
  136. /**
  137. * Updates cached config settings.
  138. */
  139. private void updateCachedSettings() {
  140. quickCopy = manager.getOptionBool("ui", "quickCopy");
  141. UIUtilities.invokeLater(this::recalc);
  142. }
  143. private void paintOntoGraphics(final Graphics2D g) {
  144. final float formatWidth = getWidth() - DOUBLE_SIDE_PADDING;
  145. final float formatHeight = getHeight();
  146. float drawPosY = formatHeight - DOUBLE_SIDE_PADDING;
  147. lineLayouts.clear();
  148. lineAreas.clear();
  149. //check theres something to draw and theres some space to draw in
  150. if (document.getNumLines() == 0 || formatWidth < 1) {
  151. setCursor(Cursor.getDefaultCursor());
  152. return;
  153. }
  154. // Check the start line is in range
  155. startLine = Math.max(0, Math.min(document.getNumLines() - 1, startLine));
  156. //sets the last visible line
  157. lastVisibleLine = startLine;
  158. firstVisibleLine = startLine;
  159. // Iterate through the lines
  160. for (int line = startLine; line >= 0; line--) {
  161. drawPosY = paintLineOntoGraphics(g, formatWidth, formatHeight, drawPosY, line);
  162. if (drawPosY <= 0) {
  163. break;
  164. }
  165. }
  166. checkForLink();
  167. }
  168. private float paintLineOntoGraphics(final Graphics2D g, final float formatWidth,
  169. final float formatHeight, final float drawPosY, final int line) {
  170. final RenderResult result = lineRenderer.render(g, formatWidth, formatHeight, drawPosY,
  171. line);
  172. lineAreas.putAll(result.drawnAreas);
  173. lineLayouts.putAll(result.textLayouts);
  174. firstVisibleLine = result.firstVisibleLine;
  175. return drawPosY - result.totalHeight;
  176. }
  177. @Override
  178. public void adjustmentValueChanged(final AdjustmentEvent e) {
  179. if (startLine != e.getValue()) {
  180. startLine = e.getValue();
  181. recalc();
  182. }
  183. }
  184. @Override
  185. public void mouseClicked(final MouseEvent e) {
  186. final LineInfo lineInfo = getClickPosition(getMousePosition(), true);
  187. fireMouseEvents(getClickType(lineInfo), MouseEventType.CLICK, e);
  188. if (lineInfo.getLine() != -1) {
  189. final String clickedText = document.getLine(lineInfo.getLine()).getText();
  190. final int start;
  191. final int end;
  192. if (lineInfo.getIndex() == -1) {
  193. start = -1;
  194. end = -1;
  195. } else {
  196. final int[] extent = StringUtils.indiciesOfWord(clickedText, lineInfo.getIndex());
  197. start = extent[0];
  198. end = extent[1];
  199. }
  200. if (e.getClickCount() == 2) {
  201. setSelection(lineInfo.getLine(), start, end, e.isShiftDown());
  202. } else if (e.getClickCount() == 3) {
  203. setSelection(lineInfo.getLine(), 0, clickedText.length(), e.isShiftDown());
  204. }
  205. }
  206. }
  207. /**
  208. * Sets the selection to a range of characters on the specified line. If quick copy is enabled,
  209. * the selection will be copied.
  210. *
  211. * @param line The line of the selection
  212. * @param start The start of the selection
  213. * @param end The end of the selection
  214. * @param copyControlCharacters Whether or not to copy control characters.
  215. */
  216. private void setSelection(final int line, final int start, final int end,
  217. final boolean copyControlCharacters) {
  218. selection.setStartLine(line);
  219. selection.setEndLine(line);
  220. selection.setStartPos(start);
  221. selection.setEndPos(end);
  222. if (quickCopy) {
  223. textPane.copy(copyControlCharacters);
  224. clearSelection();
  225. }
  226. }
  227. /**
  228. * Returns the type of text this click represents.
  229. *
  230. * @param lineInfo Line info of click.
  231. *
  232. * @return Click type for specified position
  233. */
  234. public ClickTypeValue getClickType(final LineInfo lineInfo) {
  235. if (lineInfo.getLine() != -1) {
  236. final AttributedCharacterIterator iterator = document.getStyledLine(
  237. lineInfo.getLine());
  238. final int index = lineInfo.getIndex();
  239. if (index >= iterator.getBeginIndex() && index <= iterator.getEndIndex()) {
  240. iterator.setIndex(lineInfo.getIndex());
  241. final Object linkAttribute =
  242. iterator.getAttributes().get(IRCTextAttribute.HYPERLINK);
  243. if (linkAttribute instanceof String) {
  244. return new ClickTypeValue(ClickType.HYPERLINK, (String) linkAttribute);
  245. }
  246. final Object channelAttribute =
  247. iterator.getAttributes().get(IRCTextAttribute.CHANNEL);
  248. if (channelAttribute instanceof String) {
  249. return new ClickTypeValue(ClickType.CHANNEL, (String) channelAttribute);
  250. }
  251. final Object nickAttribute =
  252. iterator.getAttributes().get(IRCTextAttribute.NICKNAME);
  253. if (nickAttribute instanceof String) {
  254. return new ClickTypeValue(ClickType.NICKNAME, (String) nickAttribute);
  255. }
  256. } else {
  257. return new ClickTypeValue(ClickType.NORMAL, "");
  258. }
  259. }
  260. return new ClickTypeValue(ClickType.NORMAL, "");
  261. }
  262. @Override
  263. public void mousePressed(final MouseEvent e) {
  264. fireMouseEvents(getClickType(getClickPosition(e.getPoint(), false)),
  265. MouseEventType.PRESSED, e);
  266. if (e.getButton() == MouseEvent.BUTTON1) {
  267. highlightEvent(MouseEventType.CLICK, e);
  268. }
  269. }
  270. @Override
  271. public void mouseReleased(final MouseEvent e) {
  272. fireMouseEvents(getClickType(getClickPosition(e.getPoint(), false)),
  273. MouseEventType.RELEASED, e);
  274. if (quickCopy) {
  275. textPane.copy((e.getModifiers() & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK);
  276. SwingUtilities.invokeLater(new Runnable() {
  277. @Override
  278. public void run() {
  279. clearSelection();
  280. }
  281. });
  282. }
  283. if (e.getButton() == MouseEvent.BUTTON1) {
  284. highlightEvent(MouseEventType.RELEASED, e);
  285. }
  286. }
  287. @Override
  288. public void mouseDragged(final MouseEvent e) {
  289. if (e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK) {
  290. highlightEvent(MouseEventType.DRAG, e);
  291. }
  292. }
  293. @Override
  294. public void mouseEntered(final MouseEvent e) {
  295. //Ignore
  296. }
  297. @Override
  298. public void mouseExited(final MouseEvent e) {
  299. //Ignore
  300. }
  301. @Override
  302. public void mouseMoved(final MouseEvent e) {
  303. checkForLink();
  304. }
  305. /** Checks for a link under the cursor and sets appropriately. */
  306. private void checkForLink() {
  307. final AttributedCharacterIterator iterator = getIterator(getMousePosition());
  308. if (iterator != null
  309. && (iterator.getAttribute(IRCTextAttribute.HYPERLINK) != null
  310. || iterator.getAttribute(IRCTextAttribute.CHANNEL) != null
  311. || iterator.getAttribute(IRCTextAttribute.NICKNAME) != null)) {
  312. setCursor(HAND_CURSOR);
  313. return;
  314. }
  315. if (getCursor() == HAND_CURSOR) {
  316. setCursor(Cursor.getDefaultCursor());
  317. }
  318. }
  319. /**
  320. * Retrieves a character iterator for the text at the specified mouse position.
  321. *
  322. * @since 0.6.4
  323. * @param mousePosition The mouse position to retrieve text for
  324. *
  325. * @return A corresponding character iterator, or null if the specified mouse position doesn't
  326. * correspond to any text
  327. */
  328. private AttributedCharacterIterator getIterator(final Point mousePosition) {
  329. final LineInfo lineInfo = getClickPosition(mousePosition, false);
  330. if (lineInfo.getLine() != -1
  331. && document.getLine(lineInfo.getLine()) != null) {
  332. final AttributedCharacterIterator iterator = document.getStyledLine(lineInfo.getLine());
  333. if (lineInfo.getIndex() < iterator.getBeginIndex()
  334. || lineInfo.getIndex() > iterator.getEndIndex()) {
  335. return null;
  336. }
  337. iterator.setIndex(lineInfo.getIndex());
  338. return iterator;
  339. }
  340. return null;
  341. }
  342. /**
  343. * Sets the selection for the given event.
  344. *
  345. * @param type mouse event type
  346. * @param e responsible mouse event
  347. */
  348. protected void highlightEvent(final MouseEventType type, final MouseEvent e) {
  349. if (isVisible()) {
  350. final Point point = e.getLocationOnScreen();
  351. SwingUtilities.convertPointFromScreen(point, this);
  352. if (!contains(point)) {
  353. final Rectangle bounds = getBounds();
  354. final Point mousePos = e.getPoint();
  355. if (mousePos.getX() < bounds.getX()) {
  356. point.setLocation(bounds.getX() + SINGLE_SIDE_PADDING, point.getY());
  357. } else if (mousePos.getX() > bounds.getX() + bounds.getWidth()) {
  358. point.setLocation(bounds.getX() + bounds.getWidth()
  359. - SINGLE_SIDE_PADDING, point.getY());
  360. }
  361. if (mousePos.getY() < bounds.getY()) {
  362. point.setLocation(point.getX(), bounds.getY() + DOUBLE_SIDE_PADDING);
  363. } else if (mousePos.getY() > bounds.getY() + bounds.getHeight()) {
  364. point.setLocation(bounds.getX() + bounds.getWidth() - SINGLE_SIDE_PADDING,
  365. bounds.getY() + bounds.getHeight() - DOUBLE_SIDE_PADDING - 1);
  366. }
  367. }
  368. LineInfo info = getClickPosition(point, true);
  369. // TODO: These are fairly expensive if the user is moving around a lot; cache them.
  370. final Rectangle2D.Float first = getFirstLineRectangle();
  371. final Rectangle2D.Float last = getLastLineRectangle();
  372. if (info.getLine() == -1 && info.getPart() == -1 && contains(point)
  373. && document.getNumLines() != 0 && first != null && last != null) {
  374. if (first.getY() >= point.getY()) {
  375. info = getFirstLineInfo();
  376. } else if (last.getY() <= point.getY()) {
  377. info = getLastLineInfo();
  378. }
  379. }
  380. if (info.getLine() != -1 && info.getPart() != -1) {
  381. if (type == MouseEventType.CLICK) {
  382. selection.setStartLine(info.getLine());
  383. selection.setStartPos(info.getIndex());
  384. }
  385. selection.setEndLine(info.getLine());
  386. selection.setEndPos(info.getIndex());
  387. recalc();
  388. }
  389. }
  390. }
  391. /**
  392. * Returns the visible rectangle of the first line.
  393. *
  394. * @return First line's rectangle
  395. */
  396. private Rectangle2D.Float getFirstLineRectangle() {
  397. for (Map.Entry<LineInfo, Rectangle2D.Float> entry : lineAreas.entrySet()) {
  398. if (entry.getKey().getLine() == firstVisibleLine) {
  399. return entry.getValue();
  400. }
  401. }
  402. return null;
  403. }
  404. /**
  405. * Returns the last line's visible rectangle.
  406. *
  407. * @return Last line's rectangle
  408. */
  409. private Rectangle2D.Float getLastLineRectangle() {
  410. for (Map.Entry<LineInfo, Rectangle2D.Float> entry : lineAreas.entrySet()) {
  411. if (entry.getKey().getLine() == lastVisibleLine) {
  412. return entry.getValue();
  413. }
  414. }
  415. return null;
  416. }
  417. /**
  418. * Returns the LineInfo for the first visible line.
  419. *
  420. * @return First line's line info
  421. */
  422. private LineInfo getFirstLineInfo() {
  423. int firstLineParts = Integer.MAX_VALUE;
  424. for (LineInfo info : lineAreas.keySet()) {
  425. if (info.getLine() == firstVisibleLine && info.getPart() < firstLineParts) {
  426. firstLineParts = info.getPart();
  427. }
  428. }
  429. return new LineInfo(firstVisibleLine, firstLineParts);
  430. }
  431. /**
  432. * Returns the LineInfo for the last visible line.
  433. *
  434. * @return Last line's line info
  435. */
  436. private LineInfo getLastLineInfo() {
  437. int lastLineParts = -1;
  438. for (LineInfo info : lineAreas.keySet()) {
  439. if (info.getLine() == lastVisibleLine && info.getPart() > lastLineParts) {
  440. lastLineParts = info.getPart();
  441. }
  442. }
  443. return new LineInfo(lastVisibleLine + 1, lastLineParts);
  444. }
  445. /**
  446. *
  447. * Returns the line information from a mouse click inside the textpane.
  448. *
  449. * @param point mouse position
  450. * @param selection Are we selecting text?
  451. *
  452. * @return line number, line part, position in whole line
  453. */
  454. public LineInfo getClickPosition(final Point point, final boolean selection) {
  455. int lineNumber = -1;
  456. int linePart = -1;
  457. int pos = 0;
  458. if (point != null) {
  459. for (Map.Entry<LineInfo, Rectangle2D.Float> entry : lineAreas.entrySet()) {
  460. if (entry.getValue().contains(point)) {
  461. lineNumber = entry.getKey().getLine();
  462. linePart = entry.getKey().getPart();
  463. }
  464. }
  465. pos = getHitPosition(lineNumber, linePart, (int) point.getX(), (int) point.getY(),
  466. selection);
  467. }
  468. return new LineInfo(lineNumber, linePart, pos);
  469. }
  470. /**
  471. * Returns the character index for a specified line and part for a specific hit position.
  472. *
  473. * @param lineNumber Line number
  474. * @param linePart Line part
  475. * @param x X position
  476. * @param y Y position
  477. *
  478. * @return Hit position
  479. */
  480. private int getHitPosition(final int lineNumber, final int linePart,
  481. final float x, final float y, final boolean selection) {
  482. int pos = 0;
  483. for (Map.Entry<LineInfo, TextLayout> entry : lineLayouts.entrySet()) {
  484. if (entry.getKey().getLine() == lineNumber) {
  485. if (entry.getKey().getPart() < linePart) {
  486. pos += entry.getValue().getCharacterCount();
  487. } else if (entry.getKey().getPart() == linePart) {
  488. final TextHitInfo hit =
  489. entry.getValue().hitTestChar(x - DOUBLE_SIDE_PADDING, y);
  490. if (selection || x > entry.getValue().getBounds().getX()) {
  491. pos += hit.getInsertionIndex();
  492. } else {
  493. pos += hit.getCharIndex();
  494. }
  495. }
  496. }
  497. }
  498. return pos;
  499. }
  500. /**
  501. * Returns the selected range info.
  502. *
  503. * @return Selected range info
  504. */
  505. protected LinePosition getSelectedRange() {
  506. return selection.getNormalised();
  507. }
  508. /** Clears the selection. */
  509. protected void clearSelection() {
  510. selection.setEndLine(selection.getStartLine());
  511. selection.setEndPos(selection.getStartPos());
  512. recalc();
  513. }
  514. /**
  515. * Selects the specified region of text.
  516. *
  517. * @param position Line position
  518. */
  519. public void setSelectedRange(final LinePosition position) {
  520. selection = new LinePosition(position);
  521. recalc();
  522. }
  523. /**
  524. * Returns the first visible line.
  525. *
  526. * @return the line number of the first visible line
  527. */
  528. public int getFirstVisibleLine() {
  529. return firstVisibleLine;
  530. }
  531. /**
  532. * Returns the last visible line.
  533. *
  534. * @return the line number of the last visible line
  535. */
  536. public int getLastVisibleLine() {
  537. return lastVisibleLine;
  538. }
  539. /**
  540. * Returns the number of visible lines.
  541. *
  542. * @return Number of visible lines
  543. */
  544. public int getNumVisibleLines() {
  545. return lastVisibleLine - firstVisibleLine;
  546. }
  547. @Override
  548. public void componentResized(final ComponentEvent e) {
  549. recalc();
  550. }
  551. @Override
  552. public void componentMoved(final ComponentEvent e) {
  553. //Ignore
  554. }
  555. @Override
  556. public void componentShown(final ComponentEvent e) {
  557. //Ignore
  558. }
  559. @Override
  560. public void componentHidden(final ComponentEvent e) {
  561. //Ignore
  562. }
  563. @Override
  564. public void configChanged(final String domain, final String key) {
  565. updateCachedSettings();
  566. }
  567. @Override
  568. public String getToolTipText(final MouseEvent event) {
  569. final AttributedCharacterIterator iterator = getIterator(
  570. event.getPoint());
  571. if (iterator != null
  572. && iterator.getAttribute(IRCTextAttribute.TOOLTIP) != null) {
  573. return iterator.getAttribute(IRCTextAttribute.TOOLTIP).toString();
  574. }
  575. return super.getToolTipText(event);
  576. }
  577. /**
  578. * Fires mouse clicked events with the associated values.
  579. *
  580. * @param clickType Click type
  581. * @param eventType Mouse event type
  582. * @param event Triggering mouse event
  583. */
  584. private void fireMouseEvents(final ClickTypeValue clickType,
  585. final MouseEventType eventType, final MouseEvent event) {
  586. for (TextPaneListener listener : listeners.get(TextPaneListener.class)) {
  587. listener.mouseClicked(clickType, eventType, event);
  588. }
  589. }
  590. /**
  591. * Adds a textpane listener.
  592. *
  593. * @param listener Listener to add
  594. */
  595. public void addTextPaneListener(final TextPaneListener listener) {
  596. listeners.add(TextPaneListener.class, listener);
  597. }
  598. /**
  599. * Removes a textpane listener.
  600. *
  601. * @param listener Listener to remove
  602. */
  603. public void removeTextPaneListener(final TextPaneListener listener) {
  604. listeners.remove(TextPaneListener.class, listener);
  605. }
  606. }