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 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  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.ui.messages.IRCTextAttribute;
  24. import java.awt.Cursor;
  25. import java.awt.Graphics;
  26. import java.awt.Graphics2D;
  27. import java.awt.Point;
  28. import java.awt.Rectangle;
  29. import java.awt.Shape;
  30. import java.awt.Toolkit;
  31. import java.awt.event.AdjustmentEvent;
  32. import java.awt.event.AdjustmentListener;
  33. import java.awt.event.ComponentEvent;
  34. import java.awt.event.ComponentListener;
  35. import java.awt.event.MouseEvent;
  36. import java.awt.font.LineBreakMeasurer;
  37. import java.awt.font.TextAttribute;
  38. import java.awt.font.TextHitInfo;
  39. import java.awt.font.TextLayout;
  40. import java.awt.image.BufferedImage;
  41. import java.text.AttributedCharacterIterator;
  42. import java.text.AttributedString;
  43. import java.util.HashMap;
  44. import java.util.Map;
  45. import javax.swing.JPanel;
  46. import javax.swing.SwingUtilities;
  47. import javax.swing.event.MouseInputListener;
  48. /** Canvas object to draw text. */
  49. class TextPaneCanvas extends JPanel implements MouseInputListener,
  50. ComponentListener, AdjustmentListener {
  51. /**
  52. * A version number for this class. It should be changed whenever the
  53. * class structure is changed (or anything else that would prevent
  54. * serialized objects being unserialized with the new class).
  55. */
  56. private static final long serialVersionUID = 8;
  57. /** Hand cursor. */
  58. private static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR);
  59. /** Single Side padding for textpane. */
  60. private static final int SINGLE_SIDE_PADDING = 3;
  61. /** Both Side padding for textpane. */
  62. private static final int DOUBLE_SIDE_PADDING = SINGLE_SIDE_PADDING * 2;
  63. /** IRCDocument. */
  64. private final IRCDocument document;
  65. /** parent textpane. */
  66. private final TextPane textPane;
  67. /** Position -> TextLayout. */
  68. private final Map<Rectangle, TextLayout> positions;
  69. /** TextLayout -> Line numbers. */
  70. private final Map<TextLayout, LineInfo> textLayouts;
  71. /** Start line. */
  72. private int startLine;
  73. /** Selection. */
  74. private LinePosition selection;
  75. /** First visible line. */
  76. private int firstVisibleLine;
  77. /** Last visible line. */
  78. private int lastVisibleLine;
  79. /** Cached canvas. */
  80. private BufferedImage buffer;
  81. /**
  82. * Creates a new text pane canvas.
  83. *
  84. * @param parent parent text pane for the canvas
  85. * @param document IRCDocument to be displayed
  86. */
  87. public TextPaneCanvas(final TextPane parent, final IRCDocument document) {
  88. super();
  89. this.document = document;
  90. startLine = 0;
  91. textPane = parent;
  92. setDoubleBuffered(true);
  93. setOpaque(true);
  94. textLayouts = new HashMap<TextLayout, LineInfo>();
  95. positions = new HashMap<Rectangle, TextLayout>();
  96. selection = new LinePosition(-1, -1, -1, -1);
  97. addMouseListener(this);
  98. addMouseMotionListener(this);
  99. addComponentListener(this);
  100. }
  101. /**
  102. * Paints the text onto the canvas.
  103. *
  104. * @param graphics graphics object to draw onto
  105. */
  106. @Override
  107. public void paintComponent(final Graphics graphics) {
  108. final Graphics2D g = (Graphics2D) graphics;
  109. //if (buffer == null) {
  110. //calc();
  111. //}
  112. final Map desktopHints = (Map) Toolkit.getDefaultToolkit().
  113. getDesktopProperty("awt.font.desktophints");
  114. if (desktopHints != null) {
  115. g.addRenderingHints(desktopHints);
  116. }
  117. g.setColor(textPane.getBackground());
  118. g.fill(g.getClipBounds());
  119. paintOntoGraphics(g);
  120. //g.drawImage(buffer, 0, 0, null);
  121. }
  122. /**
  123. * Re calculates positions of lines and repaints if required.
  124. */
  125. protected void recalc() {
  126. buffer = null;
  127. if (isVisible()) {
  128. repaint();
  129. }
  130. }
  131. /**
  132. * Calculates the position of the lines and highlights.
  133. */
  134. protected void calc() {
  135. final BufferedImage image = new BufferedImage(getWidth(), getHeight(),
  136. BufferedImage.TYPE_INT_ARGB);
  137. final Graphics2D g = image.createGraphics();
  138. paintOntoGraphics(g);
  139. buffer = new BufferedImage(getWidth(), getHeight(),
  140. BufferedImage.TYPE_INT_ARGB);
  141. buffer.createGraphics().drawImage(image, null, null);
  142. }
  143. private void paintOntoGraphics(final Graphics2D g) {
  144. final Map desktopHints = (Map) Toolkit.getDefaultToolkit().
  145. getDesktopProperty("awt.font.desktophints");
  146. if (desktopHints != null) {
  147. g.addRenderingHints(desktopHints);
  148. }
  149. final float formatWidth = getWidth() - DOUBLE_SIDE_PADDING;
  150. final float formatHeight = getHeight();
  151. float drawPosY = formatHeight;
  152. int paragraphStart;
  153. int paragraphEnd;
  154. LineBreakMeasurer lineMeasurer;
  155. g.setColor(textPane.getBackground());
  156. g.fill(getBounds());
  157. textLayouts.clear();
  158. positions.clear();
  159. //check theres something to draw and theres some space to draw in
  160. if (document.getNumLines() == 0 || formatWidth < 1) {
  161. setCursor(Cursor.getDefaultCursor());
  162. return;
  163. }
  164. // Check the start line is in range
  165. if (startLine >= document.getNumLines()) {
  166. startLine = document.getNumLines() - 1;
  167. }
  168. if (startLine <= 0) {
  169. startLine = 0;
  170. }
  171. //sets the last visible line
  172. lastVisibleLine = startLine;
  173. firstVisibleLine = startLine;
  174. // Iterate through the lines
  175. for (int line = startLine; line >= 0; line--) {
  176. float drawPosX;
  177. final AttributedCharacterIterator iterator = document.getStyledLine(
  178. line);
  179. int lineHeight = document.getLineHeight(line);
  180. lineHeight += lineHeight * 0.2;
  181. paragraphStart = iterator.getBeginIndex();
  182. paragraphEnd = iterator.getEndIndex();
  183. lineMeasurer = new LineBreakMeasurer(iterator,
  184. g.getFontRenderContext());
  185. lineMeasurer.setPosition(paragraphStart);
  186. final int wrappedLine = getNumWrappedLines(lineMeasurer,
  187. paragraphStart, paragraphEnd,
  188. formatWidth);
  189. if (wrappedLine > 1) {
  190. drawPosY -= lineHeight * wrappedLine;
  191. }
  192. if (line == startLine) {
  193. drawPosY += DOUBLE_SIDE_PADDING;
  194. }
  195. int numberOfWraps = 0;
  196. int chars = 0;
  197. // Loop through each wrapped line
  198. while (lineMeasurer.getPosition() < paragraphEnd) {
  199. final TextLayout layout = lineMeasurer.nextLayout(formatWidth);
  200. // Calculate the Y offset
  201. if (wrappedLine == 1) {
  202. drawPosY -= lineHeight;
  203. } else if (numberOfWraps != 0) {
  204. drawPosY += lineHeight;
  205. }
  206. // Calculate the initial X position
  207. if (layout.isLeftToRight()) {
  208. drawPosX = SINGLE_SIDE_PADDING;
  209. } else {
  210. drawPosX = formatWidth - layout.getAdvance();
  211. }
  212. // Check if the target is in range
  213. if (drawPosY >= 0 || drawPosY <= formatHeight) {
  214. g.setColor(textPane.getForeground());
  215. layout.draw(g, drawPosX, drawPosY + layout.getDescent());
  216. doHighlight(line, chars, layout, g, drawPosY, drawPosX);
  217. firstVisibleLine = line;
  218. textLayouts.put(layout, new LineInfo(line, numberOfWraps));
  219. positions.put(new Rectangle(0, (int) (drawPosY -
  220. layout.getDescent()), (int) (formatWidth +
  221. DOUBLE_SIDE_PADDING), lineHeight), layout);
  222. }
  223. numberOfWraps++;
  224. chars += layout.getCharacterCount();
  225. }
  226. if (numberOfWraps > 1) {
  227. drawPosY -= lineHeight * (wrappedLine - 1);
  228. }
  229. if (drawPosY <= 0) {
  230. break;
  231. }
  232. }
  233. checkForLink();
  234. }
  235. /**
  236. * Returns the number of timesa line will wrap.
  237. *
  238. * @param lineMeasurer LineBreakMeasurer to work out wrapping for
  239. * @param paragraphStart Start index of the paragraph
  240. * @param paragraphEnd End index of the paragraph
  241. * @param formatWidth Width to wrap at
  242. *
  243. * @return Number of times the line wraps
  244. */
  245. private int getNumWrappedLines(final LineBreakMeasurer lineMeasurer,
  246. final int paragraphStart,
  247. final int paragraphEnd,
  248. final float formatWidth) {
  249. int wrappedLine = 0;
  250. while (lineMeasurer.getPosition() < paragraphEnd) {
  251. lineMeasurer.nextLayout(formatWidth);
  252. wrappedLine++;
  253. }
  254. lineMeasurer.setPosition(paragraphStart);
  255. return wrappedLine;
  256. }
  257. /**
  258. * Redraws the text that has been highlighted.
  259. *
  260. * @param line Line number
  261. * @param chars Number of characters so far in the line
  262. * @param layout Current line textlayout
  263. * @param g Graphics surface to draw highlight on
  264. * @param drawPosY current y location of the line
  265. * @param drawPosX current x location of the line
  266. */
  267. private void doHighlight(final int line, final int chars,
  268. final TextLayout layout, final Graphics2D g,
  269. final float drawPosY, final float drawPosX) {
  270. int selectionStartLine;
  271. int selectionStartChar;
  272. int selectionEndLine;
  273. int selectionEndChar;
  274. if (selection.getStartLine() > selection.getEndLine()) {
  275. // Swap both
  276. selectionStartLine = selection.getEndLine();
  277. selectionStartChar = selection.getEndPos();
  278. selectionEndLine = selection.getStartLine();
  279. selectionEndChar = selection.getStartPos();
  280. } else if (selection.getStartLine() == selection.getEndLine() &&
  281. selection.getStartPos() > selection.getEndPos()) {
  282. // Just swap the chars
  283. selectionStartLine = selection.getStartLine();
  284. selectionStartChar = selection.getEndPos();
  285. selectionEndLine = selection.getEndLine();
  286. selectionEndChar = selection.getStartPos();
  287. } else {
  288. // Swap nothing
  289. selectionStartLine = selection.getStartLine();
  290. selectionStartChar = selection.getStartPos();
  291. selectionEndLine = selection.getEndLine();
  292. selectionEndChar = selection.getEndPos();
  293. }
  294. //Does this line need highlighting?
  295. if (selectionStartLine <= line && selectionEndLine >= line) {
  296. int firstChar;
  297. int lastChar;
  298. // Determine the first char we care about
  299. if (selectionStartLine < line || selectionStartChar < chars) {
  300. firstChar = chars;
  301. } else {
  302. firstChar = selectionStartChar;
  303. }
  304. // ... And the last
  305. if (selectionEndLine > line || selectionEndChar > chars + layout.getCharacterCount()) {
  306. lastChar = chars + layout.getCharacterCount();
  307. } else {
  308. lastChar = selectionEndChar;
  309. }
  310. // If the selection includes the chars we're showing
  311. if (lastChar > chars && firstChar < chars +
  312. layout.getCharacterCount()) {
  313. String text = document.getLine(line).getText();
  314. if (firstChar >= 0 && text.length() > lastChar) {
  315. text = text.substring(firstChar, lastChar);
  316. }
  317. if (text.isEmpty()) {
  318. return;
  319. }
  320. final AttributedCharacterIterator iterator = document.
  321. getStyledLine(line);
  322. int lineHeight = document.getLineHeight(line);
  323. lineHeight += lineHeight * 0.2;
  324. final AttributedString as = new AttributedString(iterator,
  325. firstChar,
  326. lastChar);
  327. as.addAttribute(TextAttribute.FOREGROUND,
  328. textPane.getBackground());
  329. as.addAttribute(TextAttribute.BACKGROUND,
  330. textPane.getForeground());
  331. final TextLayout newLayout = new TextLayout(as.getIterator(),
  332. g.getFontRenderContext());
  333. final Shape shape = layout.getLogicalHighlightShape(firstChar -
  334. chars,
  335. lastChar -
  336. chars);
  337. final int trans = (int) (newLayout.getDescent() + drawPosY);
  338. if (firstChar != 0) {
  339. g.translate(shape.getBounds().getX(), 0);
  340. }
  341. newLayout.draw(g, drawPosX, trans);
  342. if (firstChar != 0) {
  343. g.translate(-1 * shape.getBounds().getX(), 0);
  344. }
  345. }
  346. }
  347. }
  348. /**
  349. * {@{@inheritDoc}
  350. *
  351. * @param e Adjustment event
  352. */
  353. @Override
  354. public void adjustmentValueChanged(final AdjustmentEvent e) {
  355. if (startLine != e.getValue()) {
  356. startLine = e.getValue();
  357. recalc();
  358. }
  359. }
  360. /**
  361. * {@inheritDoc}
  362. *
  363. * @param e Mouse event
  364. */
  365. @Override
  366. public void mouseClicked(final MouseEvent e) {
  367. String clickedText = "";
  368. final int start;
  369. final int end;
  370. final LineInfo lineInfo = getClickPosition(getMousePosition());
  371. if (lineInfo.getLine() != -1) {
  372. clickedText = document.getLine(lineInfo.getLine()).getText();
  373. if (lineInfo.getIndex() == -1) {
  374. start = -1;
  375. end = -1;
  376. } else {
  377. final int[] extent =
  378. getSurroundingWordIndexes(clickedText,
  379. lineInfo.getIndex());
  380. start = extent[0];
  381. end = extent[1];
  382. }
  383. if (e.getClickCount() == 2) {
  384. selection.setStartLine(lineInfo.getLine());
  385. selection.setEndLine(lineInfo.getLine());
  386. selection.setStartPos(start);
  387. selection.setEndPos(end);
  388. } else if (e.getClickCount() == 3) {
  389. selection.setStartLine(lineInfo.getLine());
  390. selection.setEndLine(lineInfo.getLine());
  391. selection.setStartPos(0);
  392. selection.setEndPos(clickedText.length());
  393. }
  394. }
  395. e.setSource(textPane);
  396. textPane.dispatchEvent(e);
  397. }
  398. /**
  399. * Returns the type of text this click represents.
  400. *
  401. * @param lineInfo Line info of click.
  402. *
  403. * @return Click type for specified position
  404. */
  405. public ClickType getClickType(final LineInfo lineInfo) {
  406. if (lineInfo.getLine() != -1) {
  407. final AttributedCharacterIterator iterator = document.getStyledLine(
  408. lineInfo.getLine());
  409. final int index = lineInfo.getIndex();
  410. if (index >= iterator.getBeginIndex() && index <= iterator.
  411. getEndIndex()) {
  412. iterator.setIndex(lineInfo.getIndex());
  413. Object linkattr =
  414. iterator.getAttributes().get(IRCTextAttribute.HYPERLINK);
  415. if (linkattr instanceof String) {
  416. return ClickType.HYPERLINK;
  417. }
  418. linkattr =
  419. iterator.getAttributes().get(IRCTextAttribute.CHANNEL);
  420. if (linkattr instanceof String) {
  421. return ClickType.CHANNEL;
  422. }
  423. linkattr = iterator.getAttributes().get(
  424. IRCTextAttribute.NICKNAME);
  425. if (linkattr instanceof String) {
  426. return ClickType.NICKNAME;
  427. }
  428. } else {
  429. return ClickType.NORMAL;
  430. }
  431. }
  432. return ClickType.NORMAL;
  433. }
  434. /**
  435. * Returns the atrriute value for the specified location.
  436. *
  437. * @param lineInfo Specified location
  438. *
  439. * @return Specified value
  440. */
  441. public Object getAttributeValueAtPoint(LineInfo lineInfo) {
  442. if (lineInfo.getLine() != -1) {
  443. final AttributedCharacterIterator iterator = document.getStyledLine(
  444. lineInfo.getLine());
  445. iterator.setIndex(lineInfo.getIndex());
  446. Object linkattr =
  447. iterator.getAttributes().get(IRCTextAttribute.HYPERLINK);
  448. if (linkattr instanceof String) {
  449. return linkattr;
  450. }
  451. linkattr = iterator.getAttributes().get(IRCTextAttribute.CHANNEL);
  452. if (linkattr instanceof String) {
  453. return linkattr;
  454. }
  455. linkattr = iterator.getAttributes().get(IRCTextAttribute.NICKNAME);
  456. if (linkattr instanceof String) {
  457. return linkattr;
  458. }
  459. }
  460. return null;
  461. }
  462. /**
  463. * Returns the indexes for the word surrounding the index in the specified
  464. * string.
  465. *
  466. * @param text Text to get word from
  467. * @param index Index to get surrounding word
  468. *
  469. * @return Indexes of the word surrounding the index (start, end)
  470. */
  471. protected int[] getSurroundingWordIndexes(final String text,
  472. final int index) {
  473. final int start = getSurroundingWordStart(text, index);
  474. final int end = getSurroundingWordEnd(text, index);
  475. if (start < 0 || end > text.length() || start > end) {
  476. return new int[]{0, 0};
  477. }
  478. return new int[]{start, end};
  479. }
  480. /**
  481. * Returns the start index for the word surrounding the index in the
  482. * specified string.
  483. *
  484. * @param text Text to get word from
  485. * @param index Index to get surrounding word
  486. *
  487. * @return Start index of the word surrounding the index
  488. */
  489. private int getSurroundingWordStart(final String text, final int index) {
  490. int start = index;
  491. // Traverse backwards
  492. while (start > 0 && start < text.length() && text.charAt(start) != ' ') {
  493. start--;
  494. }
  495. if (start + 1 < text.length() && text.charAt(start) == ' ') {
  496. start++;
  497. }
  498. return start;
  499. }
  500. /**
  501. * Returns the end index for the word surrounding the index in the
  502. * specified string.
  503. *
  504. * @param text Text to get word from
  505. * @param index Index to get surrounding word
  506. *
  507. * @return End index of the word surrounding the index
  508. */
  509. private int getSurroundingWordEnd(final String text, final int index) {
  510. int end = index;
  511. // And forwards
  512. while (end < text.length() && end > 0 && text.charAt(end) != ' ') {
  513. end++;
  514. }
  515. return end;
  516. }
  517. /**
  518. * {@inheritDoc}
  519. *
  520. * @param e Mouse event
  521. */
  522. @Override
  523. public void mousePressed(final MouseEvent e) {
  524. if (e.getButton() == MouseEvent.BUTTON1) {
  525. highlightEvent(MouseEventType.CLICK, e);
  526. }
  527. e.setSource(textPane);
  528. textPane.dispatchEvent(e);
  529. }
  530. /**
  531. * {@inheritDoc}
  532. *
  533. * @param e Mouse event
  534. */
  535. @Override
  536. public void mouseReleased(final MouseEvent e) {
  537. if (e.getButton() == MouseEvent.BUTTON1) {
  538. highlightEvent(MouseEventType.RELEASE, e);
  539. }
  540. e.setSource(textPane);
  541. textPane.dispatchEvent(e);
  542. }
  543. /**
  544. * {@inheritDoc}
  545. *
  546. * @param e Mouse event
  547. */
  548. @Override
  549. public void mouseDragged(final MouseEvent e) {
  550. if (e.getModifiersEx() == MouseEvent.BUTTON1_DOWN_MASK) {
  551. highlightEvent(MouseEventType.DRAG, e);
  552. }
  553. e.setSource(textPane);
  554. textPane.dispatchEvent(e);
  555. }
  556. /**
  557. * {@inheritDoc}
  558. *
  559. * @param e Mouse event
  560. */
  561. @Override
  562. public void mouseEntered(final MouseEvent e) {
  563. //Ignore
  564. }
  565. /**
  566. * {@inheritDoc}
  567. *
  568. * @param e Mouse event
  569. */
  570. @Override
  571. public void mouseExited(final MouseEvent e) {
  572. //Ignore
  573. }
  574. /**
  575. * {@inheritDoc}
  576. *
  577. * @param e Mouse event
  578. */
  579. @Override
  580. public void mouseMoved(final MouseEvent e) {
  581. checkForLink();
  582. }
  583. /** Checks for a link under the cursor and sets appropriately. */
  584. private void checkForLink() {
  585. final LineInfo lineInfo = getClickPosition(getMousePosition());
  586. if (lineInfo.getLine() != -1 && document.getLine(lineInfo.getLine()) !=
  587. null) {
  588. final AttributedCharacterIterator iterator = document.getStyledLine(
  589. lineInfo.getLine());
  590. if (lineInfo.getIndex() < iterator.getBeginIndex() ||
  591. lineInfo.getIndex() > iterator.getEndIndex()) {
  592. return;
  593. }
  594. iterator.setIndex(lineInfo.getIndex());
  595. Object linkattr =
  596. iterator.getAttributes().get(IRCTextAttribute.HYPERLINK);
  597. if (linkattr instanceof String) {
  598. setCursor(HAND_CURSOR);
  599. return;
  600. }
  601. linkattr = iterator.getAttributes().get(IRCTextAttribute.CHANNEL);
  602. if (linkattr instanceof String) {
  603. setCursor(HAND_CURSOR);
  604. return;
  605. }
  606. linkattr = iterator.getAttributes().get(IRCTextAttribute.NICKNAME);
  607. if (linkattr instanceof String) {
  608. setCursor(HAND_CURSOR);
  609. return;
  610. }
  611. }
  612. if (getCursor() == HAND_CURSOR) {
  613. setCursor(Cursor.getDefaultCursor());
  614. }
  615. }
  616. /**
  617. * Sets the selection for the given event.
  618. *
  619. * @param type mouse event type
  620. * @param e responsible mouse event
  621. */
  622. protected void highlightEvent(final MouseEventType type,
  623. final MouseEvent e) {
  624. if (isVisible()) {
  625. Point point = e.getLocationOnScreen();
  626. SwingUtilities.convertPointFromScreen(point, this);
  627. if (!contains(point)) {
  628. final Rectangle bounds = getBounds();
  629. final Point mousePos = e.getPoint();
  630. if (mousePos.getX() < bounds.getX()) {
  631. point.setLocation(bounds.getX() + SINGLE_SIDE_PADDING,
  632. point.getY());
  633. } else if (mousePos.getX() > (bounds.getX() + bounds.
  634. getWidth())) {
  635. point.setLocation(bounds.getX() + bounds.getWidth() -
  636. SINGLE_SIDE_PADDING,
  637. point.getY());
  638. }
  639. if (mousePos.getY() < bounds.getY()) {
  640. point.setLocation(point.getX(), bounds.getY() +
  641. DOUBLE_SIDE_PADDING);
  642. } else if (mousePos.getY() >
  643. (bounds.getY() + bounds.getHeight())) {
  644. point.setLocation(bounds.getX() + bounds.getWidth() -
  645. SINGLE_SIDE_PADDING, bounds.getY() +
  646. bounds.getHeight() - DOUBLE_SIDE_PADDING);
  647. }
  648. }
  649. final LineInfo info = getClickPosition(point);
  650. if (info.getLine() == -1 && info.getPart() == -1 && contains(point)) {
  651. info.setLine(0);
  652. info.setPart(0);
  653. info.setIndex(0);
  654. }
  655. if (info.getLine() != -1 && info.getPart() != -1) {
  656. if (type == MouseEventType.CLICK) {
  657. selection.setStartLine(info.getLine());
  658. selection.setStartPos(info.getIndex());
  659. }
  660. selection.setEndLine(info.getLine());
  661. selection.setEndPos(info.getIndex());
  662. recalc();
  663. }
  664. }
  665. }
  666. /**
  667. *
  668. * Returns the line information from a mouse click inside the textpane.
  669. *
  670. * @param point mouse position
  671. *
  672. * @return line number, line part, position in whole line
  673. */
  674. public LineInfo getClickPosition(final Point point) {
  675. int lineNumber = -1;
  676. int linePart = -1;
  677. int pos = 0;
  678. if (point != null) {
  679. for (Map.Entry<Rectangle, TextLayout> entry : positions.entrySet()) {
  680. if (entry.getKey().contains(point)) {
  681. lineNumber = textLayouts.get(entry.getValue()).getLine();
  682. linePart = textLayouts.get(entry.getValue()).getPart();
  683. }
  684. }
  685. pos = getHitPosition(lineNumber, linePart, (int) point.getX(),
  686. (int) point.getY());
  687. }
  688. return new LineInfo(lineNumber, linePart, pos);
  689. }
  690. /**
  691. * Returns the character index for a specified line and part for a specific hit position.
  692. *
  693. * @param lineNumber Line number
  694. * @param linePart Line part
  695. * @param x X position
  696. * @param y Y position
  697. *
  698. * @return Hit position
  699. */
  700. private int getHitPosition(final int lineNumber, final int linePart,
  701. final int x, final int y) {
  702. int pos = 0;
  703. for (Map.Entry<Rectangle, TextLayout> entry : positions.entrySet()) {
  704. if (textLayouts.get(entry.getValue()).getLine() == lineNumber) {
  705. if (textLayouts.get(entry.getValue()).getPart() < linePart) {
  706. pos += entry.getValue().getCharacterCount();
  707. } else if (textLayouts.get(entry.getValue()).getPart() ==
  708. linePart) {
  709. final TextHitInfo hit = entry.getValue().hitTestChar(x -
  710. DOUBLE_SIDE_PADDING, y);
  711. pos += hit.getInsertionIndex();
  712. }
  713. }
  714. }
  715. return pos;
  716. }
  717. /**
  718. * Returns the selected range info.
  719. *
  720. * @return Selected range info
  721. */
  722. protected LinePosition getSelectedRange() {
  723. if (selection.getStartLine() > selection.getEndLine()) {
  724. // Swap both
  725. return new LinePosition(selection.getEndLine(),
  726. selection.getEndPos(), selection.getStartLine(),
  727. selection.getStartPos());
  728. } else if (selection.getStartLine() == selection.getEndLine() &&
  729. selection.getStartPos() > selection.getEndPos()) {
  730. // Just swap the chars
  731. return new LinePosition(selection.getStartLine(), selection.
  732. getEndPos(), selection.getEndLine(),
  733. selection.getStartPos());
  734. } else {
  735. // Swap nothing
  736. return new LinePosition(selection.getStartLine(), selection.
  737. getStartPos(), selection.getEndLine(),
  738. selection.getEndPos());
  739. }
  740. }
  741. /** Clears the selection. */
  742. protected void clearSelection() {
  743. selection.setEndLine(selection.getStartLine());
  744. selection.setEndPos(selection.getStartPos());
  745. recalc();
  746. }
  747. /**
  748. * Selects the specified region of text.
  749. *
  750. * @param position Line position
  751. */
  752. public void setSelectedRange(final LinePosition position) {
  753. selection = new LinePosition(position);
  754. recalc();
  755. }
  756. /**
  757. * Returns the first visible line.
  758. *
  759. * @return the line number of the first visible line
  760. */
  761. public int getFirstVisibleLine() {
  762. return firstVisibleLine;
  763. }
  764. /**
  765. * Returns the last visible line.
  766. *
  767. * @return the line number of the last visible line
  768. */
  769. public int getLastVisibleLine() {
  770. return lastVisibleLine;
  771. }
  772. /**
  773. * Returns the number of visible lines.
  774. *
  775. * @return Number of visible lines
  776. */
  777. public int getNumVisibleLines() {
  778. return lastVisibleLine - firstVisibleLine;
  779. }
  780. /**
  781. * {@inheritDoc}
  782. *
  783. * @param e Component event
  784. */
  785. @Override
  786. public void componentResized(final ComponentEvent e) {
  787. recalc();
  788. }
  789. /**
  790. * {@inheritDoc}
  791. *
  792. * @param e Component event
  793. */
  794. @Override
  795. public void componentMoved(final ComponentEvent e) {
  796. //Ignore
  797. }
  798. /**
  799. * {@inheritDoc}
  800. *
  801. * @param e Component event
  802. */
  803. @Override
  804. public void componentShown(final ComponentEvent e) {
  805. //Ignore
  806. }
  807. /**
  808. * {@inheritDoc}
  809. *
  810. * @param e Component event
  811. */
  812. @Override
  813. public void componentHidden(final ComponentEvent e) {
  814. //Ignore
  815. }
  816. }