PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/projects/netbeans-7.3/editor.lib/src/org/netbeans/editor/CodeFoldingSideBar.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 1223 lines | 826 code | 137 blank | 260 comment | 180 complexity | 24eb67d411633d07c0c31a8ef972c0f4 MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.editor;
  45. import java.awt.BasicStroke;
  46. import java.awt.Color;
  47. import java.awt.Dimension;
  48. import java.awt.Font;
  49. import java.awt.FontMetrics;
  50. import java.awt.Graphics;
  51. import java.awt.Graphics2D;
  52. import java.awt.Point;
  53. import java.awt.Rectangle;
  54. import java.awt.Stroke;
  55. import java.awt.event.MouseAdapter;
  56. import java.awt.event.MouseEvent;
  57. import java.util.ArrayList;
  58. import java.util.Collections;
  59. import java.util.List;
  60. import java.util.Map;
  61. import java.util.NavigableMap;
  62. import java.util.TreeMap;
  63. import java.util.logging.Level;
  64. import java.util.logging.Logger;
  65. import java.util.prefs.PreferenceChangeEvent;
  66. import java.util.prefs.PreferenceChangeListener;
  67. import java.util.prefs.Preferences;
  68. import javax.accessibility.Accessible;
  69. import javax.accessibility.AccessibleContext;
  70. import javax.accessibility.AccessibleRole;
  71. import javax.swing.JComponent;
  72. import javax.swing.SwingUtilities;
  73. import javax.swing.event.DocumentEvent;
  74. import javax.swing.event.DocumentListener;
  75. import javax.swing.text.AbstractDocument;
  76. import javax.swing.text.AttributeSet;
  77. import javax.swing.text.BadLocationException;
  78. import javax.swing.text.Document;
  79. import javax.swing.text.JTextComponent;
  80. import javax.swing.text.View;
  81. import org.netbeans.api.editor.fold.Fold;
  82. import org.netbeans.api.editor.fold.FoldHierarchy;
  83. import org.netbeans.api.editor.fold.FoldHierarchyEvent;
  84. import org.netbeans.api.editor.fold.FoldHierarchyListener;
  85. import org.netbeans.api.editor.fold.FoldUtilities;
  86. import org.netbeans.api.editor.mimelookup.MimeLookup;
  87. import org.netbeans.api.editor.settings.AttributesUtilities;
  88. import org.netbeans.api.editor.settings.FontColorNames;
  89. import org.netbeans.api.editor.settings.FontColorSettings;
  90. import org.netbeans.api.editor.settings.SimpleValueNames;
  91. import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
  92. import org.netbeans.modules.editor.lib.SettingsConversions;
  93. import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
  94. import org.netbeans.modules.editor.lib2.view.ParagraphViewDescriptor;
  95. import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
  96. import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
  97. import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
  98. import org.openide.util.Lookup;
  99. import org.openide.util.LookupEvent;
  100. import org.openide.util.LookupListener;
  101. import org.openide.util.NbBundle;
  102. import org.openide.util.WeakListeners;
  103. /**
  104. * Code Folding Side Bar. Component responsible for drawing folding signs and responding
  105. * on user fold/unfold action.
  106. *
  107. * @author Martin Roskanin
  108. */
  109. public class CodeFoldingSideBar extends JComponent implements Accessible {
  110. private static final Logger LOG = Logger.getLogger(CodeFoldingSideBar.class.getName());
  111. /** This field should be treated as final. Subclasses are forbidden to change it.
  112. * @deprecated Without any replacement.
  113. */
  114. protected Color backColor;
  115. /** This field should be treated as final. Subclasses are forbidden to change it.
  116. * @deprecated Without any replacement.
  117. */
  118. protected Color foreColor;
  119. /** This field should be treated as final. Subclasses are forbidden to change it.
  120. * @deprecated Without any replacement.
  121. */
  122. protected Font font;
  123. /** This field should be treated as final. Subclasses are forbidden to change it. */
  124. protected /*final*/ JTextComponent component;
  125. private volatile AttributeSet attribs;
  126. private Lookup.Result<? extends FontColorSettings> fcsLookupResult;
  127. private final LookupListener fcsTracker = new LookupListener() {
  128. public void resultChanged(LookupEvent ev) {
  129. attribs = null;
  130. SwingUtilities.invokeLater(new Runnable() {
  131. public void run() {
  132. //EMI: This is needed as maybe the DEFAULT_COLORING is changed, the font is different
  133. // and while getMarkSize() is used in paint() and will make the artifacts bigger,
  134. // the component itself will be the same size and it must be changed.
  135. // See http://www.netbeans.org/issues/show_bug.cgi?id=153316
  136. updatePreferredSize();
  137. CodeFoldingSideBar.this.repaint();
  138. }
  139. });
  140. }
  141. };
  142. private final Listener listener = new Listener();
  143. private boolean enabled = false;
  144. protected List<Mark> visibleMarks = new ArrayList<Mark>();
  145. /**
  146. * Mouse moved point, possibly {@code null}. Set from mouse-moved, mouse-entered
  147. * handlers, so that painting will paint this fold in bold. -1, if mouse is not
  148. * in the sidebar region. The value is used to compute highlighted portions of the
  149. * folding outline.
  150. */
  151. private int mousePoint = -1;
  152. /**
  153. * if true, the {@link #mousePoint} has been already used to make a PaintInfo active.
  154. * The flag is tested by {@link #traverseForward} and {@link #traverseBackward} after children
  155. * of the current fold are processed and cleared if the {@link #mousePoint} falls to the fold area -
  156. * fields of PaintInfo are set accordingly.
  157. * It's also used to compute (current) mouseBoundary, so mouse movement does not trigger
  158. * refreshes eagerly
  159. */
  160. private boolean mousePointConsumed;
  161. /**
  162. * Boundaries of the current area under the mouse. Can be eiher the span of the
  163. * current fold (or part of it), or the span not occupied by any fold. Serves as an optimization
  164. * for mouse handler, which does not trigger painting (refresh) unless mouse
  165. * leaves this region.
  166. */
  167. private Rectangle mouseBoundary;
  168. /**
  169. * Y-end of the nearest fold that ends above the {@link #mousePoint}. Undefined if mousePoint is null.
  170. * These two variables are initialized at each level of folds, and help to compute {@link #mouseBoundary} for
  171. * the case the mousePointer is OUTSIDE all children (or outside all folds).
  172. */
  173. private int lowestAboveMouse = -1;
  174. /**
  175. * Y-begin of the nearest fold, which starts below the {@link #mousePoint}. Undefined if mousePoint is null
  176. */
  177. private int topmostBelowMouse = Integer.MAX_VALUE;
  178. /** Paint operations */
  179. public static final int PAINT_NOOP = 0;
  180. /**
  181. * Normal opening +- marker
  182. */
  183. public static final int PAINT_MARK = 1;
  184. /**
  185. * Vertical line - typically at the end of the screen
  186. */
  187. public static final int PAINT_LINE = 2;
  188. /**
  189. * End angled line, without a sign
  190. */
  191. public static final int PAINT_END_MARK = 3;
  192. /**
  193. * Single-line marker, both start and end
  194. */
  195. public static final int SINGLE_PAINT_MARK = 4;
  196. /**
  197. * Marker value for {@link #mousePoint} indicating that mouse is outside the Component.
  198. */
  199. private static final int NO_MOUSE_POINT = -1;
  200. /**
  201. * Stroke used to draw inactive (regular) fold outlines.
  202. */
  203. private static Stroke LINE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
  204. 1f, new float[] { 1f, 1f }, 0f);
  205. /**
  206. * Stroke used to draw outlines for 'active' fold
  207. */
  208. private static final Stroke LINE_BOLD = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
  209. private final Preferences prefs;
  210. private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
  211. public void preferenceChange(PreferenceChangeEvent evt) {
  212. String key = evt == null ? null : evt.getKey();
  213. if (key == null || SimpleValueNames.CODE_FOLDING_ENABLE.equals(key)) {
  214. updateColors();
  215. boolean newEnabled = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, EditorPreferencesDefaults.defaultCodeFoldingEnable);
  216. if (enabled != newEnabled) {
  217. enabled = newEnabled;
  218. updatePreferredSize();
  219. }
  220. }
  221. SettingsConversions.callSettingsChange(CodeFoldingSideBar.this);
  222. }
  223. };
  224. private void checkRepaint(ViewHierarchyEvent vhe) {
  225. if (!vhe.isChangeY()) {
  226. // does not obscur sidebar graphics
  227. return;
  228. }
  229. SwingUtilities.invokeLater(new Runnable() {
  230. public void run() {
  231. updatePreferredSize();
  232. CodeFoldingSideBar.this.repaint();
  233. }
  234. });
  235. }
  236. /**
  237. * @deprecated Don't use this constructor, it does nothing!
  238. */
  239. public CodeFoldingSideBar() {
  240. component = null;
  241. prefs = null;
  242. throw new IllegalStateException("Do not use this constructor!"); //NOI18N
  243. }
  244. public CodeFoldingSideBar(JTextComponent component){
  245. super();
  246. this.component = component;
  247. addMouseListener(listener);
  248. addMouseMotionListener(listener);
  249. FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
  250. foldHierarchy.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, listener, foldHierarchy));
  251. Document doc = getDocument();
  252. doc.addDocumentListener(WeakListeners.document(listener, doc));
  253. setOpaque(true);
  254. prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
  255. prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, prefsListener, prefs));
  256. prefsListener.preferenceChange(null);
  257. ViewHierarchy.get(component).addViewHierarchyListener(new ViewHierarchyListener() {
  258. @Override
  259. public void viewHierarchyChanged(ViewHierarchyEvent evt) {
  260. checkRepaint(evt);
  261. }
  262. });
  263. }
  264. private void updatePreferredSize() {
  265. if (enabled) {
  266. setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
  267. setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
  268. }else{
  269. setPreferredSize(new Dimension(0,0));
  270. setMaximumSize(new Dimension(0,0));
  271. }
  272. revalidate();
  273. }
  274. private void updateColors() {
  275. Coloring c = getColoring();
  276. this.backColor = c.getBackColor();
  277. this.foreColor = c.getForeColor();
  278. this.font = c.getFont();
  279. }
  280. /**
  281. * This method should be treated as final. Subclasses are forbidden to override it.
  282. * @return The background color used for painting this component.
  283. * @deprecated Without any replacement.
  284. */
  285. protected Color getBackColor() {
  286. if (backColor == null) {
  287. updateColors();
  288. }
  289. return backColor;
  290. }
  291. /**
  292. * This method should be treated as final. Subclasses are forbidden to override it.
  293. * @return The foreground color used for painting this component.
  294. * @deprecated Without any replacement.
  295. */
  296. protected Color getForeColor() {
  297. if (foreColor == null) {
  298. updateColors();
  299. }
  300. return foreColor;
  301. }
  302. /**
  303. * This method should be treated as final. Subclasses are forbidden to override it.
  304. * @return The font used for painting this component.
  305. * @deprecated Without any replacement.
  306. */
  307. protected Font getColoringFont() {
  308. if (font == null) {
  309. updateColors();
  310. }
  311. return font;
  312. }
  313. // overriding due to issue #60304
  314. public @Override void update(Graphics g) {
  315. }
  316. protected void collectPaintInfos(
  317. View rootView, Fold fold, Map<Integer, PaintInfo> map, int level, int startIndex, int endIndex
  318. ) throws BadLocationException {
  319. //never called
  320. }
  321. /**
  322. * Adjust lowest/topmost boundaries from the Fold range y1-y2.
  323. * @param y1
  324. * @param y2
  325. * @param level
  326. */
  327. private void setMouseBoundaries(int y1, int y2, int level) {
  328. if (!hasMousePoint() || mousePointConsumed) {
  329. return;
  330. }
  331. int y = mousePoint;
  332. if (y2 < y && lowestAboveMouse < y2) {
  333. LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
  334. lowestAboveMouse = y2;
  335. }
  336. if (y1 > y && topmostBelowMouse > y1) {
  337. LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
  338. topmostBelowMouse = y1;
  339. }
  340. }
  341. /*
  342. * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
  343. * then visually span multiple lines && be marked as collapsed.
  344. */
  345. protected List<? extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
  346. javax.swing.plaf.TextUI textUI = component.getUI();
  347. if (!(textUI instanceof BaseTextUI)) {
  348. return Collections.<PaintInfo>emptyList();
  349. }
  350. BaseTextUI baseTextUI = (BaseTextUI)textUI;
  351. BaseDocument bdoc = Utilities.getDocument(component);
  352. if (bdoc == null) {
  353. return Collections.<PaintInfo>emptyList();
  354. }
  355. mousePointConsumed = false;
  356. mouseBoundary = null;
  357. topmostBelowMouse = Integer.MAX_VALUE;
  358. lowestAboveMouse = -1;
  359. bdoc.readLock();
  360. try {
  361. int startPos = baseTextUI.getPosFromY(clip.y);
  362. int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
  363. if (startPos < 0 || endPos < 0) {
  364. // editor window is not properly sized yet; return no infos
  365. return Collections.<PaintInfo>emptyList();
  366. }
  367. // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
  368. // the document.
  369. int docLen = bdoc.getLength();
  370. if (startPos >= docLen || endPos > docLen) {
  371. return Collections.<PaintInfo>emptyList();
  372. }
  373. startPos = Utilities.getRowStart(bdoc, startPos);
  374. endPos = Utilities.getRowEnd(bdoc, endPos);
  375. FoldHierarchy hierarchy = FoldHierarchy.get(component);
  376. hierarchy.lock();
  377. try {
  378. View rootView = Utilities.getDocumentView(component);
  379. if (rootView != null) {
  380. Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
  381. @SuppressWarnings("unchecked")
  382. List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
  383. int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
  384. /*
  385. * Note:
  386. *
  387. * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
  388. * This is because several folds may occupy the same line, while only one + sign is displayed,
  389. * and affect the last fold in the row.
  390. */
  391. NavigableMap<Integer, PaintInfo> map = new TreeMap<Integer, PaintInfo>();
  392. // search backwards
  393. for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
  394. Fold fold = foldList.get(i);
  395. if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
  396. break;
  397. }
  398. }
  399. // search forward
  400. for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
  401. Fold fold = foldList.get(i);
  402. if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
  403. break;
  404. }
  405. }
  406. if (map.isEmpty() && foldList.size() > 0) {
  407. assert foldList.size() == 1;
  408. PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
  409. mouseBoundary = new Rectangle(0, 0, 0, clip.height);
  410. LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
  411. if (hasMousePoint()) {
  412. pi.markActive(true, true, true);
  413. }
  414. return Collections.singletonList(pi);
  415. } else {
  416. if (mouseBoundary == null) {
  417. mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
  418. LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
  419. }
  420. return new ArrayList<PaintInfo>(map.values());
  421. }
  422. } else {
  423. return Collections.<PaintInfo>emptyList();
  424. }
  425. } finally {
  426. hierarchy.unlock();
  427. }
  428. } finally {
  429. bdoc.readUnlock();
  430. }
  431. }
  432. /**
  433. * Adds a paint info to the map. If a paintinfo already exists, it merges
  434. * the structures, so the painting process can just follow the instructions.
  435. *
  436. * @param infos
  437. * @param yOffset
  438. * @param nextInfo
  439. */
  440. private void addPaintInfo(Map<Integer, PaintInfo> infos, int yOffset, PaintInfo nextInfo) {
  441. PaintInfo prevInfo = infos.get(yOffset);
  442. nextInfo.mergeWith(prevInfo);
  443. infos.put(yOffset, nextInfo);
  444. }
  445. private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level, NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
  446. // System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
  447. // + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
  448. // + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
  449. // + ", level=" + level);
  450. if (f.getStartOffset() > upperBoundary) {
  451. return false;
  452. }
  453. int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
  454. int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
  455. int y1 = btui.getYFromPos(lineStartOffset1);
  456. int h = btui.getEditorUI().getLineHeight();
  457. int y2 = btui.getYFromPos(lineStartOffset2);
  458. // the 'active' flags can be set only after children are processed; highlights
  459. // correspond to the innermost expanded child.
  460. boolean activeMark = false;
  461. boolean activeIn = false;
  462. boolean activeOut = false;
  463. PaintInfo spi;
  464. boolean activated;
  465. if (y1 == y2) {
  466. // whole fold is on a single line
  467. spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
  468. if (activated = isActivated(y1, y1 + h)) {
  469. activeMark = true;
  470. }
  471. addPaintInfo(infos, y1, spi);
  472. } else {
  473. // fold spans multiple lines
  474. spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
  475. if (activated = isActivated(y1, y2 + h / 2)) {
  476. activeMark = true;
  477. activeOut = true;
  478. }
  479. addPaintInfo(infos, y1, spi);
  480. }
  481. setMouseBoundaries(y1, y2 + h / 2, level);
  482. // Handle end mark after possible inner folds were processed because
  483. // otherwise if there would be two nested folds both ending at the same line
  484. // then the end mark for outer one would be replaced by an end mark for inner one
  485. // (same key in infos map) and the painting code would continue to paint line marking a fold
  486. // until next fold is reached (or end of doc).
  487. PaintInfo epi = null;
  488. if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
  489. epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
  490. addPaintInfo(infos, y2, epi);
  491. }
  492. // save the topmost/lowest information, reset for child processing
  493. int topmost = topmostBelowMouse;
  494. int lowest = lowestAboveMouse;
  495. topmostBelowMouse = y2 + h / 2;
  496. lowestAboveMouse = y1;
  497. try {
  498. if (!f.isCollapsed()) {
  499. Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
  500. @SuppressWarnings("unchecked")
  501. List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
  502. int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
  503. // search backwards
  504. for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
  505. Fold fold = foldList.get(i);
  506. if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
  507. break;
  508. }
  509. }
  510. // search forward
  511. for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
  512. Fold fold = foldList.get(i);
  513. if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
  514. return false;
  515. }
  516. }
  517. }
  518. if (!mousePointConsumed && activated) {
  519. mousePointConsumed = true;
  520. mouseBoundary = makeMouseBoundary(y1, y2 + h);
  521. LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
  522. spi.markActive(activeMark, activeIn, activeOut);
  523. if (epi != null) {
  524. epi.markActive(true, true, false);
  525. }
  526. markDeepChildrenActive(infos, y1, y2, level);
  527. }
  528. } finally {
  529. topmostBelowMouse = topmost;
  530. lowestAboveMouse = lowest;
  531. }
  532. return true;
  533. }
  534. /**
  535. * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
  536. * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
  537. * as active.
  538. * The method returns Y start coordinate of the 1st child found.
  539. *
  540. * @param infos fold infos collected so far
  541. * @param yFrom upper Y-coordinate of the parent fold
  542. * @param yTo lower Y-coordinate of the parent fold
  543. * @param level level of the parent fold
  544. * @return Y-coordinate of the 1st child.
  545. */
  546. private int markDeepChildrenActive(NavigableMap<Integer, PaintInfo> infos, int yFrom, int yTo, int level) {
  547. int result = Integer.MAX_VALUE;
  548. Map<Integer, PaintInfo> m = infos.subMap(yFrom, yTo);
  549. for (Map.Entry<Integer, PaintInfo> me : m.entrySet()) {
  550. PaintInfo pi = me.getValue();
  551. int y = pi.getPaintY();
  552. if (y > yFrom && y < yTo) {
  553. if (LOG.isLoggable(Level.FINEST)) {
  554. LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
  555. }
  556. pi.markActive(false, true, true);
  557. if (y < result) {
  558. y = result;
  559. }
  560. }
  561. }
  562. return result;
  563. }
  564. /**
  565. * Returns stroke appropriate for painting (in)active outlines
  566. * @param s the default stroke
  567. * @param active true for active outlines
  568. * @return value of 's' or a Stroke which should be used to paint the outline.
  569. */
  570. private static Stroke getStroke(Stroke s, boolean active) {
  571. if (active) {
  572. return LINE_BOLD;
  573. } else {
  574. return s;
  575. }
  576. }
  577. private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
  578. // System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
  579. // + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
  580. // + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
  581. // + ", level=" + level);
  582. if (f.getEndOffset() < lowerBoundary) {
  583. return false;
  584. }
  585. int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
  586. int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
  587. int h = btui.getEditorUI().getLineHeight();
  588. boolean activeMark = false;
  589. boolean activeIn = false;
  590. boolean activeOut = false;
  591. PaintInfo spi = null;
  592. PaintInfo epi = null;
  593. boolean activated = false;
  594. int y1 = 0;
  595. int y2 = 0;
  596. if (lineStartOffset1 == lineStartOffset2) {
  597. // whole fold is on a single line
  598. y2 = y1 = btui.getYFromPos(lineStartOffset1);
  599. spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
  600. if (activated = isActivated(y1, y1 + h)) {
  601. activeMark = true;
  602. }
  603. addPaintInfo(infos, y1, spi);
  604. } else {
  605. y2 = btui.getYFromPos(lineStartOffset2);
  606. // fold spans multiple lines
  607. y1 = btui.getYFromPos(lineStartOffset1);
  608. activated = isActivated(y1, y2 + h / 2);
  609. if (f.getStartOffset() >= upperBoundary) {
  610. spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
  611. if (activated) {
  612. activeMark = true;
  613. activeOut = true;
  614. }
  615. addPaintInfo(infos, y1, spi);
  616. }
  617. if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
  618. activated |= isActivated(y1, y2 + h / 2);
  619. epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
  620. addPaintInfo(infos, y2, epi);
  621. }
  622. }
  623. setMouseBoundaries(y1, y2 + h / 2, level);
  624. // save the topmost/lowest information, reset for child processing
  625. int topmost = topmostBelowMouse;
  626. int lowest = lowestAboveMouse;
  627. topmostBelowMouse = y2 + h /2;
  628. lowestAboveMouse = y1;
  629. try {
  630. if (!f.isCollapsed()) {
  631. Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
  632. @SuppressWarnings("unchecked")
  633. List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
  634. int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
  635. // search backwards
  636. for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
  637. Fold fold = foldList.get(i);
  638. if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
  639. return false;
  640. }
  641. }
  642. // search forward
  643. for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
  644. Fold fold = foldList.get(i);
  645. if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
  646. break;
  647. }
  648. }
  649. }
  650. if (!mousePointConsumed && activated) {
  651. mousePointConsumed = true;
  652. mouseBoundary = makeMouseBoundary(y1, y2 + h);
  653. LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
  654. if (spi != null) {
  655. spi.markActive(activeMark, activeIn, activeOut);
  656. }
  657. if (epi != null) {
  658. epi.markActive(true, true, false);
  659. }
  660. int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
  661. if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
  662. // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
  663. // 1st child marker.
  664. epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
  665. epi.markActive(true, true, false);
  666. addPaintInfo(infos, y1, epi);
  667. }
  668. }
  669. } finally {
  670. topmostBelowMouse = topmost;
  671. lowestAboveMouse = lowest;
  672. }
  673. return true;
  674. }
  675. private Rectangle makeMouseBoundary(int y1, int y2) {
  676. if (!hasMousePoint()) {
  677. return null;
  678. }
  679. if (topmostBelowMouse < Integer.MAX_VALUE) {
  680. y2 = topmostBelowMouse;
  681. }
  682. if (lowestAboveMouse > -1) {
  683. y1 = lowestAboveMouse;
  684. }
  685. return new Rectangle(0, y1, 0, y2 - y1);
  686. }
  687. protected EditorUI getEditorUI(){
  688. return Utilities.getEditorUI(component);
  689. }
  690. protected Document getDocument(){
  691. return component.getDocument();
  692. }
  693. private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
  694. Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
  695. Fold prevFold = fold;
  696. while (fold != null && fold.getStartOffset()<rowEnd){
  697. Fold nextFold = FoldUtilities.findNearestFold(hierarchy, (fold.isCollapsed()) ? fold.getEndOffset() : fold.getStartOffset()+1);
  698. if (nextFold == fold) return fold;
  699. if (nextFold!=null && nextFold.getStartOffset() < rowEnd){
  700. prevFold = shift ? fold : nextFold;
  701. fold = nextFold;
  702. }else{
  703. return prevFold;
  704. }
  705. }
  706. return prevFold;
  707. }
  708. protected void performAction(Mark mark) {
  709. performAction(mark, false);
  710. }
  711. private void performActionAt(Mark mark, int mouseY) throws BadLocationException {
  712. if (mark != null) {
  713. return;
  714. }
  715. BaseDocument bdoc = Utilities.getDocument(component);
  716. BaseTextUI textUI = (BaseTextUI)component.getUI();
  717. View rootView = Utilities.getDocumentView(component);
  718. if (rootView == null) return;
  719. bdoc.readLock();
  720. try {
  721. int yOffset = textUI.getPosFromY(mouseY);
  722. FoldHierarchy hierarchy = FoldHierarchy.get(component);
  723. hierarchy.lock();
  724. try {
  725. Fold f = FoldUtilities.findOffsetFold(hierarchy, yOffset);
  726. if (f == null) {
  727. return;
  728. }
  729. if (f.isCollapsed()) {
  730. LOG.log(Level.WARNING, "Clicked on a collapsed fold {0} at {1}", new Object[] { f, mouseY });
  731. return;
  732. }
  733. int startOffset = f.getStartOffset();
  734. int endOffset = f.getEndOffset();
  735. int startY = textUI.getYFromPos(startOffset);
  736. int nextLineOffset = Utilities.getRowStart(bdoc, startOffset, 1);
  737. int nextY = textUI.getYFromPos(nextLineOffset);
  738. if (mouseY >= startY && mouseY <= nextY) {
  739. LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
  740. new Object[] { mouseY, startY, nextY });
  741. return;
  742. }
  743. startY = textUI.getYFromPos(endOffset);
  744. nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
  745. nextY = textUI.getYFromPos(nextLineOffset);
  746. if (mouseY >= startY && mouseY <= nextY) {
  747. // the mouse can be positioned above the marker (the fold found above), or
  748. // below it; in that case, the immediate enclosing fold should be used - should be the fold
  749. // that corresponds to the nextLineOffset, if any
  750. int h2 = (startY + nextY) / 2;
  751. if (mouseY >= h2) {
  752. Fold f2 = f;
  753. f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
  754. if (f == null) {
  755. // fold does not exist for the position below end-of-fold indicator
  756. return;
  757. }
  758. }
  759. }
  760. LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
  761. hierarchy.collapse(f);
  762. } finally {
  763. hierarchy.unlock();
  764. }
  765. } finally {
  766. bdoc.readUnlock();
  767. }
  768. }
  769. private void performAction(final Mark mark, final boolean shiftFold) {
  770. Document doc = component.getDocument();
  771. doc.render(new Runnable() {
  772. @Override
  773. public void run() {
  774. ViewHierarchy vh = ViewHierarchy.get(component);
  775. LockedViewHierarchy lockedVH = vh.lock();
  776. try {
  777. int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
  778. if (pViewIndex >= 0) {
  779. ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
  780. int pViewStartOffset = pViewDesc.getStartOffset();
  781. int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
  782. // Find corresponding fold
  783. FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
  784. foldHierarchy.lock();
  785. try {
  786. int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
  787. int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
  788. Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
  789. if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
  790. foldHierarchy.toggle(clickedFold);
  791. }
  792. } catch (BadLocationException ble) {
  793. LOG.log(Level.WARNING, null, ble);
  794. } finally {
  795. foldHierarchy.unlock();
  796. }
  797. }
  798. } finally {
  799. lockedVH.unlock();
  800. }
  801. }
  802. });
  803. }
  804. protected int getMarkSize(Graphics g){
  805. if (g != null){
  806. FontMetrics fm = g.getFontMetrics(getColoring().getFont());
  807. if (fm != null){
  808. int ret = fm.getAscent() - fm.getDescent();
  809. return ret - ret%2;
  810. }
  811. }
  812. return -1;
  813. }
  814. private boolean hasMousePoint() {
  815. return mousePoint >= 0;
  816. }
  817. private boolean isActivated(int y1, int y2) {
  818. return hasMousePoint() &&
  819. (mousePoint >= y1 && mousePoint < y2);
  820. }
  821. private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
  822. Stroke origStroke = g2d.getStroke();
  823. g2d.setStroke(getStroke(origStroke, active));
  824. g2d.drawLine(x1, y1, x2, y2);
  825. g2d.setStroke(origStroke);
  826. }
  827. protected @Override void paintComponent(Graphics g) {
  828. if (!enabled) {
  829. return;
  830. }
  831. Rectangle clip = getVisibleRect();//g.getClipBounds();
  832. visibleMarks.clear();
  833. Coloring coloring = getColoring();
  834. g.setColor(coloring.getBackColor());
  835. g.fillRect(clip.x, clip.y, clip.width, clip.height);
  836. g.setColor(coloring.getForeColor());
  837. AbstractDocument adoc = (AbstractDocument)component.getDocument();
  838. adoc.readLock();
  839. try {
  840. List<? extends PaintInfo> ps = getPaintInfo(clip);
  841. Font defFont = coloring.getFont();
  842. int markSize = getMarkSize(g);
  843. int halfMarkSize = markSize / 2;
  844. int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
  845. int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
  846. int lineX = markX + halfMarkSize; // x position of the centre of mark
  847. LOG.fine("CFSBar: PAINT START ------\n");
  848. int descent = g.getFontMetrics(defFont).getDescent();
  849. PaintInfo previousInfo = null;
  850. Graphics2D g2d = (Graphics2D)g;
  851. LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
  852. for(PaintInfo paintInfo : ps) {
  853. boolean isFolded = paintInfo.isCollapsed();
  854. int y = paintInfo.getPaintY();
  855. int height = paintInfo.getPaintHeight();
  856. int markY = y + descent; // y position of mark rectangle
  857. int paintOperation = paintInfo.getPaintOperation();
  858. if (previousInfo == null) {
  859. if (paintInfo.hasLineIn()) {
  860. if (LOG.isLoggable(Level.FINE)) {
  861. LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
  862. }
  863. drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
  864. }
  865. } else {
  866. if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
  867. // Draw middle vertical line
  868. int prevY = previousInfo.getPaintY();
  869. if (LOG.isLoggable(Level.FINE)) {
  870. LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
  871. }
  872. drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
  873. }
  874. }
  875. if (paintInfo.hasSign()) {
  876. g.drawRect(markX, markY, markSize, markSize);
  877. g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
  878. String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
  879. if (isFolded) {
  880. if (LOG.isLoggable(Level.FINE)) {
  881. LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
  882. }
  883. g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
  884. }
  885. if (paintOperation != SINGLE_PAINT_MARK) {
  886. if (LOG.isLoggable(Level.FINE)) {
  887. LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
  888. }
  889. }
  890. if (paintInfo.hasLineIn()) { //[PENDING]
  891. drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
  892. }
  893. if (paintInfo.hasLineOut()) {
  894. // This is an error in case there's a next paint info at the same y which is an end mark
  895. // for this mark (it must be cleared explicitly).
  896. drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
  897. }
  898. visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
  899. } else if (paintOperation == PAINT_LINE) {
  900. if (LOG.isLoggable(Level.FINE)) {
  901. LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
  902. }
  903. // FIXME !!
  904. drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
  905. } else if (paintOperation == PAINT_END_MARK) {
  906. if (LOG.isLoggable(Level.FINE)) {
  907. LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
  908. }
  909. if (previousInfo == null || y != previousInfo.getPaintY()) {
  910. drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
  911. drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
  912. if (paintInfo.getInnerLevel() > 0) {//[PENDING]
  913. if (LOG.isLoggable(Level.FINE)) {
  914. LOG.fine(" PAINT middle-line\n"); // NOI18N
  915. }
  916. drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
  917. }
  918. }
  919. }
  920. previousInfo = paintInfo;
  921. }
  922. if (previousInfo != null &&
  923. (previousInfo.getInnerLevel() > 0 ||
  924. (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
  925. ) {
  926. drawFoldLine(g2d, previousInfo.lineOutActive,
  927. lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
  928. }
  929. } catch (BadLocationException ble) {
  930. LOG.log(Level.WARNING, null, ble);
  931. } finally {
  932. LOG.fine("CFSBar: PAINT END ------\n\n");
  933. adoc.readUnlock();
  934. }
  935. }
  936. private static Object [] getFoldList(Fold parentFold, int start, int end) {
  937. List<Fold> ret = new ArrayList<Fold>();
  938. int index = FoldUtilities.findFoldEndIndex(parentFold, start);
  939. int foldCount = parentFold.getFoldCount();
  940. int idxOfFirstFoldStartingInside = -1;
  941. while (index < foldCount) {
  942. Fold f = parentFold.getFold(index);
  943. if (f.getStartOffset() <= end) {
  944. ret.add(f);
  945. } else {
  946. break; // no more relevant folds
  947. }
  948. if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
  949. idxOfFirstFoldStartingInside = ret.size() - 1;
  950. }
  951. index++;
  952. }
  953. return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
  954. }
  955. /**
  956. * This class should be never used by other code; will be made private
  957. */
  958. public class PaintInfo {
  959. int paintOperation;
  960. /**
  961. * level of the 1st marker on the line
  962. */
  963. int innerLevel;
  964. /**
  965. * Y-coordinate of the cell
  966. */
  967. int paintY;
  968. /**
  969. * Height of the paint cell
  970. */
  971. int paintHeight;
  972. /**
  973. * State of the marker (+/-)
  974. */
  975. boolean isCollapsed;
  976. /**
  977. * all markers on the line are collapsed
  978. */
  979. boolean allCollapsed;
  980. int startOffset;
  981. int endOffset;
  982. /**
  983. * nesting level of the last marker on the line
  984. */
  985. int outgoingLevel;
  986. /**
  987. * Force incoming line (from above) to be present
  988. */
  989. boolean lineIn;
  990. /**
  991. * Force outgoing line (down from marker) to be present
  992. */
  993. boolean lineOut;
  994. /**
  995. * The 'incoming' (upper) line should be painted as active
  996. */
  997. boolean lineInActive;
  998. /**
  999. * The 'outgoing' (down) line should be painted as active
  1000. */
  1001. boolean lineOutActive;
  1002. /**
  1003. * The sign/marker itself should be painted as active
  1004. */
  1005. boolean signActive;
  1006. public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
  1007. this.paintOperation = paintOperation;
  1008. this.innerLevel = this.outgoingLevel = innerLevel;
  1009. this.paintY = paintY;
  1010. this.paintHeight = paintHeight;
  1011. this.isCollapsed = this.allCollapsed = isCollapsed;
  1012. this.startOffset = startOffset;
  1013. this.endOffset = endOffset;
  1014. switch (paintOperation) {
  1015. case PAINT_MARK:
  1016. lineIn = false;
  1017. lineOut = true;
  1018. outgoingLevel++;
  1019. break;
  1020. case SINGLE_PAINT_MARK:
  1021. lineIn = false;
  1022. lineOut = false;
  1023. break;
  1024. case PAINT_END_MARK:
  1025. lineIn = true;
  1026. lineOut = false;
  1027. isCollapsed = true;
  1028. allCollapsed = true;
  1029. break;
  1030. case PAINT_LINE:
  1031. lineIn = lineOut = true;
  1032. break;
  1033. }
  1034. }
  1035. /**
  1036. * Sets active flags on inidivual parts of the mark
  1037. * @param mark
  1038. * @param lineIn
  1039. * @param lineOut S
  1040. */
  1041. void markActive(boolean mark, boolean lineIn, boolean lineOut) {
  1042. this.signActive |= mark;
  1043. this.lineInActive |= lineIn;
  1044. this.lineOutActive |= lineOut;
  1045. }
  1046. boolean hasLineIn() {
  1047. return lineIn || innerLevel > 0;
  1048. }
  1049. boolean hasLineOut() {
  1050. return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
  1051. }
  1052. public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
  1053. this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
  1054. }
  1055. public int getPaintOperation(){
  1056. return paintOperation;
  1057. }
  1058. public int getInnerLevel(){
  1059. return innerLevel;
  1060. }
  1061. public int getPaintY(){
  1062. return paintY;
  1063. }
  1064. public int getPaintHeight(){
  1065. return paintHeight;
  1066. }
  1067. public boolean isCollapsed(){
  1068. return isCollapsed;
  1069. }
  1070. boolean isAllCollapsed() {
  1071. return allCollapsed;
  1072. }
  1073. public void setPaintOperation(int paintOperation){
  1074. this.paintOperation = paintOperation;
  1075. }
  1076. public void setInnerLevel(int innerLevel){
  1077. this.innerLevel = innerLevel;
  1078. }
  1079. public @Override String toString(){
  1080. StringBuffer sb = new StringBuffer("");
  1081. if (paintOperation == PAINT_MARK){
  1082. sb.append("PAINT_MARK"); // NOI18N
  1083. }else if (paintOperation == PAINT_LINE){
  1084. sb.append("PAINT_LINE"); // NOI18N
  1085. }else if (paintOperation == PAINT_END_MARK) {
  1086. sb.a