PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/textarea/Gutter.java

#
Java | 760 lines | 492 code | 97 blank | 171 comment | 76 complexity | 3d32641582214ef0c9bf29ea023a8407 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * Gutter.java
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1999, 2000 mike dillon
  7. * Portions copyright (C) 2001 Slava Pestov
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, write to the Free Software
  21. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. */
  23. package org.gjt.sp.jedit.textarea;
  24. //{{{ Imports
  25. import java.awt.*;
  26. import java.awt.event.*;
  27. import java.lang.reflect.Method;
  28. import java.util.Vector;
  29. import javax.swing.*;
  30. import javax.swing.border.*;
  31. import javax.swing.event.*;
  32. import org.gjt.sp.jedit.*;
  33. import org.gjt.sp.util.Log;
  34. //}}}
  35. public class Gutter extends JComponent implements SwingConstants
  36. {
  37. //{{{ Gutter constructor
  38. public Gutter(View view, JEditTextArea textArea)
  39. {
  40. this.view = view;
  41. this.textArea = textArea;
  42. highlights = new Vector();
  43. setDoubleBuffered(true);
  44. MouseHandler ml = new MouseHandler();
  45. addMouseListener(ml);
  46. addMouseMotionListener(ml);
  47. } //}}}
  48. //{{{ paintComponent() method
  49. public void paintComponent(Graphics gfx)
  50. {
  51. // fill the background
  52. Rectangle clip = gfx.getClipBounds();
  53. gfx.setColor(getBackground());
  54. gfx.fillRect(clip.x, clip.y, clip.width, clip.height);
  55. // if buffer is loading, don't paint anything
  56. if (!textArea.getBuffer().isLoaded())
  57. return;
  58. // paint highlights and line numbers
  59. int lineHeight = textArea.getPainter().getFontMetrics()
  60. .getHeight();
  61. int firstLine = clip.y / lineHeight + textArea.getFirstLine();
  62. int lastLine = (clip.y + clip.height - 1) / lineHeight
  63. + textArea.getFirstLine();
  64. FontMetrics pfm = textArea.getPainter().getFontMetrics();
  65. Color fg = getForeground();
  66. int baseline = (int)((this.baseline + lineHeight
  67. - pfm.getDescent()) / 2.0);
  68. boolean highlightCurrentLine = currentLineHighlightEnabled
  69. && textArea.selection.size() == 0;
  70. int y = (clip.y - clip.y % lineHeight);
  71. Buffer buffer = textArea.getBuffer();
  72. FoldVisibilityManager foldVisibilityManager
  73. = textArea.getFoldVisibilityManager();
  74. int lastValidLine = (lastLine >= foldVisibilityManager.getVirtualLineCount())
  75. ? foldVisibilityManager.getVirtualLineCount() - 1 : lastLine;
  76. for (int line = firstLine; line <= lastLine;
  77. line++, y += lineHeight)
  78. {
  79. boolean valid = (line >= firstLine && line <= lastValidLine);
  80. //{{{ Paint plugin highlights
  81. if(highlights.size() != 0)
  82. {
  83. for(int i = 0; i < highlights.size(); i++)
  84. {
  85. TextAreaHighlight highlight = (TextAreaHighlight)
  86. highlights.elementAt(i);
  87. try
  88. {
  89. highlight.paintHighlight(gfx,line,
  90. y - fm.getLeading() - fm.getDescent());
  91. }
  92. catch(Throwable t)
  93. {
  94. Log.log(Log.ERROR,this,t);
  95. // remove it so editor can continue
  96. // functioning
  97. highlights.removeElementAt(i);
  98. i--;
  99. }
  100. }
  101. } //}}}
  102. if(!valid)
  103. return;
  104. int physicalLine = foldVisibilityManager
  105. .virtualToPhysical(line);
  106. //{{{ Paint fold triangles
  107. if(physicalLine != buffer.getLineCount() - 1
  108. && buffer.isFoldStart(physicalLine))
  109. {
  110. int _y = y + lineHeight / 2;
  111. gfx.setColor(foldColor);
  112. if(foldVisibilityManager.isLineVisible(physicalLine + 1))
  113. {
  114. gfx.drawLine(1,_y - 3,10,_y - 3);
  115. gfx.drawLine(2,_y - 2,9,_y - 2);
  116. gfx.drawLine(3,_y - 1,8,_y - 1);
  117. gfx.drawLine(4,_y,7,_y);
  118. gfx.drawLine(5,_y + 1,6,_y + 1);
  119. }
  120. else
  121. {
  122. gfx.drawLine(4,_y - 5,4,_y + 4);
  123. gfx.drawLine(5,_y - 4,5,_y + 3);
  124. gfx.drawLine(6,_y - 3,6,_y + 2);
  125. gfx.drawLine(7,_y - 2,7,_y + 1);
  126. gfx.drawLine(8,_y - 1,8,_y);
  127. }
  128. } //}}}
  129. //{{{ Paint bracket scope
  130. else if(bracketHighlight)
  131. {
  132. if(textArea.isBracketHighlightVisible())
  133. {
  134. int bracketLine = textArea.getBracketLine();
  135. int caretLine = textArea.getCaretLine();
  136. if(caretLine != bracketLine)
  137. {
  138. if(caretLine > bracketLine)
  139. {
  140. int tmp = caretLine;
  141. caretLine = bracketLine;
  142. bracketLine = tmp;
  143. }
  144. gfx.setColor(bracketHighlightColor);
  145. if(physicalLine == caretLine)
  146. {
  147. gfx.fillRect(5,
  148. y
  149. + lineHeight / 2,
  150. 5,
  151. 2);
  152. gfx.fillRect(5,
  153. y
  154. + lineHeight / 2,
  155. 2,
  156. lineHeight - lineHeight / 2);
  157. }
  158. else if(physicalLine == bracketLine)
  159. {
  160. gfx.fillRect(5,
  161. y,
  162. 2,
  163. lineHeight / 2);
  164. gfx.fillRect(5,
  165. y + lineHeight / 2,
  166. 5,
  167. 2);
  168. }
  169. else if(physicalLine > caretLine
  170. && physicalLine < bracketLine)
  171. {
  172. gfx.fillRect(5,
  173. y,
  174. 2,
  175. lineHeight);
  176. }
  177. }
  178. }
  179. } //}}}
  180. //{{{ Paint line numbers
  181. if(expanded)
  182. {
  183. String number = Integer.toString(physicalLine + 1);
  184. int offset;
  185. switch (alignment)
  186. {
  187. case RIGHT:
  188. offset = gutterSize.width - collapsedSize.width
  189. - (fm.stringWidth(number) + 1);
  190. break;
  191. case CENTER:
  192. offset = ((gutterSize.width - collapsedSize.width)
  193. - fm.stringWidth(number)) / 2;
  194. break;
  195. case LEFT: default:
  196. offset = 0;
  197. break;
  198. }
  199. if (physicalLine == textArea.getCaretLine() && highlightCurrentLine)
  200. {
  201. gfx.setColor(currentLineHighlight);
  202. }
  203. else if (interval > 1 && (line + 1) % interval == 0)
  204. gfx.setColor(intervalHighlight);
  205. else
  206. gfx.setColor(fg);
  207. gfx.drawString(number, FOLD_MARKER_SIZE + offset,
  208. baseline + y);
  209. } //}}}
  210. }
  211. } //}}}
  212. //{{{ addCustomHighlight() method
  213. /**
  214. * Adds a custom highlight painter.
  215. * @param highlight The highlight
  216. */
  217. public void addCustomHighlight(TextAreaHighlight highlight)
  218. {
  219. highlights.addElement(highlight);
  220. // handle old highlighters
  221. Class clazz = highlight.getClass();
  222. try
  223. {
  224. Method method = clazz.getMethod("init",
  225. new Class[] { JEditTextArea.class,
  226. TextAreaHighlight.class });
  227. if(method != null)
  228. {
  229. Log.log(Log.WARNING,this,clazz.getName()
  230. + " uses old highlighter API");
  231. method.invoke(highlight,new Object[] { textArea, null });
  232. }
  233. }
  234. catch(Exception e)
  235. {
  236. // ignore
  237. }
  238. repaint();
  239. } //}}}
  240. //{{{ removeCustomHighlight() method
  241. /**
  242. * Removes a custom highlight painter.
  243. * @param highlight The highlight
  244. * @since jEdit 4.0pre1
  245. */
  246. public void removeCustomHighlight(TextAreaHighlight highlight)
  247. {
  248. highlights.removeElement(highlight);
  249. repaint();
  250. } //}}}
  251. //{{{ setBorder() method
  252. /**
  253. * Convenience method for setting a default matte border on the right
  254. * with the specified border width and color
  255. * @param width The border width (in pixels)
  256. * @param color1 The focused border color
  257. * @param color2 The unfocused border color
  258. * @param color3 The gutter/text area gap color
  259. */
  260. public void setBorder(int width, Color color1, Color color2, Color color3)
  261. {
  262. this.borderWidth = width;
  263. focusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
  264. new MatteBorder(0,0,0,width,color1));
  265. noFocusBorder = new CompoundBorder(new MatteBorder(0,0,0,width,color3),
  266. new MatteBorder(0,0,0,width,color2));
  267. updateBorder();
  268. } //}}}
  269. //{{{ updateBorder() method
  270. /**
  271. * Sets the border differently if the text area has focus or not.
  272. */
  273. public void updateBorder()
  274. {
  275. // because we are called from the text area's focus handler,
  276. // we do an invokeLater() so that the view's focus handler
  277. // has a chance to execute and set the edit pane properly
  278. SwingUtilities.invokeLater(new Runnable()
  279. {
  280. public void run()
  281. {
  282. if(view.getEditPane() == null)
  283. return;
  284. if(view.getEditPane().getTextArea() == textArea)
  285. setBorder(focusBorder);
  286. else
  287. setBorder(noFocusBorder);
  288. }
  289. });
  290. } //}}}
  291. //{{{ setBorder() method
  292. /*
  293. * JComponent.setBorder(Border) is overridden here to cache the left
  294. * inset of the border (if any) to avoid having to fetch it during every
  295. * repaint.
  296. */
  297. public void setBorder(Border border)
  298. {
  299. super.setBorder(border);
  300. if (border == null)
  301. {
  302. collapsedSize.width = 0;
  303. collapsedSize.height = 0;
  304. }
  305. else
  306. {
  307. Insets insets = border.getBorderInsets(this);
  308. collapsedSize.width = FOLD_MARKER_SIZE + insets.right;
  309. collapsedSize.height = gutterSize.height
  310. = insets.top + insets.bottom;
  311. gutterSize.width = FOLD_MARKER_SIZE + insets.right
  312. + fm.stringWidth("12345");
  313. }
  314. revalidate();
  315. } //}}}
  316. //{{{ setFont() method
  317. /*
  318. * JComponent.setFont(Font) is overridden here to cache the baseline for
  319. * the font. This avoids having to get the font metrics during every
  320. * repaint.
  321. */
  322. public void setFont(Font font)
  323. {
  324. super.setFont(font);
  325. fm = getFontMetrics(font);
  326. baseline = fm.getAscent();
  327. Border border = getBorder();
  328. if(border != null)
  329. {
  330. gutterSize.width = FOLD_MARKER_SIZE
  331. + border.getBorderInsets(this).right
  332. + fm.stringWidth("12345");
  333. revalidate();
  334. }
  335. } //}}}
  336. //{{{ Getters and setters
  337. //{{{ getHighlightedForeground() method
  338. /**
  339. * Get the foreground color for highlighted line numbers
  340. * @return The highlight color
  341. */
  342. public Color getHighlightedForeground()
  343. {
  344. return intervalHighlight;
  345. } //}}}
  346. //{{{ setHighlightedForeground() method
  347. public void setHighlightedForeground(Color highlight)
  348. {
  349. intervalHighlight = highlight;
  350. } //}}}
  351. //{{{ getCurrentLineForeground() method
  352. public Color getCurrentLineForeground()
  353. {
  354. return currentLineHighlight;
  355. } //}}}
  356. //{{{ setCurrentLineForeground() method
  357. public void setCurrentLineForeground(Color highlight)
  358. {
  359. currentLineHighlight = highlight;
  360. } //}}}
  361. //{{{ getFoldColor() method
  362. public Color getFoldColor()
  363. {
  364. return foldColor;
  365. } //}}}
  366. //{{{ setFoldColor() method
  367. public void setFoldColor(Color foldColor)
  368. {
  369. this.foldColor = foldColor;
  370. } //}}}
  371. //{{{ getPreferredSize() method
  372. /*
  373. * Component.getPreferredSize() is overridden here to support the
  374. * collapsing behavior.
  375. */
  376. public Dimension getPreferredSize()
  377. {
  378. if (expanded)
  379. return gutterSize;
  380. else
  381. return collapsedSize;
  382. } //}}}
  383. //{{{ getMinimumSize() method
  384. public Dimension getMinimumSize()
  385. {
  386. return getPreferredSize();
  387. } //}}}
  388. //{{{ getToolTipText() method
  389. public String getToolTipText(MouseEvent evt)
  390. {
  391. for(int i = 0; i < highlights.size(); i++)
  392. {
  393. TextAreaHighlight highlight =
  394. (TextAreaHighlight)
  395. highlights.elementAt(i);
  396. String toolTip = highlight.getToolTipText(evt);
  397. if(toolTip != null)
  398. return toolTip;
  399. }
  400. return null;
  401. } //}}}
  402. //{{{ getLineNumberAlignment() method
  403. /**
  404. * Identifies whether the horizontal alignment of the line numbers.
  405. * @return Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
  406. */
  407. public int getLineNumberAlignment()
  408. {
  409. return alignment;
  410. } //}}}
  411. //{{{ setLineNumberAlignment() method
  412. /**
  413. * Sets the horizontal alignment of the line numbers.
  414. * @param alignment Gutter.RIGHT, Gutter.CENTER, Gutter.LEFT
  415. */
  416. public void setLineNumberAlignment(int alignment)
  417. {
  418. if (this.alignment == alignment) return;
  419. this.alignment = alignment;
  420. repaint();
  421. } //}}}
  422. //{{{ isExpanded() method
  423. /**
  424. * Identifies whether the gutter is collapsed or expanded.
  425. * @return true if the gutter is expanded, false if it is collapsed
  426. */
  427. public boolean isExpanded()
  428. {
  429. return expanded;
  430. } //}}}
  431. //{{{ setExpanded() method
  432. /**
  433. * Sets whether the gutter is collapsed or expanded and force the text
  434. * area to update its layout if there is a change.
  435. * @param collapsed true if the gutter is expanded,
  436. * false if it is collapsed
  437. */
  438. public void setExpanded(boolean expanded)
  439. {
  440. if (this.expanded == expanded) return;
  441. this.expanded = expanded;
  442. textArea.revalidate();
  443. } //}}}
  444. //{{{ toggleExpanded() method
  445. /**
  446. * Toggles whether the gutter is collapsed or expanded.
  447. */
  448. public void toggleExpanded()
  449. {
  450. setExpanded(!expanded);
  451. } //}}}
  452. //{{{ getHighlightInterval() method
  453. /**
  454. * Sets the number of lines between highlighted line numbers.
  455. * @return The number of lines between highlighted line numbers or
  456. * zero if highlighting is disabled
  457. */
  458. public int getHighlightInterval()
  459. {
  460. return interval;
  461. } //}}}
  462. //{{{ setHighlightInterval() method
  463. /**
  464. * Sets the number of lines between highlighted line numbers. Any value
  465. * less than or equal to one will result in highlighting being disabled.
  466. * @param interval The number of lines between highlighted line numbers
  467. */
  468. public void setHighlightInterval(int interval)
  469. {
  470. if (interval <= 1) interval = 0;
  471. this.interval = interval;
  472. repaint();
  473. } //}}}
  474. //{{{ isCurrentLineHighlightEnabled() method
  475. public boolean isCurrentLineHighlightEnabled()
  476. {
  477. return currentLineHighlightEnabled;
  478. } //}}}
  479. //{{{ setCurrentLineHighlightEnabled() method
  480. public void setCurrentLineHighlightEnabled(boolean enabled)
  481. {
  482. if (currentLineHighlightEnabled == enabled) return;
  483. currentLineHighlightEnabled = enabled;
  484. repaint();
  485. } //}}}
  486. //{{{ getBracketHighlightColor() method
  487. /**
  488. * Returns the bracket highlight color.
  489. */
  490. public final Color getBracketHighlightColor()
  491. {
  492. return bracketHighlightColor;
  493. } //}}}
  494. //{{{ setBracketHighlightColor() method
  495. /**
  496. * Sets the bracket highlight color.
  497. * @param bracketHighlightColor The bracket highlight color
  498. * @since jEdit 4.0pre1
  499. */
  500. public final void setBracketHighlightColor(Color bracketHighlightColor)
  501. {
  502. this.bracketHighlightColor = bracketHighlightColor;
  503. repaint();
  504. } //}}}
  505. //{{{ isBracketHighlightEnabled() method
  506. /**
  507. * Returns true if bracket highlighting is enabled, false otherwise.
  508. * When bracket highlighting is enabled, the bracket matching the
  509. * one before the caret (if any) is highlighted.
  510. * @since jEdit 4.0pre1
  511. */
  512. public final boolean isBracketHighlightEnabled()
  513. {
  514. return bracketHighlight;
  515. } //}}}
  516. //{{{ setBracketHighlightEnabled() method
  517. /**
  518. * Enables or disables bracket highlighting.
  519. * When bracket highlighting is enabled, the bracket matching the
  520. * one before the caret (if any) is highlighted.
  521. * @param bracketHighlight True if bracket highlighting should be
  522. * enabled, false otherwise
  523. * @since jEdit 4.0pre1
  524. */
  525. public final void setBracketHighlightEnabled(boolean bracketHighlight)
  526. {
  527. this.bracketHighlight = bracketHighlight;
  528. repaint();
  529. } //}}}
  530. //}}}
  531. //{{{ Private members
  532. private static final int FOLD_MARKER_SIZE = 12;
  533. private View view;
  534. private JEditTextArea textArea;
  535. private Vector highlights;
  536. private int baseline;
  537. private Dimension gutterSize = new Dimension(0,0);
  538. private Dimension collapsedSize = new Dimension(0,0);
  539. private Color intervalHighlight;
  540. private Color currentLineHighlight;
  541. private Color foldColor;
  542. private FontMetrics fm;
  543. private int alignment;
  544. private int interval;
  545. private boolean currentLineHighlightEnabled;
  546. private boolean expanded;
  547. private boolean bracketHighlight;
  548. private Color bracketHighlightColor;
  549. private int borderWidth;
  550. private Border focusBorder, noFocusBorder;
  551. //}}}
  552. //{{{ MouseHandler class
  553. class MouseHandler extends MouseInputAdapter
  554. {
  555. boolean drag;
  556. int toolTipInitialDelay, toolTipReshowDelay;
  557. //{{{ mouseEntered() method
  558. public void mouseEntered(MouseEvent e)
  559. {
  560. ToolTipManager ttm = ToolTipManager.sharedInstance();
  561. toolTipInitialDelay = ttm.getInitialDelay();
  562. toolTipReshowDelay = ttm.getReshowDelay();
  563. ttm.setInitialDelay(0);
  564. ttm.setReshowDelay(0);
  565. } //}}}
  566. //{{{ mouseExited() method
  567. public void mouseExited(MouseEvent evt)
  568. {
  569. ToolTipManager ttm = ToolTipManager.sharedInstance();
  570. ttm.setInitialDelay(toolTipInitialDelay);
  571. ttm.setReshowDelay(toolTipReshowDelay);
  572. } //}}}
  573. //{{{ mousePressed() method
  574. public void mousePressed(MouseEvent e)
  575. {
  576. if(e.getX() < getWidth() - borderWidth * 2)
  577. {
  578. Buffer buffer = textArea.getBuffer();
  579. int line = e.getY() / textArea.getPainter()
  580. .getFontMetrics().getHeight()
  581. + textArea.getFirstLine();
  582. FoldVisibilityManager foldVisibilityManager
  583. = textArea.getFoldVisibilityManager();
  584. if(line > foldVisibilityManager.getVirtualLineCount() - 1)
  585. return;
  586. line = foldVisibilityManager.virtualToPhysical(line);
  587. //{{{ Clicking on fold triangle does various things
  588. if(buffer.isFoldStart(line))
  589. {
  590. if(e.isControlDown())
  591. {
  592. foldVisibilityManager
  593. .expandFold(line,true);
  594. textArea.selectFold(line);
  595. }
  596. else if(foldVisibilityManager
  597. .isLineVisible(line + 1))
  598. {
  599. foldVisibilityManager
  600. .collapseFold(line);
  601. }
  602. else
  603. {
  604. foldVisibilityManager
  605. .expandFold(line,
  606. e.isShiftDown());
  607. }
  608. } //}}}
  609. //{{{ Clicking in bracket scope locates matching bracket
  610. else if(bracketHighlight)
  611. {
  612. if(textArea.isBracketHighlightVisible())
  613. {
  614. int bracketLine = textArea.getBracketLine();
  615. int caretLine = textArea.getCaretLine();
  616. if(caretLine != bracketLine)
  617. {
  618. if(caretLine > bracketLine)
  619. {
  620. int tmp = caretLine;
  621. caretLine = bracketLine;
  622. bracketLine = tmp;
  623. }
  624. if(line >= caretLine
  625. && line <= bracketLine)
  626. {
  627. if(e.isControlDown())
  628. textArea.selectToMatchingBracket();
  629. else
  630. textArea.goToMatchingBracket();
  631. }
  632. }
  633. }
  634. } //}}}
  635. }
  636. else
  637. {
  638. e.translatePoint(-getWidth(),0);
  639. textArea.mouseHandler.mousePressed(e);
  640. drag = true;
  641. }
  642. } //}}}
  643. //{{{ mouseDragged() method
  644. public void mouseDragged(MouseEvent e)
  645. {
  646. if(drag && e.getX() >= getWidth() - borderWidth * 2)
  647. {
  648. e.translatePoint(-getWidth(),0);
  649. textArea.mouseHandler.mouseDragged(e);
  650. }
  651. } //}}}
  652. //{{{ mouseReleased() method
  653. public void mouseReleased(MouseEvent e)
  654. {
  655. if(drag && e.getX() >= getWidth() - borderWidth * 2)
  656. {
  657. e.translatePoint(-getWidth(),0);
  658. textArea.mouseHandler.mouseReleased(e);
  659. }
  660. drag = false;
  661. } //}}}
  662. } //}}}
  663. }