This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 226368
Collapse All | Expand All

(-)a/editor.fold/apichanges.xml (+16 lines)
Lines 112-117 Link Here
112
<!-- ACTUAL CHANGES BEGIN HERE: -->
112
<!-- ACTUAL CHANGES BEGIN HERE: -->
113
113
114
  <changes>
114
  <changes>
115
    <change id="fold-editorlib-separation">
116
        <api name="api"/>
117
        <summary>Fold separation from Editor Library</summary>
118
        <version major="1" minor="34"/>
119
        <date day="26" month="2" year="2013"/>
120
        <author login="sdedic"/>
121
        <compatibility source="compatible" binary="compatible" semantic="compatible" deletion="no" addition="yes" deprecation="yes"/>
122
        <description>
123
            <p>The <code>CodeFoldingSideBar</code> and <code>CustomFoldManager</code> were moved from the <code>editor.lib</code> module
124
            into <code>editor.fold</code>. The classes were deprecated. Factory methods were provided instead, in <code>FoldingSupport</code>
125
            </p>
126
        </description>
127
        <class name="FoldingSupport" package="org.netbeans.api.editor.fold"/>
128
        <package name="org.netbeans.editor"/>
129
        <issue number="226368"/>
130
    </change>
115
  <change id="FoldOperation-owns-added">
131
  <change id="FoldOperation-owns-added">
116
      <api name="api"/>
132
      <api name="api"/>
117
      <summary>FoldOperation.owns(Fold) added</summary>
133
      <summary>FoldOperation.owns(Fold) added</summary>
(-)a/editor.fold/arch.xml (-1 / +4 lines)
Lines 787-793 Link Here
787
        </question>
787
        </question>
788
-->
788
-->
789
<answer id="exec-property">
789
<answer id="exec-property">
790
No.
790
    <api type="export" group="clientproperty" name="org.netbeans.api.fold.expander" category="friend">
791
        Injects its handler to be used with BaseCaret as client property of the JTextComponent that initializes
792
        folding. 
793
    </api>
791
</answer>
794
</answer>
792
795
793
796
(-)a/editor.fold/nbproject/project.properties (-1 / +1 lines)
Lines 46-51 Link Here
46
#cp.extra=
46
#cp.extra=
47
47
48
javac.source=1.6
48
javac.source=1.6
49
spec.version.base=1.33.0
49
spec.version.base=1.34.0
50
50
51
test.config.stableBTD.includes=**/*Test.class
51
test.config.stableBTD.includes=**/*Test.class
(-)a/editor.fold/nbproject/project.xml (+35 lines)
Lines 50-55 Link Here
50
            <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
50
            <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
51
            <module-dependencies>
51
            <module-dependencies>
52
                <dependency>
52
                <dependency>
53
                    <code-name-base>org.netbeans.modules.editor.lib</code-name-base>
54
                    <build-prerequisite/>
55
                    <compile-dependency/>
56
                    <run-dependency>
57
                        <release-version>3</release-version>
58
                        <specification-version>3.37</specification-version>
59
                    </run-dependency>
60
                </dependency>
61
                <dependency>
53
                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
62
                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
54
                    <build-prerequisite/>
63
                    <build-prerequisite/>
55
                    <compile-dependency/>
64
                    <compile-dependency/>
Lines 85-90 Link Here
85
                    </run-dependency>
94
                    </run-dependency>
86
                </dependency>
95
                </dependency>
87
                <dependency>
96
                <dependency>
97
                    <code-name-base>org.netbeans.modules.lexer</code-name-base>
98
                    <build-prerequisite/>
99
                    <compile-dependency/>
100
                    <run-dependency>
101
                        <release-version>2</release-version>
102
                        <specification-version>1.50</specification-version>
103
                    </run-dependency>
104
                </dependency>
105
                <dependency>
88
                    <code-name-base>org.openide.filesystems</code-name-base>
106
                    <code-name-base>org.openide.filesystems</code-name-base>
89
                    <build-prerequisite/>
107
                    <build-prerequisite/>
90
                    <compile-dependency/>
108
                    <compile-dependency/>
Lines 143-152 Link Here
143
                        <compile-dependency/>
161
                        <compile-dependency/>
144
                        <test/>
162
                        <test/>
145
                    </test-dependency>
163
                    </test-dependency>
164
                    <test-dependency>
165
                        <code-name-base>org.netbeans.modules.editor</code-name-base>
166
                        <recursive/>
167
                        <compile-dependency/>
168
                        <test/>
169
                    </test-dependency>
170
                    <test-dependency>
171
                        <code-name-base>org.netbeans.modules.editor.settings.storage</code-name-base>
172
                        <recursive/>
173
                        <test/>
174
                    </test-dependency>
175
                    <test-dependency>
176
                        <code-name-base>org.netbeans.modules.editor.mimelookup.impl</code-name-base>
177
                        <recursive/>
178
                        <test/>
179
                    </test-dependency>
146
                </test-type>
180
                </test-type>
147
            </test-dependencies>
181
            </test-dependencies>
148
            <public-packages>
182
            <public-packages>
149
                <package>org.netbeans.api.editor.fold</package>
183
                <package>org.netbeans.api.editor.fold</package>
184
                <package>org.netbeans.editor</package>
150
                <package>org.netbeans.spi.editor.fold</package>
185
                <package>org.netbeans.spi.editor.fold</package>
151
            </public-packages>
186
            </public-packages>
152
        </data>
187
        </data>
(-)a/editor.fold/src/org/netbeans/api/editor/fold/FoldingSupport.java (+142 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2013 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
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2013 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.fold;
43
44
import java.util.Map;
45
import javax.swing.JComponent;
46
import javax.swing.text.JTextComponent;
47
import org.netbeans.editor.SideBarFactory;
48
import org.netbeans.modules.editor.fold.CustomFoldManager;
49
import org.netbeans.modules.editor.fold.ui.CodeFoldingSideBar;
50
import org.netbeans.spi.editor.fold.FoldManager;
51
import org.netbeans.spi.editor.fold.FoldManagerFactory;
52
import org.openide.util.Parameters;
53
54
/**
55
 * This utility class collects APIs to create default implementations
56
 * of various pieces of infrastructure.
57
 * 
58
 * @author sdedic
59
 */
60
public final class FoldingSupport {
61
    private static SideBarFactory FACTORY = null;
62
    
63
    private FoldingSupport() {}
64
65
    /**
66
     * Creates a component for the code folding sidebar.
67
     * Returns a standard folding sidebar component, which displays code folds. This sidebar implementation 
68
     * can be created only once per text component.
69
     *
70
     * @param textComponent the text component which should work with the Sidebar
71
     * @return Sidebar instance.
72
     */
73
    public static JComponent sidebarComponent(JTextComponent textComponent) {
74
        return new CodeFoldingSideBar(textComponent);
75
    }
76
77
    /**
78
     * Creates a user-defined fold manager, that processes specific token type.
79
     * The manager tries to find start/end markers within the token text. If found,
80
     * a Fold is created. The manager only looks in tokens, whose {@code primaryCategory}
81
     * starts with a String, which is stored under 'tokenId' key in the params map.
82
     * <p/>
83
     * The method is designed to be called from the filesystem layer as follows:
84
     * <code><pre>
85
     * &lt;file name="my-custom-foldmanager.instance">
86
     * &lt;attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldUtilities.createUserFoldManager"/>
87
     * &lt;attr name="tokenid" stringvalue="comment"/>
88
     * &lt;/file>
89
     * </pre></code>
90
     *
91
     * @param map the configuration parameters.
92
     * @return FoldManagerFactory instance
93
     */
94
    public static FoldManagerFactory userFoldManagerFactory(Map params) {
95
        final String s = (String) params.get("tokenId");
96
        return new FoldManagerFactory() {
97
            @Override
98
            public FoldManager createFoldManager() {
99
                return userFoldManager(s);
100
            }
101
        };
102
    }
103
    
104
    /**
105
     * Creates a user-defined fold manager, that processes specific token type.
106
     * The manager tries to find start/end markers within the token text. If found,
107
     * a Fold is created. The manager only looks in tokens, whose {@code primaryCategory}
108
     * starts with tokenId string.
109
     * <p/>
110
     * {@code Null} value of 'tokenId' means the default "comment" will be used.
111
     *
112
     * @param tokenId filter for prefix of the token's primaryCategory.
113
     * @return FoldManager instance
114
     */
115
    public static FoldManager userFoldManager(String tokenId) {
116
        if (tokenId != null) {
117
            return new CustomFoldManager(tokenId);
118
        } else {
119
            return new CustomFoldManager();
120
        }
121
    }
122
123
    /**
124
     * Obtains an instance of folding sidebar factory. This method should
125
     * be used in layer, in the MIME lookup area, to register a sidebar with
126
     * an editor for a specific MIMEtype.
127
     * <p/>
128
     * There's a default sidebar instance registered for all MIME types. 
129
     *
130
     * @return shared sidebar factory instance
131
     */
132
    public static SideBarFactory foldingSidebarFactory() {
133
        if (FACTORY != null) {
134
            return FACTORY;
135
        }
136
        return FACTORY = new CodeFoldingSideBar.Factory();
137
    }
138
139
    public static void disableCodeFoldingSidebar(JTextComponent text) {
140
        text.putClientProperty(CodeFoldingSideBar.PROP_SIDEBAR_MARK, true);
141
    }
142
}
(-)a/editor.fold/src/org/netbeans/editor/CodeFoldingSideBar.java (+1482 lines)
Line 0 Link Here
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
45
package org.netbeans.editor;
46
47
import java.awt.BasicStroke;
48
import java.awt.Color;
49
import java.awt.Dimension;
50
import java.awt.Font;
51
import java.awt.FontMetrics;
52
import java.awt.Graphics;
53
import java.awt.Graphics2D;
54
import java.awt.Point;
55
import java.awt.Rectangle;
56
import java.awt.Stroke;
57
import java.awt.event.MouseAdapter;
58
import java.awt.event.MouseEvent;
59
import java.util.ArrayList;
60
import java.util.Collections;
61
import java.util.List;
62
import java.util.Map;
63
import java.util.NavigableMap;
64
import java.util.TreeMap;
65
import java.util.logging.Level;
66
import java.util.logging.Logger;
67
import java.util.prefs.PreferenceChangeEvent;
68
import java.util.prefs.PreferenceChangeListener;
69
import java.util.prefs.Preferences;
70
import javax.accessibility.Accessible;
71
import javax.accessibility.AccessibleContext;
72
import javax.accessibility.AccessibleRole;
73
import javax.swing.JComponent;
74
import javax.swing.SwingUtilities;
75
import javax.swing.event.DocumentEvent;
76
import javax.swing.event.DocumentListener;
77
import javax.swing.text.AbstractDocument;
78
import javax.swing.text.AttributeSet;
79
import javax.swing.text.BadLocationException;
80
import javax.swing.text.Document;
81
import javax.swing.text.JTextComponent;
82
import javax.swing.text.View;
83
import org.netbeans.api.editor.fold.Fold;
84
import org.netbeans.api.editor.fold.FoldHierarchy;
85
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
86
import org.netbeans.api.editor.fold.FoldHierarchyListener;
87
import org.netbeans.api.editor.fold.FoldUtilities;
88
import org.netbeans.api.editor.mimelookup.MimeLookup;
89
import org.netbeans.api.editor.settings.AttributesUtilities;
90
import org.netbeans.api.editor.settings.FontColorNames;
91
import org.netbeans.api.editor.settings.FontColorSettings;
92
import org.netbeans.api.editor.settings.SimpleValueNames;
93
import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
94
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
95
import org.netbeans.modules.editor.lib2.view.ParagraphViewDescriptor;
96
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
97
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
98
import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
99
import org.openide.util.Lookup;
100
import org.openide.util.LookupEvent;
101
import org.openide.util.LookupListener;
102
import org.openide.util.NbBundle;
103
import org.openide.util.WeakListeners;
104
105
/**
106
 *  Code Folding Side Bar. Component responsible for drawing folding signs and responding 
107
 *  on user fold/unfold action.
108
 *
109
 *  @author  Martin Roskanin
110
 *  @deprecated You should use {@link FoldUtilities#createSidebarComponent(javax.swing.text.JTextComponent)} or
111
 *  {@link FoldUtilities#getFoldingSidebarFactory()} instead. Subclassing CodeFoldingSidebar
112
 *  is no longer actively supported, though still working.
113
 */
114
@Deprecated
115
public class CodeFoldingSideBar extends JComponent implements Accessible {
116
117
    private static final Logger LOG = Logger.getLogger(CodeFoldingSideBar.class.getName());
118
119
    /** This field should be treated as final. Subclasses are forbidden to change it. 
120
     * @deprecated Without any replacement.
121
     */
122
    protected Color backColor;
123
    /** This field should be treated as final. Subclasses are forbidden to change it. 
124
     * @deprecated Without any replacement.
125
     */
126
    protected Color foreColor;
127
    /** This field should be treated as final. Subclasses are forbidden to change it. 
128
     * @deprecated Without any replacement.
129
     */
130
    protected Font font;
131
    
132
    /** This field should be treated as final. Subclasses are forbidden to change it. */
133
    protected /*final*/ JTextComponent component;
134
    private volatile AttributeSet attribs;
135
    private Lookup.Result<? extends FontColorSettings> fcsLookupResult;
136
    private final LookupListener fcsTracker = new LookupListener() {
137
        public void resultChanged(LookupEvent ev) {
138
            attribs = null;
139
            SwingUtilities.invokeLater(new Runnable() {
140
                public void run() {
141
                    //EMI: This is needed as maybe the DEFAULT_COLORING is changed, the font is different
142
                    // and while getMarkSize() is used in paint() and will make the artifacts bigger,
143
                    // the component itself will be the same size and it must be changed.
144
                    // See http://www.netbeans.org/issues/show_bug.cgi?id=153316
145
                    updatePreferredSize();
146
                    CodeFoldingSideBar.this.repaint();
147
                }
148
            });
149
        }
150
    };
151
    private final Listener listener = new Listener();
152
    
153
    private boolean enabled = false;
154
    
155
    protected List<Mark> visibleMarks = new ArrayList<Mark>();
156
    
157
    /**
158
     * Mouse moved point, possibly {@code null}. Set from mouse-moved, mouse-entered
159
     * handlers, so that painting will paint this fold in bold. -1, if mouse is not
160
     * in the sidebar region. The value is used to compute highlighted portions of the 
161
     * folding outline.
162
     */
163
    private int   mousePoint = -1;
164
    
165
    /**
166
     * if true, the {@link #mousePoint} has been already used to make a PaintInfo active.
167
     * The flag is tested by {@link #traverseForward} and {@link #traverseBackward} after children
168
     * of the current fold are processed and cleared if the {@link #mousePoint} falls to the fold area -
169
     * fields of PaintInfo are set accordingly.
170
     * It's also used to compute (current) mouseBoundary, so mouse movement does not trigger 
171
     * refreshes eagerly
172
     */
173
    private boolean mousePointConsumed;
174
    
175
    /**
176
     * Boundaries of the current area under the mouse. Can be eiher the span of the
177
     * current fold (or part of it), or the span not occupied by any fold. Serves as an optimization
178
     * for mouse handler, which does not trigger painting (refresh) unless mouse 
179
     * leaves this region.
180
     */
181
    private Rectangle   mouseBoundary;
182
    
183
    /**
184
     * Y-end of the nearest fold that ends above the {@link #mousePoint}. Undefined if mousePoint is null.
185
     * These two variables are initialized at each level of folds, and help to compute {@link #mouseBoundary} for
186
     * the case the mousePointer is OUTSIDE all children (or outside all folds). 
187
     */
188
    private int lowestAboveMouse = -1;
189
190
    /**
191
     * Y-begin of the nearest fold, which starts below the {@link #mousePoint}. Undefined if mousePoint is null
192
     */
193
    private int topmostBelowMouse = Integer.MAX_VALUE;
194
    
195
    /** Paint operations */
196
    public static final int PAINT_NOOP             = 0;
197
    /**
198
     * Normal opening +- marker
199
     */
200
    public static final int PAINT_MARK             = 1;
201
    
202
    /**
203
     * Vertical line - typically at the end of the screen
204
     */
205
    public static final int PAINT_LINE             = 2;
206
    
207
    /**
208
     * End angled line, without a sign
209
     */
210
    public static final int PAINT_END_MARK         = 3;
211
    
212
    /**
213
     * Single-line marker, both start and end
214
     */
215
    public static final int SINGLE_PAINT_MARK      = 4;
216
    
217
    /**
218
     * Marker value for {@link #mousePoint} indicating that mouse is outside the Component.
219
     */
220
    private static final int NO_MOUSE_POINT = -1;
221
    
222
    /**
223
     * Stroke used to draw inactive (regular) fold outlines.
224
     */
225
    private static Stroke LINE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 
226
            1f, new float[] { 1f, 1f }, 0f);
227
    
228
    private boolean alreadyPresent;
229
    
230
    /**
231
     * Stroke used to draw outlines for 'active' fold
232
     */
233
    private static final Stroke LINE_BOLD = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
234
    
235
    private final Preferences prefs;
236
    private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
237
        public void preferenceChange(PreferenceChangeEvent evt) {
238
            String key = evt == null ? null : evt.getKey();
239
            if (key == null || SimpleValueNames.CODE_FOLDING_ENABLE.equals(key)) {
240
                updateColors();
241
                
242
                boolean newEnabled = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, EditorPreferencesDefaults.defaultCodeFoldingEnable);
243
                if (enabled != newEnabled) {
244
                    enabled = newEnabled;
245
                    updatePreferredSize();
246
                }
247
            }
248
        }
249
    };
250
    
251
    private void checkRepaint(ViewHierarchyEvent vhe) {
252
        if (!vhe.isChangeY()) {
253
            // does not obscur sidebar graphics
254
            return;
255
        }
256
        
257
        SwingUtilities.invokeLater(new Runnable() {
258
            public void run() {
259
                updatePreferredSize();
260
                CodeFoldingSideBar.this.repaint();
261
            }
262
        });
263
    }
264
    
265
    /**
266
     * @deprecated Don't use this constructor, it does nothing!
267
     */
268
    public CodeFoldingSideBar() {
269
        component = null;
270
        prefs = null;
271
        throw new IllegalStateException("Do not use this constructor!"); //NOI18N
272
    }
273
274
    public CodeFoldingSideBar(JTextComponent component){
275
        super();
276
        this.component = component;
277
278
        if (component.getClientProperty("org.netbeans.editor.CodeFoldingSidebar") == null) {
279
            component.putClientProperty("org.netbeans.editor.CodeFoldingSidebar", Boolean.TRUE);
280
        } else {
281
            alreadyPresent = true;
282
        }
283
284
        addMouseListener(listener);
285
        addMouseMotionListener(listener);
286
287
        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
288
        foldHierarchy.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, listener, foldHierarchy));
289
290
        Document doc = getDocument();
291
        doc.addDocumentListener(WeakListeners.document(listener, doc));
292
        setOpaque(true);
293
        
294
        prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
295
        prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, prefsListener, prefs));
296
        prefsListener.preferenceChange(null);
297
        
298
        ViewHierarchy.get(component).addViewHierarchyListener(new ViewHierarchyListener() {
299
300
            @Override
301
            public void viewHierarchyChanged(ViewHierarchyEvent evt) {
302
                checkRepaint(evt);
303
            }
304
            
305
        });
306
    }
307
    
308
    private void updatePreferredSize() {
309
        if (enabled && !alreadyPresent) {
310
            setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
311
            setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
312
        }else{
313
            setPreferredSize(new Dimension(0,0));
314
            setMaximumSize(new Dimension(0,0));
315
        }
316
        revalidate();
317
    }
318
319
    private void updateColors() {
320
        Coloring c = getColoring();
321
        this.backColor = c.getBackColor();
322
        this.foreColor = c.getForeColor();
323
        this.font = c.getFont();
324
    }
325
326
    /**
327
     * This method should be treated as final. Subclasses are forbidden to override it.
328
     * @return The background color used for painting this component.
329
     * @deprecated Without any replacement.
330
     */
331
    protected Color getBackColor() {
332
        if (backColor == null) {
333
            updateColors();
334
        }
335
        return backColor;
336
    }
337
    
338
    /**
339
     * This method should be treated as final. Subclasses are forbidden to override it.
340
     * @return The foreground color used for painting this component.
341
     * @deprecated Without any replacement.
342
     */
343
    protected Color getForeColor() {
344
        if (foreColor == null) {
345
            updateColors();
346
        }
347
        return foreColor;
348
    }
349
    
350
    /**
351
     * This method should be treated as final. Subclasses are forbidden to override it.
352
     * @return The font used for painting this component.
353
     * @deprecated Without any replacement.
354
     */
355
    protected Font getColoringFont() {
356
        if (font == null) {
357
            updateColors();
358
        }
359
        return font;
360
    }
361
    
362
    // overriding due to issue #60304
363
    public @Override void update(Graphics g) {
364
    }
365
    
366
    protected void collectPaintInfos(
367
        View rootView, Fold fold, Map<Integer, PaintInfo> map, int level, int startIndex, int endIndex
368
    ) throws BadLocationException {
369
        //never called
370
    }
371
372
    /**
373
     * Adjust lowest/topmost boundaries from the Fold range y1-y2.
374
     * @param y1
375
     * @param y2
376
     * @param level 
377
     */
378
    private void setMouseBoundaries(int y1, int y2, int level) {
379
        if (!hasMousePoint() || mousePointConsumed) {
380
            return;
381
        }
382
        int y = mousePoint;
383
        if (y2 < y && lowestAboveMouse < y2) {
384
            LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
385
            lowestAboveMouse = y2;
386
        }
387
        if (y1 > y && topmostBelowMouse > y1) {
388
            LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
389
            topmostBelowMouse = y1;
390
        }
391
    }
392
    
393
    /*
394
     * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
395
     * then visually span multiple lines && be marked as collapsed.
396
     */
397
398
    protected List<? extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
399
        javax.swing.plaf.TextUI textUI = component.getUI();
400
        if (!(textUI instanceof BaseTextUI)) {
401
            return Collections.<PaintInfo>emptyList();
402
        }
403
        BaseTextUI baseTextUI = (BaseTextUI)textUI;
404
        BaseDocument bdoc = Utilities.getDocument(component);
405
        if (bdoc == null) {
406
            return Collections.<PaintInfo>emptyList();
407
        }
408
        mousePointConsumed = false;
409
        mouseBoundary = null;
410
        topmostBelowMouse = Integer.MAX_VALUE;
411
        lowestAboveMouse = -1;
412
        bdoc.readLock();
413
        try {
414
            int startPos = baseTextUI.getPosFromY(clip.y);
415
            int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
416
            
417
            if (startPos < 0 || endPos < 0) {
418
                // editor window is not properly sized yet; return no infos
419
                return Collections.<PaintInfo>emptyList();
420
            }
421
            
422
            // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
423
            // the document.
424
            int docLen = bdoc.getLength();
425
            if (startPos >= docLen || endPos > docLen) {
426
                return Collections.<PaintInfo>emptyList();
427
            }
428
            
429
            startPos = Utilities.getRowStart(bdoc, startPos);
430
            endPos = Utilities.getRowEnd(bdoc, endPos);
431
            
432
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
433
            hierarchy.lock();
434
            try {
435
                View rootView = Utilities.getDocumentView(component);
436
                if (rootView != null) {
437
                    Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
438
                    @SuppressWarnings("unchecked")
439
                    List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
440
                    int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
441
442
                    /*
443
                     * Note:
444
                     * 
445
                     * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
446
                     * This is because several folds may occupy the same line, while only one + sign is displayed,
447
                     * and affect the last fold in the row.
448
                     */
449
                    NavigableMap<Integer, PaintInfo> map = new TreeMap<Integer, PaintInfo>();
450
                    // search backwards
451
                    for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
452
                        Fold fold = foldList.get(i);
453
                        if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
454
                            break;
455
                        }
456
                    }
457
458
                    // search forward
459
                    for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
460
                        Fold fold = foldList.get(i);
461
                        if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
462
                            break;
463
                        }
464
                    }
465
                    
466
                    if (map.isEmpty() && foldList.size() > 0) {
467
                        assert foldList.size() == 1;
468
                        PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
469
                        mouseBoundary = new Rectangle(0, 0, 0, clip.height);
470
                        LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
471
                        if (hasMousePoint()) {
472
                            pi.markActive(true, true, true);
473
                        }
474
                        return Collections.singletonList(pi);
475
                    } else {
476
                        if (mouseBoundary == null) {
477
                            mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
478
                            LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
479
                        }
480
                        return new ArrayList<PaintInfo>(map.values());
481
                    }
482
                } else {
483
                    return Collections.<PaintInfo>emptyList();
484
                }
485
            } finally {
486
                hierarchy.unlock();
487
            }
488
        } finally {
489
            bdoc.readUnlock();
490
        }
491
    }
492
    
493
    /**
494
     * Adds a paint info to the map. If a paintinfo already exists, it merges
495
     * the structures, so the painting process can just follow the instructions.
496
     * 
497
     * @param infos
498
     * @param yOffset
499
     * @param nextInfo 
500
     */
501
    private void addPaintInfo(Map<Integer, PaintInfo> infos, int yOffset, PaintInfo nextInfo) {
502
        PaintInfo prevInfo = infos.get(yOffset);
503
        nextInfo.mergeWith(prevInfo);
504
        infos.put(yOffset, nextInfo);
505
    }
506
507
    private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level,  NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
508
//        System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
509
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
510
//                + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
511
//                + ", level=" + level);
512
        
513
        if (f.getStartOffset() > upperBoundary) {
514
            return false;
515
        }
516
517
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
518
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
519
        int y1 = btui.getYFromPos(lineStartOffset1);
520
        int h = btui.getEditorUI().getLineHeight();
521
        int y2 = btui.getYFromPos(lineStartOffset2);
522
         
523
        // the 'active' flags can be set only after children are processed; highlights
524
        // correspond to the innermost expanded child.
525
        boolean activeMark = false;
526
        boolean activeIn = false;
527
        boolean activeOut = false;
528
        PaintInfo spi;
529
        boolean activated;
530
        
531
        if (y1 == y2) {
532
            // whole fold is on a single line
533
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
534
            if (activated = isActivated(y1, y1 + h)) {
535
                activeMark = true;
536
            }
537
            addPaintInfo(infos, y1, spi);
538
        } else {
539
            // fold spans multiple lines
540
            spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
541
            if (activated = isActivated(y1, y2 + h / 2)) {
542
                activeMark = true;
543
                activeOut = true;
544
            }
545
            addPaintInfo(infos, y1, spi);
546
        }
547
548
        setMouseBoundaries(y1, y2 + h / 2, level);
549
550
        // Handle end mark after possible inner folds were processed because
551
        // otherwise if there would be two nested folds both ending at the same line
552
        // then the end mark for outer one would be replaced by an end mark for inner one
553
        // (same key in infos map) and the painting code would continue to paint line marking a fold
554
        // until next fold is reached (or end of doc).
555
        PaintInfo epi = null;
556
        if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
557
            epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
558
            addPaintInfo(infos, y2, epi);
559
        }
560
561
        // save the topmost/lowest information, reset for child processing
562
        int topmost = topmostBelowMouse;
563
        int lowest = lowestAboveMouse;
564
        topmostBelowMouse = y2 + h / 2;
565
        lowestAboveMouse = y1;
566
567
        try {
568
            if (!f.isCollapsed()) {
569
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
570
                @SuppressWarnings("unchecked")
571
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
572
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
573
574
                // search backwards
575
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
576
                    Fold fold = foldList.get(i);
577
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
578
                        break;
579
                    }
580
                }
581
582
                // search forward
583
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
584
                    Fold fold = foldList.get(i);
585
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
586
                        return false;
587
                    }
588
                }
589
            }
590
            if (!mousePointConsumed && activated) {
591
                mousePointConsumed = true;
592
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
593
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
594
                spi.markActive(activeMark, activeIn, activeOut);
595
                if (epi != null) {
596
                    epi.markActive(true, true, false);
597
                }
598
                markDeepChildrenActive(infos, y1, y2, level);
599
            }
600
        } finally {
601
            topmostBelowMouse = topmost;
602
            lowestAboveMouse = lowest;
603
        }
604
        return true;
605
    }
606
     
607
    /**
608
     * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
609
     * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
610
     * as active.
611
     * The method returns Y start coordinate of the 1st child found.
612
     * 
613
     * @param infos fold infos collected so far
614
     * @param yFrom upper Y-coordinate of the parent fold
615
     * @param yTo lower Y-coordinate of the parent fold
616
     * @param level level of the parent fold
617
     * @return Y-coordinate of the 1st child.
618
     */
619
    private int markDeepChildrenActive(NavigableMap<Integer, PaintInfo> infos, int yFrom, int yTo, int level) {
620
        int result = Integer.MAX_VALUE;
621
        Map<Integer, PaintInfo> m = infos.subMap(yFrom, yTo);
622
        for (Map.Entry<Integer, PaintInfo> me : m.entrySet()) {
623
            PaintInfo pi = me.getValue();
624
            int y = pi.getPaintY();
625
            if (y > yFrom && y < yTo) {
626
                if (LOG.isLoggable(Level.FINEST)) {
627
                    LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
628
                }
629
                pi.markActive(false, true, true);
630
                if (y < result) {
631
                    y = result;
632
                }
633
            }
634
        }
635
        return result;
636
    }
637
    
638
    /**
639
     * Returns stroke appropriate for painting (in)active outlines
640
     * @param s the default stroke
641
     * @param active true for active outlines
642
     * @return value of 's' or a Stroke which should be used to paint the outline.
643
     */
644
    private static Stroke getStroke(Stroke s, boolean active) {
645
        if (active) {
646
            return LINE_BOLD;
647
        } else {
648
            return s;
649
        }
650
    }
651
    
652
    private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
653
//        System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
654
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
655
//                + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
656
//                + ", level=" + level);
657
658
        if (f.getEndOffset() < lowerBoundary) {
659
            return false;
660
        }
661
662
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
663
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
664
        int h = btui.getEditorUI().getLineHeight();
665
666
        boolean activeMark = false;
667
        boolean activeIn = false;
668
        boolean activeOut = false;
669
        PaintInfo spi = null;
670
        PaintInfo epi = null;
671
        boolean activated = false;
672
        int y1 = 0;
673
        int y2 = 0;
674
        
675
        if (lineStartOffset1 == lineStartOffset2) {
676
            // whole fold is on a single line
677
            y2 = y1 = btui.getYFromPos(lineStartOffset1);
678
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
679
            if (activated = isActivated(y1, y1 + h)) {
680
                activeMark = true;
681
            }
682
            addPaintInfo(infos, y1, spi);
683
        } else {
684
            y2 = btui.getYFromPos(lineStartOffset2);
685
            // fold spans multiple lines
686
            y1 = btui.getYFromPos(lineStartOffset1);
687
            activated = isActivated(y1, y2 + h / 2);
688
            if (f.getStartOffset() >= upperBoundary) {
689
                spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
690
                if (activated) {
691
                    activeMark = true;
692
                    activeOut = true;
693
                }
694
                addPaintInfo(infos, y1, spi);
695
            }
696
697
            if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
698
                activated |= isActivated(y1, y2 + h / 2);
699
                epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
700
                addPaintInfo(infos, y2, epi);
701
            }
702
        }
703
        
704
        setMouseBoundaries(y1, y2 + h / 2, level);
705
706
        // save the topmost/lowest information, reset for child processing
707
        int topmost = topmostBelowMouse;
708
        int lowest = lowestAboveMouse;
709
        topmostBelowMouse = y2 + h /2;
710
        lowestAboveMouse = y1;
711
712
        try {
713
            if (!f.isCollapsed()) {
714
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
715
                @SuppressWarnings("unchecked")
716
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
717
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
718
719
                // search backwards
720
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
721
                    Fold fold = foldList.get(i);
722
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
723
                        return false;
724
                    }
725
                }
726
727
                // search forward
728
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
729
                    Fold fold = foldList.get(i);
730
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
731
                        break;
732
                    }
733
                }
734
            }
735
            if (!mousePointConsumed && activated) {
736
                mousePointConsumed = true;
737
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
738
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
739
                if (spi != null) {
740
                    spi.markActive(activeMark, activeIn, activeOut);
741
                }
742
                if (epi != null) {
743
                    epi.markActive(true, true, false);
744
                }
745
                int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
746
                if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
747
                    // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
748
                    // 1st child marker.
749
                    epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
750
                    epi.markActive(true, true, false);
751
                    addPaintInfo(infos, y1, epi);
752
                }
753
            }
754
        } finally {
755
            topmostBelowMouse = topmost;
756
            lowestAboveMouse = lowest;
757
        }
758
        return true;
759
    }
760
    
761
    private Rectangle makeMouseBoundary(int y1, int y2) {
762
        if (!hasMousePoint()) {
763
            return null;
764
        }
765
        if (topmostBelowMouse < Integer.MAX_VALUE) {
766
            y2 = topmostBelowMouse;
767
        }
768
        if (lowestAboveMouse  > -1) {
769
            y1 = lowestAboveMouse;
770
        }
771
        return new Rectangle(0, y1, 0, y2 - y1);
772
    }
773
    
774
    protected EditorUI getEditorUI(){
775
        return Utilities.getEditorUI(component);
776
    }
777
    
778
    protected Document getDocument(){
779
        return component.getDocument();
780
    }
781
782
783
    private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
784
        Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
785
        Fold prevFold = fold;
786
        while (fold != null && fold.getStartOffset()<rowEnd){
787
            Fold nextFold = FoldUtilities.findNearestFold(hierarchy, (fold.isCollapsed()) ? fold.getEndOffset() : fold.getStartOffset()+1);
788
            if (nextFold == fold) return fold;
789
            if (nextFold!=null && nextFold.getStartOffset() < rowEnd){
790
                prevFold = shift ? fold : nextFold;
791
                fold = nextFold;
792
            }else{
793
                return prevFold;
794
            }
795
        }
796
        return prevFold;
797
    }
798
    
799
    protected void performAction(Mark mark) {
800
        performAction(mark, false);
801
    }
802
    
803
    private void performActionAt(Mark mark, int mouseY) throws BadLocationException {
804
        if (mark != null) {
805
            return;
806
        }
807
        BaseDocument bdoc = Utilities.getDocument(component);
808
        BaseTextUI textUI = (BaseTextUI)component.getUI();
809
810
        View rootView = Utilities.getDocumentView(component);
811
        if (rootView == null) return;
812
        
813
        bdoc.readLock();
814
        try {
815
            int yOffset = textUI.getPosFromY(mouseY);
816
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
817
            hierarchy.lock();
818
            try {
819
                Fold f = FoldUtilities.findOffsetFold(hierarchy, yOffset);
820
                if (f == null) {
821
                    return;
822
                }
823
                if (f.isCollapsed()) {
824
                    LOG.log(Level.WARNING, "Clicked on a collapsed fold {0} at {1}", new Object[] { f, mouseY });
825
                    return;
826
                }
827
                int startOffset = f.getStartOffset();
828
                int endOffset = f.getEndOffset();
829
                
830
                int startY = textUI.getYFromPos(startOffset);
831
                int nextLineOffset = Utilities.getRowStart(bdoc, startOffset, 1);
832
                int nextY = textUI.getYFromPos(nextLineOffset);
833
834
                if (mouseY >= startY && mouseY <= nextY) {
835
                    LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
836
                            new Object[] { mouseY, startY, nextY });
837
                    return;
838
                }
839
840
                startY = textUI.getYFromPos(endOffset);
841
                nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
842
                nextY = textUI.getYFromPos(nextLineOffset);
843
844
                if (mouseY >= startY && mouseY <= nextY) {
845
                    // the mouse can be positioned above the marker (the fold found above), or
846
                    // below it; in that case, the immediate enclosing fold should be used - should be the fold
847
                    // that corresponds to the nextLineOffset, if any
848
                    int h2 = (startY + nextY) / 2;
849
                    if (mouseY >= h2) {
850
                        Fold f2 = f;
851
                        
852
                        f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
853
                        if (f == null) {
854
                            // fold does not exist for the position below end-of-fold indicator
855
                            return;
856
                        }
857
                    }
858
                    
859
                }
860
                
861
                LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
862
                hierarchy.collapse(f);
863
            } finally {
864
                hierarchy.unlock();
865
            }
866
        } finally {
867
            bdoc.readUnlock();
868
        }        
869
    }
870
    
871
    private void performAction(final Mark mark, final boolean shiftFold) {
872
        Document doc = component.getDocument();
873
        doc.render(new Runnable() {
874
            @Override
875
            public void run() {
876
                ViewHierarchy vh = ViewHierarchy.get(component);
877
                LockedViewHierarchy lockedVH = vh.lock();
878
                try {
879
                    int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
880
                    if (pViewIndex >= 0) {
881
                        ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
882
                        int pViewStartOffset = pViewDesc.getStartOffset();
883
                        int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
884
                        // Find corresponding fold
885
                        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
886
                        foldHierarchy.lock();
887
                        try {
888
                            int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
889
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
890
                            Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
891
                            if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
892
                                foldHierarchy.toggle(clickedFold);
893
                            }
894
                        } catch (BadLocationException ble) {
895
                            LOG.log(Level.WARNING, null, ble);
896
                        } finally {
897
                            foldHierarchy.unlock();
898
                        }
899
                    }
900
                } finally {
901
                    lockedVH.unlock();
902
                }
903
            }
904
        });
905
    }
906
    
907
    protected int getMarkSize(Graphics g){
908
        if (g != null){
909
            FontMetrics fm = g.getFontMetrics(getColoring().getFont());
910
            if (fm != null){
911
                int ret = fm.getAscent() - fm.getDescent();
912
                return ret - ret%2;
913
            }
914
        }
915
        return -1;
916
    }
917
    
918
    private boolean hasMousePoint() {
919
        return mousePoint >= 0;
920
    }
921
    
922
    private boolean isActivated(int y1, int y2) {
923
        return hasMousePoint() && 
924
               (mousePoint >= y1 && mousePoint < y2);
925
    }
926
    
927
    private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
928
        Stroke origStroke = g2d.getStroke();
929
        g2d.setStroke(getStroke(origStroke, active));
930
        g2d.drawLine(x1, y1, x2, y2);
931
        g2d.setStroke(origStroke);
932
    }
933
    
934
    protected @Override void paintComponent(Graphics g) {
935
        if (!enabled) {
936
            return;
937
        }
938
        
939
        Rectangle clip = getVisibleRect();//g.getClipBounds();
940
        visibleMarks.clear();
941
        
942
        Coloring coloring = getColoring();
943
        g.setColor(coloring.getBackColor());
944
        g.fillRect(clip.x, clip.y, clip.width, clip.height);
945
        g.setColor(coloring.getForeColor());
946
947
        AbstractDocument adoc = (AbstractDocument)component.getDocument();
948
        adoc.readLock();
949
        try {
950
            List<? extends PaintInfo> ps = getPaintInfo(clip);
951
            Font defFont = coloring.getFont();
952
            int markSize = getMarkSize(g);
953
            int halfMarkSize = markSize / 2;
954
            int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
955
            int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
956
            int lineX = markX + halfMarkSize; // x position of the centre of mark
957
958
            LOG.fine("CFSBar: PAINT START ------\n");
959
            int descent = g.getFontMetrics(defFont).getDescent();
960
            PaintInfo previousInfo = null;
961
            Graphics2D g2d = (Graphics2D)g;
962
            LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
963
964
            for(PaintInfo paintInfo : ps) {
965
                boolean isFolded = paintInfo.isCollapsed();
966
                int y = paintInfo.getPaintY();
967
                int height = paintInfo.getPaintHeight();
968
                int markY = y + descent; // y position of mark rectangle
969
                int paintOperation = paintInfo.getPaintOperation();
970
971
                if (previousInfo == null) {
972
                    if (paintInfo.hasLineIn()) {
973
                        if (LOG.isLoggable(Level.FINE)) {
974
                            LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
975
                        }
976
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
977
                    }
978
                } else {
979
                    if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
980
                        // Draw middle vertical line
981
                        int prevY = previousInfo.getPaintY();
982
                        if (LOG.isLoggable(Level.FINE)) {
983
                            LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
984
                        }
985
                        drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
986
                    }
987
                }
988
989
                if (paintInfo.hasSign()) {
990
                    g.drawRect(markX, markY, markSize, markSize);
991
                    g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
992
                    String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
993
                    if (isFolded) {
994
                        if (LOG.isLoggable(Level.FINE)) {
995
                            LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
996
                        }
997
                        g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
998
                    }
999
                    if (paintOperation != SINGLE_PAINT_MARK) {
1000
                        if (LOG.isLoggable(Level.FINE)) {
1001
                            LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1002
                        }
1003
                    }
1004
                    if (paintInfo.hasLineIn()) { //[PENDING]
1005
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
1006
                    }
1007
                    if (paintInfo.hasLineOut()) {
1008
                        // This is an error in case there's a next paint info at the same y which is an end mark
1009
                        // for this mark (it must be cleared explicitly).
1010
                        drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
1011
                    }
1012
                    visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
1013
1014
                } else if (paintOperation == PAINT_LINE) {
1015
                    if (LOG.isLoggable(Level.FINE)) {
1016
                        LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1017
                    }
1018
                    // FIXME !!
1019
                    drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
1020
                } else if (paintOperation == PAINT_END_MARK) {
1021
                    if (LOG.isLoggable(Level.FINE)) {
1022
                        LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1023
                    }
1024
                    if (previousInfo == null || y != previousInfo.getPaintY()) {
1025
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
1026
                        drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
1027
                        if (paintInfo.getInnerLevel() > 0) {//[PENDING]
1028
                            if (LOG.isLoggable(Level.FINE)) {
1029
                                LOG.fine("  PAINT middle-line\n"); // NOI18N
1030
                            }
1031
                            drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
1032
                        }
1033
                    }
1034
                }
1035
1036
                previousInfo = paintInfo;
1037
            }
1038
1039
            if (previousInfo != null &&
1040
                (previousInfo.getInnerLevel() > 0 ||
1041
                 (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
1042
            ) {
1043
                drawFoldLine(g2d, previousInfo.lineOutActive, 
1044
                        lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
1045
            }
1046
1047
        } catch (BadLocationException ble) {
1048
            LOG.log(Level.WARNING, null, ble);
1049
        } finally {
1050
            LOG.fine("CFSBar: PAINT END ------\n\n");
1051
            adoc.readUnlock();
1052
        }
1053
    }
1054
    
1055
    private static Object [] getFoldList(Fold parentFold, int start, int end) {
1056
        List<Fold> ret = new ArrayList<Fold>();
1057
1058
        int index = FoldUtilities.findFoldEndIndex(parentFold, start);
1059
        int foldCount = parentFold.getFoldCount();
1060
        int idxOfFirstFoldStartingInside = -1;
1061
        while (index < foldCount) {
1062
            Fold f = parentFold.getFold(index);
1063
            if (f.getStartOffset() <= end) {
1064
                ret.add(f);
1065
            } else {
1066
                break; // no more relevant folds
1067
            }
1068
            if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
1069
                idxOfFirstFoldStartingInside = ret.size() - 1;
1070
            }
1071
            index++;
1072
        }
1073
1074
        return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
1075
    }
1076
1077
    /**
1078
     * This class should be never used by other code; will be made private
1079
     */
1080
    public class PaintInfo {
1081
        
1082
        int paintOperation;
1083
        /**
1084
         * level of the 1st marker on the line
1085
         */
1086
        int innerLevel;
1087
        
1088
        /**
1089
         * Y-coordinate of the cell
1090
         */
1091
        int paintY;
1092
        
1093
        /**
1094
         * Height of the paint cell
1095
         */
1096
        int paintHeight;
1097
        
1098
        /**
1099
         * State of the marker (+/-)
1100
         */
1101
        boolean isCollapsed;
1102
        
1103
        /**
1104
         * all markers on the line are collapsed
1105
         */
1106
        boolean allCollapsed;
1107
        int startOffset;
1108
        int endOffset;
1109
        /**
1110
         * nesting level of the last marker on the line
1111
         */
1112
        int outgoingLevel;
1113
        
1114
        /**
1115
         * Force incoming line (from above) to be present
1116
         */
1117
        boolean lineIn;
1118
        
1119
        /**
1120
         * Force outgoing line (down from marker) to be present
1121
         */
1122
        boolean lineOut;
1123
        
1124
        /**
1125
         * The 'incoming' (upper) line should be painted as active
1126
         */
1127
        boolean lineInActive;
1128
        
1129
        /**
1130
         * The 'outgoing' (down) line should be painted as active
1131
         */
1132
        boolean lineOutActive;
1133
        
1134
        /**
1135
         * The sign/marker itself should be painted as active
1136
         */
1137
        boolean signActive;
1138
        
1139
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
1140
            this.paintOperation = paintOperation;
1141
            this.innerLevel = this.outgoingLevel = innerLevel;
1142
            this.paintY = paintY;
1143
            this.paintHeight = paintHeight;
1144
            this.isCollapsed = this.allCollapsed = isCollapsed;
1145
            this.startOffset = startOffset;
1146
            this.endOffset = endOffset;
1147
1148
            switch (paintOperation) {
1149
                case PAINT_MARK:
1150
                    lineIn = false;
1151
                    lineOut = true;
1152
                    outgoingLevel++;
1153
                    break;
1154
                case SINGLE_PAINT_MARK:
1155
                    lineIn = false;
1156
                    lineOut = false;
1157
                    break;
1158
                case PAINT_END_MARK:
1159
                    lineIn = true;
1160
                    lineOut = false;
1161
                    isCollapsed = true;
1162
                    allCollapsed = true;
1163
                    break;
1164
                case PAINT_LINE:
1165
                    lineIn = lineOut = true;
1166
                    break;
1167
            }
1168
        }
1169
        
1170
        /**
1171
         * Sets active flags on inidivual parts of the mark
1172
         * @param mark
1173
         * @param lineIn
1174
         * @param lineOut S
1175
         */
1176
        void markActive(boolean mark, boolean lineIn, boolean lineOut) {
1177
            this.signActive |= mark;
1178
            this.lineInActive |= lineIn;
1179
            this.lineOutActive |= lineOut;
1180
        }
1181
        
1182
        boolean hasLineIn() {
1183
            return lineIn || innerLevel > 0;
1184
        }
1185
        
1186
        boolean hasLineOut() {
1187
            return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
1188
        }
1189
1190
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
1191
            this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
1192
        }
1193
        
1194
        public int getPaintOperation(){
1195
            return paintOperation;
1196
        }
1197
        
1198
        public int getInnerLevel(){
1199
            return innerLevel;
1200
        }
1201
        
1202
        public int getPaintY(){
1203
            return paintY;
1204
        }
1205
        
1206
        public int getPaintHeight(){
1207
            return paintHeight;
1208
        }
1209
        
1210
        public boolean isCollapsed(){
1211
            return isCollapsed;
1212
        }
1213
        
1214
         boolean isAllCollapsed() {
1215
            return allCollapsed;
1216
        }
1217
        
1218
        public void setPaintOperation(int paintOperation){
1219
            this.paintOperation = paintOperation;
1220
        }
1221
        
1222
        public void setInnerLevel(int innerLevel){
1223
            this.innerLevel = innerLevel;
1224
        }
1225
        
1226
        public @Override String toString(){
1227
            StringBuffer sb = new StringBuffer("");
1228
            if (paintOperation == PAINT_MARK){
1229
                sb.append("PAINT_MARK"); // NOI18N
1230
            }else if (paintOperation == PAINT_LINE){
1231
                sb.append("PAINT_LINE"); // NOI18N
1232
            }else if (paintOperation == PAINT_END_MARK) {
1233
                sb.append("PAINT_END_MARK"); // NOI18N
1234
            }else if (paintOperation == SINGLE_PAINT_MARK) {
1235
                sb.append("SINGLE_PAINT_MARK");
1236
            }
1237
            sb.append(",L:").append(innerLevel).append("/").append(outgoingLevel); // NOI18N
1238
            sb.append(',').append(isCollapsed ? "C" : "E"); // NOI18N
1239
            sb.append(", start=").append(startOffset).append(", end=").append(endOffset);
1240
            sb.append(", lineIn=").append(lineIn).append(", lineOut=").append(lineOut);
1241
            return sb.toString();
1242
        }
1243
        
1244
        boolean hasSign() {
1245
            return paintOperation == PAINT_MARK || paintOperation == SINGLE_PAINT_MARK;
1246
        }
1247
        
1248
        
1249
        void mergeWith(PaintInfo prevInfo) {
1250
            if (prevInfo == null) {
1251
                return;
1252
            }
1253
1254
            int operation = this.paintOperation;
1255
            boolean lineIn = prevInfo.lineIn;
1256
            boolean lineOut = prevInfo.lineOut;
1257
            
1258
            LOG.log(Level.FINE, "Merging {0} with {1}: ", new Object[] { this, prevInfo });
1259
            if (prevInfo.getPaintOperation() == PAINT_END_MARK) {
1260
                // merge with start|single -> start mark + line-in
1261
                lineIn = true;
1262
            } else {
1263
                operation = PAINT_MARK;
1264
            }
1265
1266
            int level1 = Math.min(prevInfo.innerLevel, innerLevel);
1267
            int level2 = prevInfo.outgoingLevel;
1268
1269
            if (getPaintOperation() == PAINT_END_MARK 
1270
                && innerLevel == prevInfo.outgoingLevel) {
1271
                // if merging end marker at the last level, update to the new outgoing level
1272
                level2 = outgoingLevel;
1273
            } else if (!isCollapsed) {
1274
                level2 = Math.max(prevInfo.outgoingLevel, outgoingLevel);
1275
            }
1276
1277
            if (prevInfo.getInnerLevel() < getInnerLevel()) {
1278
                int paintFrom = Math.min(prevInfo.paintY, paintY);
1279
                int paintTo = Math.max(prevInfo.paintY + prevInfo.paintHeight, paintY + paintHeight);
1280
                // at least one collapsed -> paint plus sign
1281
                boolean collapsed = prevInfo.isCollapsed() || isCollapsed();
1282
                int offsetFrom = Math.min(prevInfo.startOffset, startOffset);
1283
                int offsetTo = Math.max(prevInfo.endOffset, endOffset);
1284
                
1285
                this.paintY = paintFrom;
1286
                this.paintHeight = paintTo - paintFrom;
1287
                this.isCollapsed = collapsed;
1288
                this.startOffset = offsetFrom;
1289
                this.endOffset = offsetTo;
1290
            }
1291
            this.paintOperation = operation;
1292
            this.allCollapsed = prevInfo.allCollapsed && allCollapsed;
1293
            this.innerLevel = level1;
1294
            this.outgoingLevel = level2;
1295
            this.lineIn |= lineIn;
1296
            this.lineOut |= lineOut;
1297
            
1298
            this.signActive |= prevInfo.signActive;
1299
            this.lineInActive |= prevInfo.lineInActive;
1300
            this.lineOutActive |= prevInfo.lineOutActive;
1301
            
1302
            LOG.log(Level.FINE, "Merged result: {0}", this);
1303
        }
1304
    }
1305
    
1306
    /** Keeps info of visible folding mark */
1307
    public class Mark{
1308
        public int x;
1309
        public int y;
1310
        public int size;
1311
        public boolean isFolded;
1312
        
1313
        public Mark(int x, int y, int size, boolean isFolded){
1314
            this.x = x;
1315
            this.y = y;
1316
            this.size = size;
1317
            this.isFolded = isFolded;
1318
        }
1319
    }
1320
    
1321
    private final class Listener extends MouseAdapter implements FoldHierarchyListener, DocumentListener, Runnable {
1322
    
1323
        public Listener(){
1324
        }
1325
1326
        // --------------------------------------------------------------------
1327
        // FoldHierarchyListener implementation
1328
        // --------------------------------------------------------------------
1329
1330
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
1331
            refresh();
1332
        }
1333
1334
        // --------------------------------------------------------------------
1335
        // DocumentListener implementation
1336
        // --------------------------------------------------------------------
1337
1338
        public void insertUpdate(DocumentEvent evt) {
1339
            if (!(evt instanceof BaseDocumentEvent)) return;
1340
1341
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1342
            if (bevt.getLFCount() > 0) { // one or more lines inserted
1343
                refresh();
1344
            }
1345
        }
1346
1347
        public void removeUpdate(DocumentEvent evt) {
1348
            if (!(evt instanceof BaseDocumentEvent)) return;
1349
1350
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1351
            if (bevt.getLFCount() > 0) { // one or more lines removed
1352
                refresh();
1353
            }
1354
        }
1355
1356
        public void changedUpdate(DocumentEvent evt) {
1357
        }
1358
1359
        // --------------------------------------------------------------------
1360
        // MouseListener implementation
1361
        // --------------------------------------------------------------------
1362
1363
        @Override
1364
        public void mousePressed (MouseEvent e) {
1365
            Mark mark = getClickedMark(e);
1366
            if (mark!=null){
1367
                e.consume();
1368
                performAction(mark, (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
1369
            }
1370
        }
1371
1372
        @Override
1373
        public void mouseClicked(MouseEvent e) {
1374
            // #102288 - missing event consuming caused quick doubleclicks to break
1375
            // fold expanding/collapsing and move caret to the particular line
1376
            if (e.getClickCount() > 1) {
1377
                LOG.log(Level.FINEST, "Mouse {0}click at {1}", new Object[] { e.getClickCount(), e.getY()});
1378
                Mark mark = getClickedMark(e);
1379
                try {
1380
                    performActionAt(mark, e.getY());
1381
                } catch (BadLocationException ex) {
1382
                    LOG.log(Level.WARNING, "Error during fold expansion using sideline", ex);
1383
                }
1384
            } else {
1385
                e.consume();
1386
            }
1387
        }
1388
1389
        private void refreshIfMouseOutside(Point pt) {
1390
            mousePoint = (int)pt.getY();
1391
            if (LOG.isLoggable(Level.FINEST)) {
1392
                if (mouseBoundary == null) {
1393
                    LOG.log(Level.FINEST, "Mouse boundary not set, refreshing: {0}", mousePoint);
1394
                } else {
1395
                    LOG.log(Level.FINEST, "Mouse {0} inside known mouse boundary: {1}-{2}", 
1396
                            new Object[] { mousePoint, mouseBoundary.y, mouseBoundary.getMaxY() });
1397
                }
1398
            }
1399
            if (mouseBoundary == null || mousePoint < mouseBoundary.y || mousePoint > mouseBoundary.getMaxY()) {
1400
                refresh();
1401
            }
1402
        }
1403
        
1404
        @Override
1405
        public void mouseMoved(MouseEvent e) {
1406
            refreshIfMouseOutside(e.getPoint());
1407
        }
1408
        
1409
        public void mouseEntered(MouseEvent e) {
1410
            refreshIfMouseOutside(e.getPoint());
1411
        }
1412
        
1413
        public void mouseExited(MouseEvent e) {
1414
            mousePoint = NO_MOUSE_POINT;
1415
            refresh();
1416
        }
1417
        
1418
1419
        // --------------------------------------------------------------------
1420
        // private implementation
1421
        // --------------------------------------------------------------------
1422
1423
        private Mark getClickedMark(MouseEvent e){
1424
            if (e == null || !SwingUtilities.isLeftMouseButton(e)) {
1425
                return null;
1426
            }
1427
            
1428
            int x = e.getX();
1429
            int y = e.getY();
1430
            for (Mark mark : visibleMarks) {
1431
                if (x >= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)) {
1432
                    return mark;
1433
                }
1434
            }
1435
            return null;
1436
        }
1437
1438
        private void refresh() {
1439
            SwingUtilities.invokeLater(this);
1440
        }
1441
1442
        @Override
1443
        public void run() {
1444
            repaint();
1445
        }
1446
    } // End of Listener class
1447
    
1448
    @Override
1449
    public AccessibleContext getAccessibleContext() {
1450
        if (accessibleContext == null) {
1451
            accessibleContext = new AccessibleJComponent() {
1452
                public @Override AccessibleRole getAccessibleRole() {
1453
                    return AccessibleRole.PANEL;
1454
                }
1455
            };
1456
            accessibleContext.setAccessibleName(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSN_CodeFoldingSideBar")); //NOI18N
1457
        accessibleContext.setAccessibleDescription(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSD_CodeFoldingSideBar")); //NOI18N
1458
        }
1459
        return accessibleContext;
1460
    }
1461
1462
    private Coloring getColoring() {
1463
        if (attribs == null) {
1464
            if (fcsLookupResult == null) {
1465
                fcsLookupResult = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component))
1466
                        .lookupResult(FontColorSettings.class);
1467
                fcsLookupResult.addLookupListener(WeakListeners.create(LookupListener.class, fcsTracker, fcsLookupResult));
1468
            }
1469
            
1470
            FontColorSettings fcs = fcsLookupResult.allInstances().iterator().next();
1471
            AttributeSet attr = fcs.getFontColors(FontColorNames.CODE_FOLDING_BAR_COLORING);
1472
            if (attr == null) {
1473
                attr = fcs.getFontColors(FontColorNames.DEFAULT_COLORING);
1474
            } else {
1475
                attr = AttributesUtilities.createComposite(attr, fcs.getFontColors(FontColorNames.DEFAULT_COLORING));
1476
            }
1477
            attribs = attr;
1478
        }        
1479
        return Coloring.fromAttributeSet(attribs);
1480
    }
1481
    
1482
}
(-)a/editor.fold/src/org/netbeans/editor/CustomFoldManager.java (+769 lines)
Line 0 Link Here
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-2006 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
45
package org.netbeans.editor;
46
47
import org.netbeans.modules.editor.fold.*;
48
import javax.swing.text.Document;
49
import javax.swing.text.BadLocationException;
50
import javax.swing.text.Position;
51
import javax.swing.event.DocumentEvent;
52
import java.util.*;
53
import java.util.logging.Level;
54
import java.util.logging.Logger;
55
import java.util.regex.Pattern;
56
import java.util.regex.Matcher;
57
import org.netbeans.api.editor.fold.Fold;
58
import org.netbeans.api.editor.fold.FoldHierarchy;
59
import org.netbeans.api.editor.fold.FoldType;
60
import org.netbeans.api.lexer.Token;
61
import org.netbeans.api.lexer.TokenHierarchy;
62
import org.netbeans.api.lexer.TokenSequence;
63
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
64
import org.netbeans.spi.editor.fold.FoldManager;
65
import org.netbeans.spi.editor.fold.FoldManagerFactory;
66
import org.netbeans.spi.editor.fold.FoldOperation;
67
import org.openide.util.Parameters;
68
import org.openide.util.RequestProcessor;
69
70
/**
71
 * Fold maintainer that creates and updates custom folds.
72
 *
73
 * @author Dusan Balek, Miloslav Metelka
74
 * @version 1.00
75
 * @deprecated Please use {@link org.netbeans.api.editor.fold.FoldingSupport#userFoldManager} to create an instance of
76
 * the fold manager, or {@link org.netbeans.api.editor.fold.FoldingSupport#userFoldManagerFactory} to register factory
77
 * instance in a layer.
78
 */
79
@Deprecated
80
public final class CustomFoldManager implements FoldManager, Runnable {
81
    
82
    private static final Logger LOG = Logger.getLogger(CustomFoldManager.class.getName());
83
    
84
    public static final FoldType CUSTOM_FOLD_TYPE = new FoldType("custom-fold"); // NOI18N
85
86
    private FoldOperation operation;
87
    private Document doc;
88
    private org.netbeans.editor.GapObjectArray markArray = new org.netbeans.editor.GapObjectArray();
89
    private int minUpdateMarkOffset = Integer.MAX_VALUE;
90
    private int maxUpdateMarkOffset = -1;
91
    private List removedFoldList;
92
    private HashMap customFoldId = new HashMap();
93
94
    private static final RequestProcessor RP = new RequestProcessor(CustomFoldManager.class.getName(),
95
            1, false, false);
96
    private final RequestProcessor.Task task = RP.create(this);
97
    
98
    private final String tokenId;
99
    
100
    public CustomFoldManager() {
101
        this.tokenId = "comment";
102
    }
103
104
    public void init(FoldOperation operation) {
105
        this.operation = operation;
106
        if (LOG.isLoggable(Level.FINE)) {
107
            LOG.log(Level.FINE, "Initialized: {0}", System.identityHashCode(this));
108
        }
109
    }
110
    
111
    private FoldOperation getOperation() {
112
        return operation;
113
    }
114
115
    public void initFolds(FoldHierarchyTransaction transaction) {
116
        doc = getOperation().getHierarchy().getComponent().getDocument();
117
        task.schedule(300);
118
    }
119
120
    public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
121
        processRemovedFolds(transaction);
122
        task.schedule(300);
123
    }
124
125
    public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
126
        processRemovedFolds(transaction);
127
        removeAffectedMarks(evt, transaction);
128
        task.schedule(300);
129
    }
130
    
131
    public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
132
    }
133
    
134
    public void removeEmptyNotify(Fold emptyFold) {
135
        removeFoldNotify(emptyFold);
136
    }
137
    
138
    public void removeDamagedNotify(Fold damagedFold) {
139
        removeFoldNotify(damagedFold);
140
    }
141
    
142
    public void expandNotify(Fold expandedFold) {
143
        
144
    }
145
146
    public void release() {
147
        if (LOG.isLoggable(Level.FINE)) {
148
            LOG.log(Level.FINE, "Released: {0}", System.identityHashCode(this));
149
        }
150
    }
151
152
    public void run() {
153
        if (operation.isReleased()) {
154
            if (LOG.isLoggable(Level.FINE)) {
155
                LOG.log(Level.FINE, "Update skipped, already relaesed: {0}", System.identityHashCode(this));
156
            }
157
            return;
158
        }
159
        ((BaseDocument) doc).readLock();
160
        try {
161
            TokenHierarchy th = TokenHierarchy.get(doc);
162
            if (th != null && th.isActive()) {
163
                FoldHierarchy hierarchy = getOperation().getHierarchy();
164
                hierarchy.lock();
165
                try {
166
                    if (operation.isReleased()) {
167
                        if (LOG.isLoggable(Level.FINE)) {
168
                            LOG.log(Level.FINE, "Update skipped, already relaesed: {0}", System.identityHashCode(this));
169
                        }
170
                        return;
171
                    }
172
                    if (LOG.isLoggable(Level.FINE)) {
173
                        LOG.log(Level.FINE, "Updating: {0}", System.identityHashCode(this));
174
                    }
175
                    FoldHierarchyTransaction transaction = getOperation().openTransaction();
176
                    try {
177
                        updateFolds(th.tokenSequence(), transaction);
178
                    } finally {
179
                        transaction.commit();
180
                    }
181
                } finally {
182
                    hierarchy.unlock();
183
                }
184
            }
185
        } finally {
186
            ((BaseDocument) doc).readUnlock();
187
        }
188
    }
189
    
190
    private void removeFoldNotify(Fold removedFold) {
191
        if (removedFoldList == null) {
192
            removedFoldList = new ArrayList(3);
193
        }
194
        removedFoldList.add(removedFold);
195
    }
196
    
197
    private void removeAffectedMarks(DocumentEvent evt, FoldHierarchyTransaction transaction) {
198
        int removeOffset = evt.getOffset();
199
        int markIndex = findMarkIndex(removeOffset);
200
        if (markIndex < getMarkCount()) {
201
            FoldMarkInfo mark;
202
            while (markIndex >= 0 && (mark = getMark(markIndex)).getOffset() == removeOffset) {
203
                mark.release(false, transaction);
204
                removeMark(markIndex);
205
                markIndex--;
206
            }
207
        }
208
    }
209
    
210
    private void processRemovedFolds(FoldHierarchyTransaction transaction) {
211
        if (removedFoldList != null) {
212
            for (int i = removedFoldList.size() - 1; i >= 0; i--) {
213
                Fold removedFold = (Fold)removedFoldList.get(i);
214
                FoldMarkInfo startMark = (FoldMarkInfo)getOperation().getExtraInfo(removedFold);
215
                if (startMark.getId() != null)
216
                    customFoldId.put(startMark.getId(), Boolean.valueOf(removedFold.isCollapsed())); // remember the last fold's state before remove
217
                FoldMarkInfo endMark = startMark.getPairMark(); // get prior releasing
218
                if (getOperation().isStartDamaged(removedFold)) { // start mark area was damaged
219
                    startMark.release(true, transaction); // forced remove
220
                }
221
                if (getOperation().isEndDamaged(removedFold)) {
222
                    endMark.release(true, transaction);
223
                }
224
            }
225
        }
226
        removedFoldList = null;
227
    }
228
229
    private void markUpdate(FoldMarkInfo mark) {
230
        markUpdate(mark.getOffset());
231
    }
232
    
233
    private void markUpdate(int offset) {
234
        if (offset < minUpdateMarkOffset) {
235
            minUpdateMarkOffset = offset;
236
        }
237
        if (offset > maxUpdateMarkOffset) {
238
            maxUpdateMarkOffset = offset;
239
        }
240
    }
241
    
242
    private FoldMarkInfo getMark(int index) {
243
        return (FoldMarkInfo)markArray.getItem(index);
244
    }
245
    
246
    private int getMarkCount() {
247
        return markArray.getItemCount();
248
    }
249
    
250
    private void removeMark(int index) {
251
        if (LOG.isLoggable(Level.FINE)) {
252
            LOG.fine("Removing mark from ind=" + index + ": " + getMark(index)); // NOI18N
253
        }
254
        markArray.remove(index, 1);
255
    }
256
    
257
    private void insertMark(int index, FoldMarkInfo mark) {
258
        markArray.insertItem(index, mark);
259
        if (LOG.isLoggable(Level.FINE)) {
260
            LOG.fine("Inserted mark at ind=" + index + ": " + mark); // NOI18N
261
        }
262
    }
263
264
    private int findMarkIndex(int offset) {
265
        int markCount = getMarkCount();
266
        int low = 0;
267
        int high = markCount - 1;
268
        
269
        while (low <= high) {
270
            int mid = (low + high) / 2;
271
            int midMarkOffset = getMark(mid).getOffset();
272
            
273
            if (midMarkOffset < offset) {
274
                low = mid + 1;
275
            } else if (midMarkOffset > offset) {
276
                high = mid - 1;
277
            } else {
278
                // mark starting exactly at the given offset found
279
                // If multiple -> find the one with highest index
280
                mid++;
281
                while (mid < markCount && getMark(mid).getOffset() == offset) {
282
                    mid++;
283
                }
284
                mid--;
285
                return mid;
286
            }
287
        }
288
        return low; // return higher index (e.g. for insert)
289
    }
290
    
291
    private List<FoldMarkInfo> getMarkList(TokenSequence seq) {
292
        List<FoldMarkInfo> markList = null;
293
        
294
        for(seq.moveStart(); seq.moveNext(); ) {
295
            Token token = seq.token();
296
            FoldMarkInfo info;
297
            try {
298
                info = scanToken(token);
299
            } catch (BadLocationException e) {
300
                LOG.log(Level.WARNING, null, e);
301
                info = null;
302
            }
303
304
            if (info != null) {
305
                if (markList == null) {
306
                    markList = new ArrayList<FoldMarkInfo>();
307
                }
308
                markList.add(info);
309
            }
310
        }
311
312
        return markList;
313
    }
314
    
315
    private void processTokenList(TokenSequence seq, FoldHierarchyTransaction transaction) {
316
        List<FoldMarkInfo> markList = getMarkList(seq);
317
        int markListSize;
318
        if (markList != null && ((markListSize = markList.size()) > 0)) {
319
            // Find the index for insertion
320
            int offset = ((FoldMarkInfo)markList.get(0)).getOffset();
321
            int arrayMarkIndex = findMarkIndex(offset);
322
            // Remember the corresponding mark in the array as well
323
            FoldMarkInfo arrayMark;
324
            int arrayMarkOffset;
325
            if (arrayMarkIndex < getMarkCount()) {
326
                arrayMark = getMark(arrayMarkIndex);
327
                arrayMarkOffset = arrayMark.getOffset();
328
            } else { // at last mark
329
                arrayMark = null;
330
                arrayMarkOffset = Integer.MAX_VALUE;
331
            }
332
333
            for (int i = 0; i < markListSize; i++) {
334
                FoldMarkInfo listMark = (FoldMarkInfo)markList.get(i);
335
                int listMarkOffset = listMark.getOffset();
336
                if (i == 0 || i == markListSize - 1) {
337
                    // Update the update-offsets by the first and last marks in the list
338
                    markUpdate(listMarkOffset);
339
                }
340
                while (listMarkOffset >= arrayMarkOffset) {
341
                    if (listMarkOffset == arrayMarkOffset) {
342
                        // At the same offset - likely the same mark
343
                        //   -> retain the collapsed state
344
                        listMark.setCollapsed(arrayMark.isCollapsed());
345
                    }
346
                    if (!arrayMark.isReleased()) { // make sure that the mark is released
347
                        arrayMark.release(false, transaction); 
348
                    }
349
                    removeMark(arrayMarkIndex);
350
                    if (LOG.isLoggable(Level.FINE)) {
351
                        LOG.fine("Removed dup mark from ind=" + arrayMarkIndex + ": " + arrayMark); // NOI18N
352
                    }
353
                    if (arrayMarkIndex < getMarkCount()) {
354
                        arrayMark = getMark(arrayMarkIndex);
355
                        arrayMarkOffset = arrayMark.getOffset();
356
                    } else { // no more marks
357
                        arrayMark = null;
358
                        arrayMarkOffset = Integer.MAX_VALUE;
359
                    }
360
                }
361
                // Insert the listmark
362
                insertMark(arrayMarkIndex, listMark);
363
                if (LOG.isLoggable(Level.FINE)) {
364
                    LOG.fine("Inserted mark at ind=" + arrayMarkIndex + ": " + listMark); // NOI18N
365
                }
366
                arrayMarkIndex++;
367
            }
368
        }
369
    }
370
371
    private void updateFolds(TokenSequence seq, FoldHierarchyTransaction transaction) {
372
373
        if (seq != null && !seq.isEmpty()) {
374
            processTokenList(seq, transaction);
375
        }
376
377
        if (maxUpdateMarkOffset == -1) { // no updates
378
            return;
379
        }
380
        
381
        // Find the first mark to update and init the prevMark and parentMark prior the loop
382
        int index = findMarkIndex(minUpdateMarkOffset);
383
        FoldMarkInfo prevMark;
384
        FoldMarkInfo parentMark;
385
        if (index == 0) { // start from begining
386
            prevMark = null;
387
            parentMark = null;
388
        } else {
389
            prevMark = getMark(index - 1);
390
            parentMark = prevMark.getParentMark();
391
        }
392
        
393
        // Iterate through the changed marks in the mark array 
394
        int markCount = getMarkCount();
395
        while (index < markCount) { // process the marks
396
            FoldMarkInfo mark = getMark(index);
397
398
            // If the mark was released then it must be removed
399
            if (mark.isReleased()) {
400
                if (LOG.isLoggable(Level.FINE)) {
401
                    LOG.fine("Removing released mark at ind=" + index + ": " + mark); // NOI18N
402
                }
403
                removeMark(index);
404
                markCount--;
405
                continue;
406
            }
407
408
            // Update mark's status (folds, parentMark etc.)
409
            if (mark.isStartMark()) { // starting a new fold
410
                if (prevMark == null || prevMark.isStartMark()) { // new level
411
                    mark.setParentMark(prevMark); // prevMark == null means root level
412
                    parentMark = prevMark;
413
414
                } // same level => parent to the parent of the prevMark
415
416
            } else { // end mark
417
                if (prevMark != null) {
418
                    if (prevMark.isStartMark()) { // closing nearest fold
419
                        prevMark.setEndMark(mark, false, transaction);
420
421
                    } else { // prevMark is end mark - closing its parent fold
422
                        if (parentMark != null) {
423
                            // mark's parent gets set as well
424
                            parentMark.setEndMark(mark, false, transaction);
425
                            parentMark = parentMark.getParentMark();
426
427
                        } else { // prevMark's parentMark is null (top level)
428
                            mark.makeSolitaire(false, transaction);
429
                        }
430
                    }
431
                    
432
                } else { // prevMark is null
433
                    mark.makeSolitaire(false, transaction);
434
                }
435
            }
436
437
            // Set parent mark of the mark
438
            mark.setParentMark(parentMark);
439
440
            
441
            prevMark = mark;
442
            index++;
443
        }
444
445
        minUpdateMarkOffset = Integer.MAX_VALUE;
446
        maxUpdateMarkOffset = -1;
447
        
448
        if (LOG.isLoggable(Level.FINE)) {
449
            LOG.fine("MARKS DUMP:\n" + this); //NOI18N
450
        }
451
    }
452
    
453
    public @Override String toString() {
454
        StringBuffer sb = new StringBuffer();
455
        int markCount = getMarkCount();
456
        int markCountDigitCount = Integer.toString(markCount).length();
457
        for (int i = 0; i < markCount; i++) {
458
            sb.append("["); // NOI18N
459
            String iStr = Integer.toString(i);
460
            appendSpaces(sb, markCountDigitCount - iStr.length());
461
            sb.append(iStr);
462
            sb.append("]:"); // NOI18N
463
            FoldMarkInfo mark = getMark(i);
464
            
465
            // Add extra indent regarding the depth in hierarchy
466
            int indent = 0;
467
            FoldMarkInfo parentMark = mark.getParentMark();
468
            while (parentMark != null) {
469
                indent += 4;
470
                parentMark = parentMark.getParentMark();
471
            }
472
            appendSpaces(sb, indent);
473
474
            sb.append(mark);
475
            sb.append('\n');
476
        }
477
        return sb.toString();
478
    }
479
    
480
    private static void appendSpaces(StringBuffer sb, int spaces) {
481
        while (--spaces >= 0) {
482
            sb.append(' ');
483
        }
484
    }
485
486
    private static Pattern pattern = Pattern.compile(
487
            "(<\\s*editor-fold" +
488
            // id="x"[opt] defaultstate="y"[opt] desc="z"[opt] defaultstate="a"[opt]
489
            // id must be first, the rest of attributes in random order
490
            "(?:(?:\\s+id=\"(\\S*)\")?(?:\\s+defaultstate=\"(\\S*?)\")?(?:\\s+desc=\"([\\S \\t]*?)\")?(?:\\s+defaultstate=\"(\\S*?)\")?)" +
491
            "\\s*>)|(?:</\\s*editor-fold\\s*>)"); // NOI18N
492
493
    private FoldMarkInfo scanToken(Token token) throws BadLocationException {
494
        // ignore any token that is not comment
495
        if (token.id().primaryCategory() != null && token.id().primaryCategory().startsWith(tokenId)) { //NOI18N
496
            Matcher matcher = pattern.matcher(token.text());
497
            if (matcher.find()) {
498
                if (matcher.group(1) != null) { // fold's start mark found
499
                    boolean state;
500
                    if (matcher.group(3) != null) {
501
                        state = "collapsed".equals(matcher.group(3)); // remember the defaultstate // NOI18N
502
                    } else {
503
                        state = "collapsed".equals(matcher.group(5));
504
                    }
505
                    
506
                    if (matcher.group(2) != null) { // fold's id exists
507
                        Boolean collapsed = (Boolean)customFoldId.get(matcher.group(2));
508
                        if (collapsed != null)
509
                            state = collapsed.booleanValue(); // fold's state is already known from the past
510
                        else
511
                            customFoldId.put(matcher.group(2), Boolean.valueOf(state));
512
                    }
513
                    return new FoldMarkInfo(true, token.offset(null), matcher.end(0), matcher.group(2), state, matcher.group(4)); // NOI18N
514
                } else { // fold's end mark found
515
                    return new FoldMarkInfo(false, token.offset(null), matcher.end(0), null, false, null);
516
                }
517
            }
518
        }
519
        return null;
520
    }
521
522
    private final class FoldMarkInfo {
523
524
        private boolean startMark;
525
        private Position pos;
526
        private int length;
527
        private String id;
528
        private boolean collapsed;
529
        private String description;
530
531
        /** Matching pair mark used for fold construction */
532
        private FoldMarkInfo pairMark;
533
        
534
        /** Parent mark defining nesting in the mark hierarchy. */
535
        private FoldMarkInfo parentMark;
536
        
537
        /**
538
         * Fold that corresponds to this mark (if it's start mark).
539
         * It can be null if this mark is end mark or if it currently
540
         * does not have the fold assigned.
541
         */
542
        private Fold fold;
543
        
544
        private boolean released;
545
        
546
        private FoldMarkInfo(boolean startMark, int offset,
547
                             int length, String id, boolean collapsed, String description)
548
        throws BadLocationException {
549
550
            this.startMark = startMark;
551
            this.pos = doc.createPosition(offset);
552
            this.length = length;
553
            this.id = id;
554
            this.collapsed = collapsed;
555
            this.description = description;
556
        }
557
558
        public String getId() {
559
            return id;
560
        }
561
562
        public String getDescription() {
563
            return description;
564
        }
565
566
        public boolean isStartMark() {
567
            return startMark;
568
        }
569
570
        public int getLength() {
571
            return length;
572
        }
573
574
        public int getOffset() {
575
            return pos.getOffset();
576
        }
577
        
578
        public int getEndOffset() {
579
            return getOffset() + getLength();
580
        }
581
582
        public boolean isCollapsed() {
583
            return (fold != null) ? fold.isCollapsed() : collapsed;
584
        }
585
        
586
        public boolean hasFold() {
587
            return (fold != null);
588
        }
589
        
590
        public void setCollapsed(boolean collapsed) {
591
            this.collapsed = collapsed;
592
        }
593
        
594
        public boolean isSolitaire() {
595
            return (pairMark == null);
596
        }
597
        
598
        public void makeSolitaire(boolean forced, FoldHierarchyTransaction transaction) {
599
            if (!isSolitaire()) {
600
                if (isStartMark()) {
601
                    setEndMark(null, forced, transaction);
602
                } else { // end mark
603
                    getPairMark().setEndMark(null, forced, transaction);
604
                }
605
            }
606
        }
607
        
608
        public boolean isReleased() {
609
            return released;
610
        }
611
        
612
        /**
613
         * Release this mark and mark for update.
614
         */
615
        public void release(boolean forced, FoldHierarchyTransaction transaction) {
616
            if (!released) {
617
                makeSolitaire(forced, transaction);
618
                released = true;
619
                markUpdate(this);
620
            }
621
        }
622
        
623
        public FoldMarkInfo getPairMark() {
624
            return pairMark;
625
        }
626
        
627
        private void setPairMark(FoldMarkInfo pairMark) {
628
            this.pairMark = pairMark;
629
        }
630
631
        public void setEndMark(FoldMarkInfo endMark, boolean forced,
632
        FoldHierarchyTransaction transaction) {
633
            if (!isStartMark()) {
634
                throw new IllegalStateException("Not start mark"); // NOI18N
635
            }
636
            if (pairMark == endMark) {
637
                return;
638
            }
639
            
640
            if (pairMark != null) { // is currently paired to an end mark
641
                releaseFold(forced, transaction);
642
                pairMark.setPairMark(null);
643
            }
644
645
            pairMark = endMark;
646
            if (endMark != null) {
647
                if (!endMark.isSolitaire()) { // make solitaire first
648
                    endMark.makeSolitaire(false, transaction); // not forced here
649
                }
650
                endMark.setPairMark(this);
651
                endMark.setParentMark(this.getParentMark());
652
                ensureFoldExists(transaction);
653
            }
654
        }
655
        
656
        public FoldMarkInfo getParentMark() {
657
            return parentMark;
658
        }
659
        
660
        public void setParentMark(FoldMarkInfo parentMark) {
661
            this.parentMark = parentMark;
662
        }
663
        
664
        private void releaseFold(boolean forced, FoldHierarchyTransaction transaction) {
665
            if (isSolitaire() || !isStartMark()) {
666
               throw new IllegalStateException();
667
            }
668
669
            if (fold != null) {
670
                setCollapsed(fold.isCollapsed()); // serialize the collapsed info
671
                if (!forced) {
672
                    getOperation().removeFromHierarchy(fold, transaction);
673
                }
674
                fold = null;
675
            }
676
        }
677
678
        public Fold getFold() {
679
            if (isSolitaire()) {
680
                return null;
681
            }
682
            if (!isStartMark()) {
683
                return pairMark.getFold();
684
            }
685
            return fold;
686
        }
687
        
688
        public void ensureFoldExists(FoldHierarchyTransaction transaction) {
689
            if (isSolitaire() || !isStartMark()) {
690
                throw new IllegalStateException();
691
            }
692
693
            if (fold == null) {
694
                try {
695
                    if (!startMark) {
696
                        throw new IllegalStateException("Not start mark: " + this); // NOI18N
697
                    }
698
                    if (pairMark == null) {
699
                        throw new IllegalStateException("No pairMark for mark:" + this); // NOI18N
700
                    }
701
                    int startOffset = getOffset();
702
                    int startGuardedLength = getLength();
703
                    int endGuardedLength = pairMark.getLength();
704
                    int endOffset = pairMark.getOffset() + endGuardedLength;
705
                    fold = getOperation().addToHierarchy(
706
                        CUSTOM_FOLD_TYPE, getDescription(), collapsed,
707
                        startOffset, endOffset,
708
                        startGuardedLength, endGuardedLength,
709
                        this,
710
                        transaction
711
                    );
712
                } catch (BadLocationException e) {
713
                    LOG.log(Level.WARNING, null, e);
714
                }
715
            }
716
        }
717
        
718
        public @Override String toString() {
719
            StringBuffer sb = new StringBuffer();
720
            sb.append(isStartMark() ? 'S' : 'E');  // NOI18N
721
            
722
            // Check whether this mark (or its pair) has fold
723
            if (hasFold() || (!isSolitaire() && getPairMark().hasFold())) {
724
                sb.append("F"); // NOI18N
725
                
726
                // Check fold's status
727
                if (isStartMark() && (isSolitaire()
728
                        || getOffset() != fold.getStartOffset()
729
                        || getPairMark().getEndOffset() != fold.getEndOffset())
730
                ) {
731
                    sb.append("!!<"); // NOI18N
732
                    sb.append(fold.getStartOffset());
733
                    sb.append(","); // NOI18N
734
                    sb.append(fold.getEndOffset());
735
                    sb.append(">!!"); // NOI18N
736
                }
737
            }
738
739
            // Append mark's internal status
740
            sb.append(" ("); // NOI18N
741
            sb.append("o="); // NOI18N
742
            sb.append(pos.getOffset());
743
            sb.append(", l="); // NOI18N
744
            sb.append(length);
745
            sb.append(", d='"); // NOI18N
746
            sb.append(description);
747
            sb.append('\'');
748
            if (getPairMark() != null) {
749
                sb.append(", <->"); // NOI18N
750
                sb.append(getPairMark().getOffset());
751
            }
752
            if (getParentMark() != null) {
753
                sb.append(", ^"); // NOI18N
754
                sb.append(getParentMark().getOffset());
755
            }
756
            sb.append(')');
757
            
758
            return sb.toString();
759
        }
760
761
    }
762
        
763
    public static final class Factory implements FoldManagerFactory {
764
765
        public FoldManager createFoldManager() {
766
            return new CustomFoldManager();
767
        }
768
    }
769
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ApiPackageAccessor.java (+1 lines)
Lines 44-49 Link Here
44
44
45
package org.netbeans.modules.editor.fold;
45
package org.netbeans.modules.editor.fold;
46
46
47
import org.netbeans.modules.editor.fold.ui.FoldViewFactory;
47
import javax.swing.event.DocumentEvent;
48
import javax.swing.event.DocumentEvent;
48
import javax.swing.text.BadLocationException;
49
import javax.swing.text.BadLocationException;
49
import javax.swing.text.Document;
50
import javax.swing.text.Document;
(-)a/editor.lib/src/org/netbeans/editor/CustomFoldManager.java (-3 / +16 lines)
Lines 42-48 Link Here
42
 * made subject to such option by the copyright holder.
42
 * made subject to such option by the copyright holder.
43
 */
43
 */
44
44
45
package org.netbeans.editor;
45
package org.netbeans.modules.editor.fold;
46
46
47
import javax.swing.text.Document;
47
import javax.swing.text.Document;
48
import javax.swing.text.BadLocationException;
48
import javax.swing.text.BadLocationException;
Lines 59-68 Link Here
59
import org.netbeans.api.lexer.Token;
59
import org.netbeans.api.lexer.Token;
60
import org.netbeans.api.lexer.TokenHierarchy;
60
import org.netbeans.api.lexer.TokenHierarchy;
61
import org.netbeans.api.lexer.TokenSequence;
61
import org.netbeans.api.lexer.TokenSequence;
62
import org.netbeans.editor.BaseDocument;
62
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
63
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
63
import org.netbeans.spi.editor.fold.FoldManager;
64
import org.netbeans.spi.editor.fold.FoldManager;
64
import org.netbeans.spi.editor.fold.FoldManagerFactory;
65
import org.netbeans.spi.editor.fold.FoldManagerFactory;
65
import org.netbeans.spi.editor.fold.FoldOperation;
66
import org.netbeans.spi.editor.fold.FoldOperation;
67
import org.openide.util.Parameters;
66
import org.openide.util.RequestProcessor;
68
import org.openide.util.RequestProcessor;
67
69
68
/**
70
/**
Lines 72-78 Link Here
72
 * @version 1.00
74
 * @version 1.00
73
 */
75
 */
74
76
75
final class CustomFoldManager implements FoldManager, Runnable {
77
public final class CustomFoldManager implements FoldManager, Runnable {
76
    
78
    
77
    private static final Logger LOG = Logger.getLogger(CustomFoldManager.class.getName());
79
    private static final Logger LOG = Logger.getLogger(CustomFoldManager.class.getName());
78
    
80
    
Lines 89-95 Link Here
89
    private static final RequestProcessor RP = new RequestProcessor(CustomFoldManager.class.getName(),
91
    private static final RequestProcessor RP = new RequestProcessor(CustomFoldManager.class.getName(),
90
            1, false, false);
92
            1, false, false);
91
    private final RequestProcessor.Task task = RP.create(this);
93
    private final RequestProcessor.Task task = RP.create(this);
94
    
95
    private final String tokenId;
96
    
97
    public CustomFoldManager() {
98
        this.tokenId = "comment";
99
    }
92
100
101
    public CustomFoldManager(String tokenId) {
102
        Parameters.notNull("tokenId", tokenId);
103
        this.tokenId = tokenId;
104
    }
105
    
93
    public void init(FoldOperation operation) {
106
    public void init(FoldOperation operation) {
94
        this.operation = operation;
107
        this.operation = operation;
95
        if (LOG.isLoggable(Level.FINE)) {
108
        if (LOG.isLoggable(Level.FINE)) {
Lines 481-487 Link Here
481
494
482
    private FoldMarkInfo scanToken(Token token) throws BadLocationException {
495
    private FoldMarkInfo scanToken(Token token) throws BadLocationException {
483
        // ignore any token that is not comment
496
        // ignore any token that is not comment
484
        if (token.id().primaryCategory() != null && token.id().primaryCategory().startsWith("comment")) { //NOI18N
497
        if (token.id().primaryCategory() != null && token.id().primaryCategory().startsWith(tokenId)) { //NOI18N
485
            Matcher matcher = pattern.matcher(token.text());
498
            Matcher matcher = pattern.matcher(token.text());
486
            if (matcher.find()) {
499
            if (matcher.find()) {
487
                if (matcher.group(1) != null) { // fold's start mark found
500
                if (matcher.group(1) != null) { // fold's start mark found
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java (+11 lines)
Lines 57-62 Link Here
57
import java.util.List;
57
import java.util.List;
58
import java.util.Map;
58
import java.util.Map;
59
import java.util.Set;
59
import java.util.Set;
60
import java.util.concurrent.TimeUnit;
60
import java.util.logging.Level;
61
import java.util.logging.Level;
61
import java.util.logging.Logger;
62
import java.util.logging.Logger;
62
import java.util.prefs.Preferences;
63
import java.util.prefs.Preferences;
Lines 174-179 Link Here
174
    
175
    
175
    private Task initTask;
176
    private Task initTask;
176
    
177
    
178
    private FoldingEditorSupport editSupport;
179
    
177
    public static synchronized FoldHierarchy getOrCreateFoldHierarchy(JTextComponent component) {
180
    public static synchronized FoldHierarchy getOrCreateFoldHierarchy(JTextComponent component) {
178
        return getOrCreateFoldExecution(component).getHierarchy();
181
        return getOrCreateFoldExecution(component).getHierarchy();
179
    }
182
    }
Lines 256-261 Link Here
256
        } catch (BadLocationException e) {
259
        } catch (BadLocationException e) {
257
            ErrorManager.getDefault().notify(e);
260
            ErrorManager.getDefault().notify(e);
258
        }
261
        }
262
        
263
        editSupport = new FoldingEditorSupport(hierarchy, component);
264
259
    }
265
    }
260
    
266
    
261
    /* testing only */
267
    /* testing only */
Lines 263-268 Link Here
263
        getOrCreateFoldExecution(panel).getInitTask().waitFinished();
269
        getOrCreateFoldExecution(panel).getInitTask().waitFinished();
264
    }
270
    }
265
    
271
    
272
    /* testing only */
273
    static boolean waitAllTasks() throws InterruptedException {
274
        return RP.awaitTermination(30, TimeUnit.SECONDS);
275
    }
276
    
266
    private Task getInitTask() {
277
    private Task getInitTask() {
267
        return initTask;
278
        return initTask;
268
    }
279
    }
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldToolTip.java (-152 lines)
Lines 1-152 Link Here
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-2006 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
45
package org.netbeans.modules.editor.fold;
46
47
import java.awt.BorderLayout;
48
import java.awt.Color;
49
import java.awt.Dimension;
50
import java.lang.reflect.InvocationTargetException;
51
import java.lang.reflect.Method;
52
import javax.swing.JComponent;
53
import javax.swing.JEditorPane;
54
import javax.swing.JPanel;
55
import javax.swing.border.LineBorder;
56
import javax.swing.event.AncestorEvent;
57
import javax.swing.event.AncestorListener;
58
import javax.swing.text.JTextComponent;
59
import org.netbeans.modules.editor.lib2.view.DocumentView;
60
import org.openide.util.Exceptions;
61
import org.openide.util.Lookup;
62
63
/**
64
 * Component that displays a collapsed fold preview.
65
 *
66
 * @author Miloslav Metelka
67
 */
68
public class FoldToolTip extends JPanel {
69
    private int editorPaneWidth;
70
71
    public FoldToolTip(JEditorPane editorPane, final JEditorPane foldPreviewPane, Color borderColor) {
72
        setLayout(new BorderLayout());
73
        add(foldPreviewPane, BorderLayout.CENTER);
74
        putClientProperty("tooltip-type", "fold-preview"); // Checked in NbToolTip
75
76
        addGlyphGutter(foldPreviewPane);
77
        
78
        addAncestorListener(new AncestorListener() {
79
            @Override
80
            public void ancestorAdded(AncestorEvent event) {
81
            }
82
83
            @Override
84
            public void ancestorRemoved(AncestorEvent event) {
85
                // Deactivate the view hierarchy immediately for foldPreviewPane
86
                final DocumentView docView = DocumentView.get(foldPreviewPane);
87
                if (docView != null) {
88
                    docView.runTransaction(new Runnable() {
89
                        @Override
90
                        public void run() {
91
                            docView.updateLengthyAtomicEdit(+100); // Effectively disable any VH updates
92
                        }
93
                    });
94
                }
95
                // Remove the listener
96
                FoldToolTip.this.removeAncestorListener(this);
97
            }
98
99
            @Override
100
            public void ancestorMoved(AncestorEvent event) {
101
            }
102
        });
103
104
        editorPaneWidth = editorPane.getSize().width;
105
106
        setBorder(new LineBorder(borderColor));
107
        setOpaque(true);
108
    }
109
    
110
    private void addGlyphGutter(JTextComponent jtx) {
111
        ClassLoader cls = Lookup.getDefault().lookup(ClassLoader.class);
112
        Class clazz;
113
        Class editorUiClass;
114
        
115
        JComponent gutter = null;
116
        try {
117
            clazz = Class.forName("org.netbeans.editor.GlyphGutter", true, cls); // NOI18N
118
            editorUiClass = Class.forName("org.netbeans.editor.EditorUI", true, cls); // NOI18N
119
            // get the factory instance
120
            Object o = clazz.newInstance();
121
            Method m = clazz.getDeclaredMethod("createSideBar", JTextComponent.class); // NOI18N
122
            gutter = (JComponent)m.invoke(o, jtx);
123
        } catch (IllegalArgumentException ex) {
124
            Exceptions.printStackTrace(ex);
125
        } catch (InvocationTargetException ex) {
126
            Exceptions.printStackTrace(ex);
127
        } catch (NoSuchMethodException ex) {
128
            Exceptions.printStackTrace(ex);
129
        } catch (SecurityException ex) {
130
            Exceptions.printStackTrace(ex);
131
        } catch (InstantiationException ex) {
132
            Exceptions.printStackTrace(ex);
133
        } catch (IllegalAccessException ex) {
134
            Exceptions.printStackTrace(ex);
135
        } catch (ClassNotFoundException ex) {
136
            Exceptions.printStackTrace(ex);
137
        }
138
        if (gutter != null) {
139
            add(gutter, BorderLayout.WEST);
140
        }
141
    }
142
143
    @Override
144
    public Dimension getPreferredSize() {
145
        Dimension prefSize = super.getPreferredSize();
146
        // Return width like for editor pane which forces the PopupManager to display
147
        // the tooltip to align exacty with the text (below/above).
148
        prefSize.width = Math.min(prefSize.width, editorPaneWidth);
149
        return prefSize;
150
    }
151
152
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldingEditorSupport.java (+172 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2013 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
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2013 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.fold;
43
44
import java.awt.Rectangle;
45
import java.util.concurrent.Callable;
46
import java.util.logging.Logger;
47
import javax.swing.SwingUtilities;
48
import javax.swing.text.Document;
49
import javax.swing.text.JTextComponent;
50
import org.netbeans.api.editor.fold.Fold;
51
import org.netbeans.api.editor.fold.FoldHierarchy;
52
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
53
import org.netbeans.api.editor.fold.FoldHierarchyListener;
54
import org.netbeans.api.editor.fold.FoldStateChange;
55
import org.netbeans.api.editor.fold.FoldUtilities;
56
import org.netbeans.editor.BaseCaret;
57
58
/**
59
 * Provides adjustments to functions of editor component
60
 * based on folding operations.
61
 * This code was originally part of editor.lib, in BaseCaret class.
62
 * 
63
 * @author sdedic
64
 */
65
class FoldingEditorSupport implements Callable, FoldHierarchyListener {
66
    private static final Logger LOG = Logger.getLogger(FoldingEditorSupport.class.getName());
67
    
68
    /**
69
     * Component where the folding takes place
70
     */
71
    private final JTextComponent component;
72
    
73
    /**
74
     * Fold hierarchy
75
     */
76
    private final FoldHierarchy foldHierarchy;
77
    
78
    FoldingEditorSupport(FoldHierarchy h, JTextComponent component) {
79
        this.component = component;
80
        this.foldHierarchy = h;
81
        component.putClientProperty("org.netbeans.api.fold.expander", this);
82
        foldHierarchy.addFoldHierarchyListener(this);
83
    }
84
    
85
    /**
86
     * Callable that autoexpands place with caret.
87
     * The Callable is called from the BaseCaret to expand folds at the caret position,
88
     * if possible.
89
     * 
90
     * @return true, if a fold was expanded, true otherwise.
91
     */
92
    public Boolean call() {
93
        final Document doc = component.getDocument();
94
        final Boolean[] res = new Boolean[] { false };
95
        doc.render(new Runnable() {
96
            public void run() {
97
                foldHierarchy.lock();
98
                try {
99
                    int offset = component.getCaret().getDot();
100
                    Fold f = FoldUtilities.findCollapsedFold(foldHierarchy, offset, offset);
101
                    if (f != null) {
102
                        foldHierarchy.expand(f);
103
                        res[0] = true;
104
                    }
105
                } finally {
106
                    foldHierarchy.unlock();
107
                }
108
            }
109
        });
110
        return res[0];
111
    }
112
113
    public @Override void foldHierarchyChanged(FoldHierarchyEvent evt) {
114
        if (!(component.getCaret() instanceof BaseCaret)) {
115
            return;
116
        }
117
        int caretOffset = component.getCaret().getDot();
118
        final int addedFoldCnt = evt.getAddedFoldCount();
119
        final boolean scrollToView;
120
        LOG.finest("Received fold hierarchy change");
121
        if (addedFoldCnt > 0) {
122
            FoldHierarchy hierarchy = (FoldHierarchy) evt.getSource();
123
            Fold collapsed = null;
124
            boolean wasExpanded = false;
125
            while ((collapsed = FoldUtilities.findCollapsedFold(hierarchy, caretOffset, caretOffset)) != null && collapsed.getStartOffset() < caretOffset &&
126
                    collapsed.getEndOffset() > caretOffset) {
127
                        hierarchy.expand(collapsed);
128
                        wasExpanded = true;
129
            }
130
            // prevent unneeded scrolling; the user may have scrolled out using mouse already
131
            // so scroll only if the added fold may affect Y axis. Actually it's unclear why
132
            // we should reveal the current position on fold events except when caret is positioned in now-collapsed fold
133
            scrollToView = wasExpanded;
134
        } else {
135
            int startOffset = Integer.MAX_VALUE;
136
            // Set the caret's offset to the end of just collapsed fold if necessary
137
            if (evt.getAffectedStartOffset() <= caretOffset && evt.getAffectedEndOffset() >= caretOffset) {
138
                for (int i = 0; i < evt.getFoldStateChangeCount(); i++) {
139
                    FoldStateChange change = evt.getFoldStateChange(i);
140
                    if (change.isCollapsedChanged()) {
141
                        Fold fold = change.getFold();
142
                        if (fold.isCollapsed() && fold.getStartOffset() <= caretOffset && fold.getEndOffset() >= caretOffset) {
143
                            if (fold.getStartOffset() < startOffset) {
144
                                startOffset = fold.getStartOffset();
145
                            }
146
                        }
147
                    }
148
                }
149
                if (startOffset != Integer.MAX_VALUE) {
150
                    ((BaseCaret)component.getCaret()).setDot(startOffset, false);
151
                }
152
            }
153
            scrollToView = false;
154
        }
155
        // Update caret's visual position
156
        // Post the caret update asynchronously since the fold hierarchy is updated before
157
        // the view hierarchy and the views so the dispatchUpdate() could be picking obsolete
158
        // view information.
159
        if (addedFoldCnt > 1 || scrollToView) {
160
            SwingUtilities.invokeLater(new Runnable() {
161
                public @Override void run() {
162
                    LOG.finest("Updating after fold hierarchy change");
163
                    if (component == null) {
164
                        return;
165
                    }
166
                    ((BaseCaret)component.getCaret()).refresh(addedFoldCnt > 1 && !scrollToView);
167
                }
168
            });
169
        }
170
    }
171
    
172
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/Bundle.properties (+67 lines)
Line 0 Link Here
1
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
#
3
# Copyright 2013 Oracle and/or its affiliates. All rights reserved.
4
#
5
# Oracle and Java are registered trademarks of Oracle and/or its affiliates.
6
# Other names may be trademarks of their respective owners.
7
#
8
# The contents of this file are subject to the terms of either the GNU
9
# General Public License Version 2 only ("GPL") or the Common
10
# Development and Distribution License("CDDL") (collectively, the
11
# "License"). You may not use this file except in compliance with the
12
# License. You can obtain a copy of the License at
13
# http://www.netbeans.org/cddl-gplv2.html
14
# or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
15
# specific language governing permissions and limitations under the
16
# License.  When distributing the software, include this License Header
17
# Notice in each file and include the License file at
18
# nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
19
# particular file as subject to the "Classpath" exception as provided
20
# by Oracle in the GPL Version 2 section of the License file that
21
# accompanied this code. If applicable, add the following below the
22
# License Header, with the fields enclosed by brackets [] replaced by
23
# your own identifying information:
24
# "Portions Copyrighted [year] [name of copyright owner]"
25
#
26
# If you wish your version of this file to be governed by only the CDDL
27
# or only the GPL Version 2, indicate your decision by adding
28
# "[Contributor] elects to include this software in this distribution
29
# under the [CDDL or GPL Version 2] license." If you do not indicate a
30
# single choice of license, a recipient has the option to distribute
31
# your version of this file under either the CDDL, the GPL Version 2 or
32
# to extend the choice of license to its licensees as provided above.
33
# However, if you add GPL Version 2 code and therefore, elected the GPL
34
# Version 2 license, then the option applies only if the new code is
35
# made subject to such option by the copyright holder.
36
#
37
# Contributor(s):
38
#
39
# Portions Copyrighted 2013 Sun Microsystems, Inc.
40
41
# CodeFoldingSideBar
42
ACSN_CodeFoldingSideBar=Code folding side bar
43
ACSD_CodeFoldingSideBar=Code folding side bar shows text folds and allows their collapsing and expanding.
44
45
collapse-all-folds_menu_text=Co&llapse All
46
collapse-fold_menu_text=&Collapse Fold
47
expand-all-folds_menu_text=E&xpand All
48
expand-fold_menu_text=&Expand Fold
49
collapse-all-folds=Collapse All
50
collapse-fold=Collapse Fold
51
expand-all-folds=Expand All
52
expand-fold=Expand Fold
53
54
CTL_OptionsDisplayName=Folding
55
KW_Options=Fold
56
FoldOptionsPanel.langLabel.text=Language:
57
TITLE_ElementsToCollapse=Collapse by default
58
FoldOptionsPanel.enableFolds.text=Enable Code Folding
59
DefaultFoldingOptions.useDefaults.text=Override default settings
60
DefaultFoldingOptions.collapseContainer.border.title=Collapse by default
61
FoldOptionsPanel.contentPreview.text=Content preview
62
FoldOptionsPanel.foldedSummary.text=Show summary
63
Title_FoldDisplayOptions=Display Options
64
FMT_ContentPlaceholder_1={0}...
65
FMT_ContentPlaceholder_2=...{1} 
66
FMT_ContentPlaceholder_3={0} ...{1} 
67
FoldOptionsPanel.useDefaults.text=Use Default Settings
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/CodeFoldingSideBar.java (+1507 lines)
Line 0 Link Here
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
45
package org.netbeans.modules.editor.fold.ui;
46
47
import java.awt.BasicStroke;
48
import java.awt.Color;
49
import java.awt.Dimension;
50
import java.awt.Font;
51
import java.awt.FontMetrics;
52
import java.awt.Graphics;
53
import java.awt.Graphics2D;
54
import java.awt.Point;
55
import java.awt.Rectangle;
56
import java.awt.Stroke;
57
import java.awt.event.MouseAdapter;
58
import java.awt.event.MouseEvent;
59
import java.util.ArrayList;
60
import java.util.Collections;
61
import java.util.List;
62
import java.util.Map;
63
import java.util.NavigableMap;
64
import java.util.TreeMap;
65
import java.util.logging.Level;
66
import java.util.logging.Logger;
67
import java.util.prefs.PreferenceChangeEvent;
68
import java.util.prefs.PreferenceChangeListener;
69
import java.util.prefs.Preferences;
70
import javax.accessibility.Accessible;
71
import javax.accessibility.AccessibleContext;
72
import javax.accessibility.AccessibleRole;
73
import javax.swing.JComponent;
74
import javax.swing.SwingUtilities;
75
import javax.swing.event.DocumentEvent;
76
import javax.swing.event.DocumentListener;
77
import javax.swing.text.AbstractDocument;
78
import javax.swing.text.AttributeSet;
79
import javax.swing.text.BadLocationException;
80
import javax.swing.text.Document;
81
import javax.swing.text.JTextComponent;
82
import javax.swing.text.View;
83
import org.netbeans.api.editor.fold.Fold;
84
import org.netbeans.api.editor.fold.FoldHierarchy;
85
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
86
import org.netbeans.api.editor.fold.FoldHierarchyListener;
87
import org.netbeans.api.editor.fold.FoldUtilities;
88
import org.netbeans.api.editor.mimelookup.MimeLookup;
89
import org.netbeans.api.editor.settings.AttributesUtilities;
90
import org.netbeans.api.editor.settings.FontColorNames;
91
import org.netbeans.api.editor.settings.FontColorSettings;
92
import org.netbeans.api.editor.settings.SimpleValueNames;
93
import org.netbeans.editor.BaseDocument;
94
import org.netbeans.editor.BaseDocumentEvent;
95
import org.netbeans.editor.BaseTextUI;
96
import org.netbeans.editor.Coloring;
97
import org.netbeans.editor.EditorUI;
98
import org.netbeans.editor.SideBarFactory;
99
import org.netbeans.editor.Utilities;
100
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
101
import org.netbeans.modules.editor.fold.FoldHierarchyExecution;
102
import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
103
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
104
import org.netbeans.modules.editor.lib2.view.ParagraphViewDescriptor;
105
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
106
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
107
import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
108
import org.openide.util.Lookup;
109
import org.openide.util.LookupEvent;
110
import org.openide.util.LookupListener;
111
import org.openide.util.NbBundle;
112
import org.openide.util.WeakListeners;
113
114
/**
115
 *  Code Folding Side Bar. Component responsible for drawing folding signs and responding 
116
 *  on user fold/unfold action.
117
 * <p/>
118
 * The class was copied/hidden from org.netbeans.editor.CodeFoldingSidebar. If any error is fixed here,
119
 * please fix it as well in {@link org.netbeans.editor.CodeFoldingSidebar} in this module for backward
120
 * compatibility with potential users.
121
 *
122
 *  @author  Martin Roskanin
123
 */
124
public final class CodeFoldingSideBar extends JComponent implements Accessible {
125
    public static final String PROP_SIDEBAR_MARK = "org.netbeans.editor.CodeFoldingSidebar"; // NOI18N
126
    
127
    private static final Logger LOG = Logger.getLogger(CodeFoldingSideBar.class.getName());
128
129
    /** This field should be treated as final. Subclasses are forbidden to change it. 
130
     * @deprecated Without any replacement.
131
     */
132
    protected Color backColor;
133
    /** This field should be treated as final. Subclasses are forbidden to change it. 
134
     * @deprecated Without any replacement.
135
     */
136
    protected Color foreColor;
137
    /** This field should be treated as final. Subclasses are forbidden to change it. 
138
     * @deprecated Without any replacement.
139
     */
140
    protected Font font;
141
    
142
    /** This field should be treated as final. Subclasses are forbidden to change it. */
143
    protected /*final*/ JTextComponent component;
144
    private volatile AttributeSet attribs;
145
    private Lookup.Result<? extends FontColorSettings> fcsLookupResult;
146
    private final LookupListener fcsTracker = new LookupListener() {
147
        public void resultChanged(LookupEvent ev) {
148
            attribs = null;
149
            SwingUtilities.invokeLater(new Runnable() {
150
                public void run() {
151
                    //EMI: This is needed as maybe the DEFAULT_COLORING is changed, the font is different
152
                    // and while getMarkSize() is used in paint() and will make the artifacts bigger,
153
                    // the component itself will be the same size and it must be changed.
154
                    // See http://www.netbeans.org/issues/show_bug.cgi?id=153316
155
                    updatePreferredSize();
156
                    CodeFoldingSideBar.this.repaint();
157
                }
158
            });
159
        }
160
    };
161
    private final Listener listener = new Listener();
162
    
163
    private boolean enabled = false;
164
    
165
    protected List<Mark> visibleMarks = new ArrayList<Mark>();
166
    
167
    /**
168
     * Mouse moved point, possibly {@code null}. Set from mouse-moved, mouse-entered
169
     * handlers, so that painting will paint this fold in bold. -1, if mouse is not
170
     * in the sidebar region. The value is used to compute highlighted portions of the 
171
     * folding outline.
172
     */
173
    private int   mousePoint = -1;
174
    
175
    /**
176
     * if true, the {@link #mousePoint} has been already used to make a PaintInfo active.
177
     * The flag is tested by {@link #traverseForward} and {@link #traverseBackward} after children
178
     * of the current fold are processed and cleared if the {@link #mousePoint} falls to the fold area -
179
     * fields of PaintInfo are set accordingly.
180
     * It's also used to compute (current) mouseBoundary, so mouse movement does not trigger 
181
     * refreshes eagerly
182
     */
183
    private boolean mousePointConsumed;
184
    
185
    /**
186
     * Boundaries of the current area under the mouse. Can be eiher the span of the
187
     * current fold (or part of it), or the span not occupied by any fold. Serves as an optimization
188
     * for mouse handler, which does not trigger painting (refresh) unless mouse 
189
     * leaves this region.
190
     */
191
    private Rectangle   mouseBoundary;
192
    
193
    /**
194
     * Y-end of the nearest fold that ends above the {@link #mousePoint}. Undefined if mousePoint is null.
195
     * These two variables are initialized at each level of folds, and help to compute {@link #mouseBoundary} for
196
     * the case the mousePointer is OUTSIDE all children (or outside all folds). 
197
     */
198
    private int lowestAboveMouse = -1;
199
200
    /**
201
     * Y-begin of the nearest fold, which starts below the {@link #mousePoint}. Undefined if mousePoint is null
202
     */
203
    private int topmostBelowMouse = Integer.MAX_VALUE;
204
    
205
    private boolean alreadyPresent;
206
    
207
    /** Paint operations */
208
    public static final int PAINT_NOOP             = 0;
209
    /**
210
     * Normal opening +- marker
211
     */
212
    public static final int PAINT_MARK             = 1;
213
    
214
    /**
215
     * Vertical line - typically at the end of the screen
216
     */
217
    public static final int PAINT_LINE             = 2;
218
    
219
    /**
220
     * End angled line, without a sign
221
     */
222
    public static final int PAINT_END_MARK         = 3;
223
    
224
    /**
225
     * Single-line marker, both start and end
226
     */
227
    public static final int SINGLE_PAINT_MARK      = 4;
228
    
229
    /**
230
     * Marker value for {@link #mousePoint} indicating that mouse is outside the Component.
231
     */
232
    private static final int NO_MOUSE_POINT = -1;
233
    
234
    /**
235
     * Stroke used to draw inactive (regular) fold outlines.
236
     */
237
    private static Stroke LINE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 
238
            1f, new float[] { 1f, 1f }, 0f);
239
    
240
    /**
241
     * Stroke used to draw outlines for 'active' fold
242
     */
243
    private static final Stroke LINE_BOLD = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
244
    
245
    private final Preferences prefs;
246
    private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
247
        public void preferenceChange(PreferenceChangeEvent evt) {
248
            String key = evt == null ? null : evt.getKey();
249
            if (key == null || SimpleValueNames.CODE_FOLDING_ENABLE.equals(key)) {
250
                updateColors();
251
                
252
                boolean newEnabled = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, EditorPreferencesDefaults.defaultCodeFoldingEnable);
253
                if (enabled != newEnabled) {
254
                    enabled = newEnabled;
255
                    updatePreferredSize();
256
                }
257
            }
258
        }
259
    };
260
    
261
    private void checkRepaint(ViewHierarchyEvent vhe) {
262
        if (!vhe.isChangeY()) {
263
            // does not obscur sidebar graphics
264
            return;
265
        }
266
        
267
        SwingUtilities.invokeLater(new Runnable() {
268
            public void run() {
269
                updatePreferredSize();
270
                CodeFoldingSideBar.this.repaint();
271
            }
272
        });
273
    }
274
    
275
    /**
276
     * @deprecated Don't use this constructor, it does nothing!
277
     */
278
    public CodeFoldingSideBar() {
279
        component = null;
280
        prefs = null;
281
        throw new IllegalStateException("Do not use this constructor!"); //NOI18N
282
    }
283
284
    public CodeFoldingSideBar(final JTextComponent component){
285
        super();
286
        this.component = component;
287
288
        // prevent from display CF sidebar twice
289
        if (component.getClientProperty(PROP_SIDEBAR_MARK) == null) {
290
            component.putClientProperty("org.netbeans.editor.CodeFoldingSidebar", Boolean.TRUE);
291
        } else {
292
            alreadyPresent = true;
293
        }
294
295
        addMouseListener(listener);
296
        addMouseMotionListener(listener);
297
298
        final FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
299
        foldHierarchy.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, listener, foldHierarchy));
300
301
        final Document doc = getDocument();
302
        doc.addDocumentListener(WeakListeners.document(listener, doc));
303
        setOpaque(true);
304
        
305
        prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
306
        prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, prefsListener, prefs));
307
        prefsListener.preferenceChange(null);
308
        
309
        ViewHierarchy.get(component).addViewHierarchyListener(new ViewHierarchyListener() {
310
311
            @Override
312
            public void viewHierarchyChanged(ViewHierarchyEvent evt) {
313
                checkRepaint(evt);
314
            }
315
            
316
        });
317
    }
318
    
319
    private void updatePreferredSize() {
320
        // do not show at all, if there are no providers registered.
321
        if (enabled && !alreadyPresent) {
322
            setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
323
            setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
324
        }else{
325
            setPreferredSize(new Dimension(0,0));
326
            setMaximumSize(new Dimension(0,0));
327
        }
328
        revalidate();
329
    }
330
331
    private void updateColors() {
332
        Coloring c = getColoring();
333
        this.backColor = c.getBackColor();
334
        this.foreColor = c.getForeColor();
335
        this.font = c.getFont();
336
    }
337
338
    /**
339
     * This method should be treated as final. Subclasses are forbidden to override it.
340
     * @return The background color used for painting this component.
341
     * @deprecated Without any replacement.
342
     */
343
    protected Color getBackColor() {
344
        if (backColor == null) {
345
            updateColors();
346
        }
347
        return backColor;
348
    }
349
    
350
    /**
351
     * This method should be treated as final. Subclasses are forbidden to override it.
352
     * @return The foreground color used for painting this component.
353
     * @deprecated Without any replacement.
354
     */
355
    protected Color getForeColor() {
356
        if (foreColor == null) {
357
            updateColors();
358
        }
359
        return foreColor;
360
    }
361
    
362
    /**
363
     * This method should be treated as final. Subclasses are forbidden to override it.
364
     * @return The font used for painting this component.
365
     * @deprecated Without any replacement.
366
     */
367
    protected Font getColoringFont() {
368
        if (font == null) {
369
            updateColors();
370
        }
371
        return font;
372
    }
373
    
374
    // overriding due to issue #60304
375
    public @Override void update(Graphics g) {
376
    }
377
    
378
    protected void collectPaintInfos(
379
        View rootView, Fold fold, Map<Integer, PaintInfo> map, int level, int startIndex, int endIndex
380
    ) throws BadLocationException {
381
        //never called
382
    }
383
384
    /**
385
     * Adjust lowest/topmost boundaries from the Fold range y1-y2.
386
     * @param y1
387
     * @param y2
388
     * @param level 
389
     */
390
    private void setMouseBoundaries(int y1, int y2, int level) {
391
        if (!hasMousePoint() || mousePointConsumed) {
392
            return;
393
        }
394
        int y = mousePoint;
395
        if (y2 < y && lowestAboveMouse < y2) {
396
            LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
397
            lowestAboveMouse = y2;
398
        }
399
        if (y1 > y && topmostBelowMouse > y1) {
400
            LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
401
            topmostBelowMouse = y1;
402
        }
403
    }
404
    
405
    /*
406
     * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
407
     * then visually span multiple lines && be marked as collapsed.
408
     */
409
410
    protected List<? extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
411
        javax.swing.plaf.TextUI textUI = component.getUI();
412
        if (!(textUI instanceof BaseTextUI)) {
413
            return Collections.<PaintInfo>emptyList();
414
        }
415
        BaseTextUI baseTextUI = (BaseTextUI)textUI;
416
        BaseDocument bdoc = Utilities.getDocument(component);
417
        if (bdoc == null) {
418
            return Collections.<PaintInfo>emptyList();
419
        }
420
        mousePointConsumed = false;
421
        mouseBoundary = null;
422
        topmostBelowMouse = Integer.MAX_VALUE;
423
        lowestAboveMouse = -1;
424
        bdoc.readLock();
425
        try {
426
            int startPos = baseTextUI.getPosFromY(clip.y);
427
            int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
428
            
429
            if (startPos < 0 || endPos < 0) {
430
                // editor window is not properly sized yet; return no infos
431
                return Collections.<PaintInfo>emptyList();
432
            }
433
            
434
            // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
435
            // the document.
436
            int docLen = bdoc.getLength();
437
            if (startPos >= docLen || endPos > docLen) {
438
                return Collections.<PaintInfo>emptyList();
439
            }
440
            
441
            startPos = Utilities.getRowStart(bdoc, startPos);
442
            endPos = Utilities.getRowEnd(bdoc, endPos);
443
            
444
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
445
            hierarchy.lock();
446
            try {
447
                View rootView = Utilities.getDocumentView(component);
448
                if (rootView != null) {
449
                    Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
450
                    @SuppressWarnings("unchecked")
451
                    List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
452
                    int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
453
454
                    /*
455
                     * Note:
456
                     * 
457
                     * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
458
                     * This is because several folds may occupy the same line, while only one + sign is displayed,
459
                     * and affect the last fold in the row.
460
                     */
461
                    NavigableMap<Integer, PaintInfo> map = new TreeMap<Integer, PaintInfo>();
462
                    // search backwards
463
                    for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
464
                        Fold fold = foldList.get(i);
465
                        if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
466
                            break;
467
                        }
468
                    }
469
470
                    // search forward
471
                    for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
472
                        Fold fold = foldList.get(i);
473
                        if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
474
                            break;
475
                        }
476
                    }
477
                    
478
                    if (map.isEmpty() && foldList.size() > 0) {
479
                        assert foldList.size() == 1;
480
                        PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
481
                        mouseBoundary = new Rectangle(0, 0, 0, clip.height);
482
                        LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
483
                        if (hasMousePoint()) {
484
                            pi.markActive(true, true, true);
485
                        }
486
                        return Collections.singletonList(pi);
487
                    } else {
488
                        if (mouseBoundary == null) {
489
                            mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
490
                            LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
491
                        }
492
                        return new ArrayList<PaintInfo>(map.values());
493
                    }
494
                } else {
495
                    return Collections.<PaintInfo>emptyList();
496
                }
497
            } finally {
498
                hierarchy.unlock();
499
            }
500
        } finally {
501
            bdoc.readUnlock();
502
        }
503
    }
504
    
505
    /**
506
     * Adds a paint info to the map. If a paintinfo already exists, it merges
507
     * the structures, so the painting process can just follow the instructions.
508
     * 
509
     * @param infos
510
     * @param yOffset
511
     * @param nextInfo 
512
     */
513
    private void addPaintInfo(Map<Integer, PaintInfo> infos, int yOffset, PaintInfo nextInfo) {
514
        PaintInfo prevInfo = infos.get(yOffset);
515
        nextInfo.mergeWith(prevInfo);
516
        infos.put(yOffset, nextInfo);
517
    }
518
519
    private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level,  NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
520
//        System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
521
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
522
//                + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
523
//                + ", level=" + level);
524
        
525
        if (f.getStartOffset() > upperBoundary) {
526
            return false;
527
        }
528
529
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
530
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
531
        int y1 = btui.getYFromPos(lineStartOffset1);
532
        int h = btui.getEditorUI().getLineHeight();
533
        int y2 = btui.getYFromPos(lineStartOffset2);
534
         
535
        // the 'active' flags can be set only after children are processed; highlights
536
        // correspond to the innermost expanded child.
537
        boolean activeMark = false;
538
        boolean activeIn = false;
539
        boolean activeOut = false;
540
        PaintInfo spi;
541
        boolean activated;
542
        
543
        if (y1 == y2) {
544
            // whole fold is on a single line
545
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
546
            if (activated = isActivated(y1, y1 + h)) {
547
                activeMark = true;
548
            }
549
            addPaintInfo(infos, y1, spi);
550
        } else {
551
            // fold spans multiple lines
552
            spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
553
            if (activated = isActivated(y1, y2 + h / 2)) {
554
                activeMark = true;
555
                activeOut = true;
556
            }
557
            addPaintInfo(infos, y1, spi);
558
        }
559
560
        setMouseBoundaries(y1, y2 + h / 2, level);
561
562
        // Handle end mark after possible inner folds were processed because
563
        // otherwise if there would be two nested folds both ending at the same line
564
        // then the end mark for outer one would be replaced by an end mark for inner one
565
        // (same key in infos map) and the painting code would continue to paint line marking a fold
566
        // until next fold is reached (or end of doc).
567
        PaintInfo epi = null;
568
        if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
569
            epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
570
            addPaintInfo(infos, y2, epi);
571
        }
572
573
        // save the topmost/lowest information, reset for child processing
574
        int topmost = topmostBelowMouse;
575
        int lowest = lowestAboveMouse;
576
        topmostBelowMouse = y2 + h / 2;
577
        lowestAboveMouse = y1;
578
579
        try {
580
            if (!f.isCollapsed()) {
581
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
582
                @SuppressWarnings("unchecked")
583
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
584
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
585
586
                // search backwards
587
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
588
                    Fold fold = foldList.get(i);
589
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
590
                        break;
591
                    }
592
                }
593
594
                // search forward
595
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
596
                    Fold fold = foldList.get(i);
597
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
598
                        return false;
599
                    }
600
                }
601
            }
602
            if (!mousePointConsumed && activated) {
603
                mousePointConsumed = true;
604
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
605
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
606
                spi.markActive(activeMark, activeIn, activeOut);
607
                if (epi != null) {
608
                    epi.markActive(true, true, false);
609
                }
610
                markDeepChildrenActive(infos, y1, y2, level);
611
            }
612
        } finally {
613
            topmostBelowMouse = topmost;
614
            lowestAboveMouse = lowest;
615
        }
616
        return true;
617
    }
618
     
619
    /**
620
     * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
621
     * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
622
     * as active.
623
     * The method returns Y start coordinate of the 1st child found.
624
     * 
625
     * @param infos fold infos collected so far
626
     * @param yFrom upper Y-coordinate of the parent fold
627
     * @param yTo lower Y-coordinate of the parent fold
628
     * @param level level of the parent fold
629
     * @return Y-coordinate of the 1st child.
630
     */
631
    private int markDeepChildrenActive(NavigableMap<Integer, PaintInfo> infos, int yFrom, int yTo, int level) {
632
        int result = Integer.MAX_VALUE;
633
        Map<Integer, PaintInfo> m = infos.subMap(yFrom, yTo);
634
        for (Map.Entry<Integer, PaintInfo> me : m.entrySet()) {
635
            PaintInfo pi = me.getValue();
636
            int y = pi.getPaintY();
637
            if (y > yFrom && y < yTo) {
638
                if (LOG.isLoggable(Level.FINEST)) {
639
                    LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
640
                }
641
                pi.markActive(false, true, true);
642
                if (y < result) {
643
                    y = result;
644
                }
645
            }
646
        }
647
        return result;
648
    }
649
    
650
    /**
651
     * Returns stroke appropriate for painting (in)active outlines
652
     * @param s the default stroke
653
     * @param active true for active outlines
654
     * @return value of 's' or a Stroke which should be used to paint the outline.
655
     */
656
    private static Stroke getStroke(Stroke s, boolean active) {
657
        if (active) {
658
            return LINE_BOLD;
659
        } else {
660
            return s;
661
        }
662
    }
663
    
664
    private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
665
//        System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
666
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
667
//                + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
668
//                + ", level=" + level);
669
670
        if (f.getEndOffset() < lowerBoundary) {
671
            return false;
672
        }
673
674
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
675
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
676
        int h = btui.getEditorUI().getLineHeight();
677
678
        boolean activeMark = false;
679
        boolean activeIn = false;
680
        boolean activeOut = false;
681
        PaintInfo spi = null;
682
        PaintInfo epi = null;
683
        boolean activated = false;
684
        int y1 = 0;
685
        int y2 = 0;
686
        
687
        if (lineStartOffset1 == lineStartOffset2) {
688
            // whole fold is on a single line
689
            y2 = y1 = btui.getYFromPos(lineStartOffset1);
690
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
691
            if (activated = isActivated(y1, y1 + h)) {
692
                activeMark = true;
693
            }
694
            addPaintInfo(infos, y1, spi);
695
        } else {
696
            y2 = btui.getYFromPos(lineStartOffset2);
697
            // fold spans multiple lines
698
            y1 = btui.getYFromPos(lineStartOffset1);
699
            activated = isActivated(y1, y2 + h / 2);
700
            if (f.getStartOffset() >= upperBoundary) {
701
                spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
702
                if (activated) {
703
                    activeMark = true;
704
                    activeOut = true;
705
                }
706
                addPaintInfo(infos, y1, spi);
707
            }
708
709
            if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
710
                activated |= isActivated(y1, y2 + h / 2);
711
                epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
712
                addPaintInfo(infos, y2, epi);
713
            }
714
        }
715
        
716
        setMouseBoundaries(y1, y2 + h / 2, level);
717
718
        // save the topmost/lowest information, reset for child processing
719
        int topmost = topmostBelowMouse;
720
        int lowest = lowestAboveMouse;
721
        topmostBelowMouse = y2 + h /2;
722
        lowestAboveMouse = y1;
723
724
        try {
725
            if (!f.isCollapsed()) {
726
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
727
                @SuppressWarnings("unchecked")
728
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
729
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
730
731
                // search backwards
732
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
733
                    Fold fold = foldList.get(i);
734
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
735
                        return false;
736
                    }
737
                }
738
739
                // search forward
740
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
741
                    Fold fold = foldList.get(i);
742
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
743
                        break;
744
                    }
745
                }
746
            }
747
            if (!mousePointConsumed && activated) {
748
                mousePointConsumed = true;
749
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
750
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
751
                if (spi != null) {
752
                    spi.markActive(activeMark, activeIn, activeOut);
753
                }
754
                if (epi != null) {
755
                    epi.markActive(true, true, false);
756
                }
757
                int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
758
                if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
759
                    // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
760
                    // 1st child marker.
761
                    epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
762
                    epi.markActive(true, true, false);
763
                    addPaintInfo(infos, y1, epi);
764
                }
765
            }
766
        } finally {
767
            topmostBelowMouse = topmost;
768
            lowestAboveMouse = lowest;
769
        }
770
        return true;
771
    }
772
    
773
    private Rectangle makeMouseBoundary(int y1, int y2) {
774
        if (!hasMousePoint()) {
775
            return null;
776
        }
777
        if (topmostBelowMouse < Integer.MAX_VALUE) {
778
            y2 = topmostBelowMouse;
779
        }
780
        if (lowestAboveMouse  > -1) {
781
            y1 = lowestAboveMouse;
782
        }
783
        return new Rectangle(0, y1, 0, y2 - y1);
784
    }
785
    
786
    protected EditorUI getEditorUI(){
787
        return Utilities.getEditorUI(component);
788
    }
789
    
790
    protected Document getDocument(){
791
        return component.getDocument();
792
    }
793
794
795
    private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
796
        Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
797
        Fold prevFold = fold;
798
        while (fold != null && fold.getStartOffset()<rowEnd){
799
            Fold nextFold = FoldUtilities.findNearestFold(hierarchy, (fold.isCollapsed()) ? fold.getEndOffset() : fold.getStartOffset()+1);
800
            if (nextFold == fold) return fold;
801
            if (nextFold!=null && nextFold.getStartOffset() < rowEnd){
802
                prevFold = shift ? fold : nextFold;
803
                fold = nextFold;
804
            }else{
805
                return prevFold;
806
            }
807
        }
808
        return prevFold;
809
    }
810
    
811
    protected void performAction(Mark mark) {
812
        performAction(mark, false);
813
    }
814
    
815
    private void performActionAt(Mark mark, int mouseY) throws BadLocationException {
816
        if (mark != null) {
817
            return;
818
        }
819
        BaseDocument bdoc = Utilities.getDocument(component);
820
        BaseTextUI textUI = (BaseTextUI)component.getUI();
821
822
        View rootView = Utilities.getDocumentView(component);
823
        if (rootView == null) return;
824
        
825
        bdoc.readLock();
826
        try {
827
            int yOffset = textUI.getPosFromY(mouseY);
828
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
829
            hierarchy.lock();
830
            try {
831
                Fold f = FoldUtilities.findOffsetFold(hierarchy, yOffset);
832
                if (f == null) {
833
                    return;
834
                }
835
                if (f.isCollapsed()) {
836
                    LOG.log(Level.WARNING, "Clicked on a collapsed fold {0} at {1}", new Object[] { f, mouseY });
837
                    return;
838
                }
839
                int startOffset = f.getStartOffset();
840
                int endOffset = f.getEndOffset();
841
                
842
                int startY = textUI.getYFromPos(startOffset);
843
                int nextLineOffset = Utilities.getRowStart(bdoc, startOffset, 1);
844
                int nextY = textUI.getYFromPos(nextLineOffset);
845
846
                if (mouseY >= startY && mouseY <= nextY) {
847
                    LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
848
                            new Object[] { mouseY, startY, nextY });
849
                    return;
850
                }
851
852
                startY = textUI.getYFromPos(endOffset);
853
                nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
854
                nextY = textUI.getYFromPos(nextLineOffset);
855
856
                if (mouseY >= startY && mouseY <= nextY) {
857
                    // the mouse can be positioned above the marker (the fold found above), or
858
                    // below it; in that case, the immediate enclosing fold should be used - should be the fold
859
                    // that corresponds to the nextLineOffset, if any
860
                    int h2 = (startY + nextY) / 2;
861
                    if (mouseY >= h2) {
862
                        Fold f2 = f;
863
                        
864
                        f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
865
                        if (f == null) {
866
                            // fold does not exist for the position below end-of-fold indicator
867
                            return;
868
                        }
869
                    }
870
                    
871
                }
872
                
873
                LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
874
                hierarchy.collapse(f);
875
            } finally {
876
                hierarchy.unlock();
877
            }
878
        } finally {
879
            bdoc.readUnlock();
880
        }        
881
    }
882
    
883
    private void performAction(final Mark mark, final boolean shiftFold) {
884
        Document doc = component.getDocument();
885
        doc.render(new Runnable() {
886
            @Override
887
            public void run() {
888
                ViewHierarchy vh = ViewHierarchy.get(component);
889
                LockedViewHierarchy lockedVH = vh.lock();
890
                try {
891
                    int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
892
                    if (pViewIndex >= 0) {
893
                        ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
894
                        int pViewStartOffset = pViewDesc.getStartOffset();
895
                        int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
896
                        // Find corresponding fold
897
                        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
898
                        foldHierarchy.lock();
899
                        try {
900
                            int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
901
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
902
                            Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
903
                            if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
904
                                foldHierarchy.toggle(clickedFold);
905
                            }
906
                        } catch (BadLocationException ble) {
907
                            LOG.log(Level.WARNING, null, ble);
908
                        } finally {
909
                            foldHierarchy.unlock();
910
                        }
911
                    }
912
                } finally {
913
                    lockedVH.unlock();
914
                }
915
            }
916
        });
917
    }
918
    
919
    protected int getMarkSize(Graphics g){
920
        if (g != null){
921
            FontMetrics fm = g.getFontMetrics(getColoring().getFont());
922
            if (fm != null){
923
                int ret = fm.getAscent() - fm.getDescent();
924
                return ret - ret%2;
925
            }
926
        }
927
        return -1;
928
    }
929
    
930
    private boolean hasMousePoint() {
931
        return mousePoint >= 0;
932
    }
933
    
934
    private boolean isActivated(int y1, int y2) {
935
        return hasMousePoint() && 
936
               (mousePoint >= y1 && mousePoint < y2);
937
    }
938
    
939
    private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
940
        Stroke origStroke = g2d.getStroke();
941
        g2d.setStroke(getStroke(origStroke, active));
942
        g2d.drawLine(x1, y1, x2, y2);
943
        g2d.setStroke(origStroke);
944
    }
945
    
946
    protected @Override void paintComponent(Graphics g) {
947
        if (!enabled) {
948
            return;
949
        }
950
        
951
        Rectangle clip = getVisibleRect();//g.getClipBounds();
952
        visibleMarks.clear();
953
        
954
        Coloring coloring = getColoring();
955
        g.setColor(coloring.getBackColor());
956
        g.fillRect(clip.x, clip.y, clip.width, clip.height);
957
        g.setColor(coloring.getForeColor());
958
959
        AbstractDocument adoc = (AbstractDocument)component.getDocument();
960
        adoc.readLock();
961
        try {
962
            List<? extends PaintInfo> ps = getPaintInfo(clip);
963
            Font defFont = coloring.getFont();
964
            int markSize = getMarkSize(g);
965
            int halfMarkSize = markSize / 2;
966
            int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
967
            int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
968
            int lineX = markX + halfMarkSize; // x position of the centre of mark
969
970
            LOG.fine("CFSBar: PAINT START ------\n");
971
            int descent = g.getFontMetrics(defFont).getDescent();
972
            PaintInfo previousInfo = null;
973
            Graphics2D g2d = (Graphics2D)g;
974
            LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
975
976
            for(PaintInfo paintInfo : ps) {
977
                boolean isFolded = paintInfo.isCollapsed();
978
                int y = paintInfo.getPaintY();
979
                int height = paintInfo.getPaintHeight();
980
                int markY = y + descent; // y position of mark rectangle
981
                int paintOperation = paintInfo.getPaintOperation();
982
983
                if (previousInfo == null) {
984
                    if (paintInfo.hasLineIn()) {
985
                        if (LOG.isLoggable(Level.FINE)) {
986
                            LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
987
                        }
988
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
989
                    }
990
                } else {
991
                    if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
992
                        // Draw middle vertical line
993
                        int prevY = previousInfo.getPaintY();
994
                        if (LOG.isLoggable(Level.FINE)) {
995
                            LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
996
                        }
997
                        drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
998
                    }
999
                }
1000
1001
                if (paintInfo.hasSign()) {
1002
                    g.drawRect(markX, markY, markSize, markSize);
1003
                    g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
1004
                    String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
1005
                    if (isFolded) {
1006
                        if (LOG.isLoggable(Level.FINE)) {
1007
                            LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1008
                        }
1009
                        g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
1010
                    }
1011
                    if (paintOperation != SINGLE_PAINT_MARK) {
1012
                        if (LOG.isLoggable(Level.FINE)) {
1013
                            LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1014
                        }
1015
                    }
1016
                    if (paintInfo.hasLineIn()) { //[PENDING]
1017
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
1018
                    }
1019
                    if (paintInfo.hasLineOut()) {
1020
                        // This is an error in case there's a next paint info at the same y which is an end mark
1021
                        // for this mark (it must be cleared explicitly).
1022
                        drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
1023
                    }
1024
                    visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
1025
1026
                } else if (paintOperation == PAINT_LINE) {
1027
                    if (LOG.isLoggable(Level.FINE)) {
1028
                        LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1029
                    }
1030
                    // FIXME !!
1031
                    drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
1032
                } else if (paintOperation == PAINT_END_MARK) {
1033
                    if (LOG.isLoggable(Level.FINE)) {
1034
                        LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1035
                    }
1036
                    if (previousInfo == null || y != previousInfo.getPaintY()) {
1037
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
1038
                        drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
1039
                        if (paintInfo.getInnerLevel() > 0) {//[PENDING]
1040
                            if (LOG.isLoggable(Level.FINE)) {
1041
                                LOG.fine("  PAINT middle-line\n"); // NOI18N
1042
                            }
1043
                            drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
1044
                        }
1045
                    }
1046
                }
1047
1048
                previousInfo = paintInfo;
1049
            }
1050
1051
            if (previousInfo != null &&
1052
                (previousInfo.getInnerLevel() > 0 ||
1053
                 (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
1054
            ) {
1055
                drawFoldLine(g2d, previousInfo.lineOutActive, 
1056
                        lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
1057
            }
1058
1059
        } catch (BadLocationException ble) {
1060
            LOG.log(Level.WARNING, null, ble);
1061
        } finally {
1062
            LOG.fine("CFSBar: PAINT END ------\n\n");
1063
            adoc.readUnlock();
1064
        }
1065
    }
1066
    
1067
    private static Object [] getFoldList(Fold parentFold, int start, int end) {
1068
        List<Fold> ret = new ArrayList<Fold>();
1069
1070
        int index = FoldUtilities.findFoldEndIndex(parentFold, start);
1071
        int foldCount = parentFold.getFoldCount();
1072
        int idxOfFirstFoldStartingInside = -1;
1073
        while (index < foldCount) {
1074
            Fold f = parentFold.getFold(index);
1075
            if (f.getStartOffset() <= end) {
1076
                ret.add(f);
1077
            } else {
1078
                break; // no more relevant folds
1079
            }
1080
            if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
1081
                idxOfFirstFoldStartingInside = ret.size() - 1;
1082
            }
1083
            index++;
1084
        }
1085
1086
        return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
1087
    }
1088
1089
    /**
1090
     * This class should be never used by other code; will be made private
1091
     */
1092
    public class PaintInfo {
1093
        
1094
        int paintOperation;
1095
        /**
1096
         * level of the 1st marker on the line
1097
         */
1098
        int innerLevel;
1099
        
1100
        /**
1101
         * Y-coordinate of the cell
1102
         */
1103
        int paintY;
1104
        
1105
        /**
1106
         * Height of the paint cell
1107
         */
1108
        int paintHeight;
1109
        
1110
        /**
1111
         * State of the marker (+/-)
1112
         */
1113
        boolean isCollapsed;
1114
        
1115
        /**
1116
         * all markers on the line are collapsed
1117
         */
1118
        boolean allCollapsed;
1119
        int startOffset;
1120
        int endOffset;
1121
        /**
1122
         * nesting level of the last marker on the line
1123
         */
1124
        int outgoingLevel;
1125
        
1126
        /**
1127
         * Force incoming line (from above) to be present
1128
         */
1129
        boolean lineIn;
1130
        
1131
        /**
1132
         * Force outgoing line (down from marker) to be present
1133
         */
1134
        boolean lineOut;
1135
        
1136
        /**
1137
         * The 'incoming' (upper) line should be painted as active
1138
         */
1139
        boolean lineInActive;
1140
        
1141
        /**
1142
         * The 'outgoing' (down) line should be painted as active
1143
         */
1144
        boolean lineOutActive;
1145
        
1146
        /**
1147
         * The sign/marker itself should be painted as active
1148
         */
1149
        boolean signActive;
1150
        
1151
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
1152
            this.paintOperation = paintOperation;
1153
            this.innerLevel = this.outgoingLevel = innerLevel;
1154
            this.paintY = paintY;
1155
            this.paintHeight = paintHeight;
1156
            this.isCollapsed = this.allCollapsed = isCollapsed;
1157
            this.startOffset = startOffset;
1158
            this.endOffset = endOffset;
1159
1160
            switch (paintOperation) {
1161
                case PAINT_MARK:
1162
                    lineIn = false;
1163
                    lineOut = true;
1164
                    outgoingLevel++;
1165
                    break;
1166
                case SINGLE_PAINT_MARK:
1167
                    lineIn = false;
1168
                    lineOut = false;
1169
                    break;
1170
                case PAINT_END_MARK:
1171
                    lineIn = true;
1172
                    lineOut = false;
1173
                    isCollapsed = true;
1174
                    allCollapsed = true;
1175
                    break;
1176
                case PAINT_LINE:
1177
                    lineIn = lineOut = true;
1178
                    break;
1179
            }
1180
        }
1181
        
1182
        /**
1183
         * Sets active flags on inidivual parts of the mark
1184
         * @param mark
1185
         * @param lineIn
1186
         * @param lineOut S
1187
         */
1188
        void markActive(boolean mark, boolean lineIn, boolean lineOut) {
1189
            this.signActive |= mark;
1190
            this.lineInActive |= lineIn;
1191
            this.lineOutActive |= lineOut;
1192
        }
1193
        
1194
        boolean hasLineIn() {
1195
            return lineIn || innerLevel > 0;
1196
        }
1197
        
1198
        boolean hasLineOut() {
1199
            return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
1200
        }
1201
1202
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
1203
            this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
1204
        }
1205
        
1206
        public int getPaintOperation(){
1207
            return paintOperation;
1208
        }
1209
        
1210
        public int getInnerLevel(){
1211
            return innerLevel;
1212
        }
1213
        
1214
        public int getPaintY(){
1215
            return paintY;
1216
        }
1217
        
1218
        public int getPaintHeight(){
1219
            return paintHeight;
1220
        }
1221
        
1222
        public boolean isCollapsed(){
1223
            return isCollapsed;
1224
        }
1225
        
1226
         boolean isAllCollapsed() {
1227
            return allCollapsed;
1228
        }
1229
        
1230
        public void setPaintOperation(int paintOperation){
1231
            this.paintOperation = paintOperation;
1232
        }
1233
        
1234
        public void setInnerLevel(int innerLevel){
1235
            this.innerLevel = innerLevel;
1236
        }
1237
        
1238
        public @Override String toString(){
1239
            StringBuffer sb = new StringBuffer("");
1240
            if (paintOperation == PAINT_MARK){
1241
                sb.append("PAINT_MARK"); // NOI18N
1242
            }else if (paintOperation == PAINT_LINE){
1243
                sb.append("PAINT_LINE"); // NOI18N
1244
            }else if (paintOperation == PAINT_END_MARK) {
1245
                sb.append("PAINT_END_MARK"); // NOI18N
1246
            }else if (paintOperation == SINGLE_PAINT_MARK) {
1247
                sb.append("SINGLE_PAINT_MARK");
1248
            }
1249
            sb.append(",L:").append(innerLevel).append("/").append(outgoingLevel); // NOI18N
1250
            sb.append(',').append(isCollapsed ? "C" : "E"); // NOI18N
1251
            sb.append(", start=").append(startOffset).append(", end=").append(endOffset);
1252
            sb.append(", lineIn=").append(lineIn).append(", lineOut=").append(lineOut);
1253
            return sb.toString();
1254
        }
1255
        
1256
        boolean hasSign() {
1257
            return paintOperation == PAINT_MARK || paintOperation == SINGLE_PAINT_MARK;
1258
        }
1259
        
1260
        
1261
        void mergeWith(PaintInfo prevInfo) {
1262
            if (prevInfo == null) {
1263
                return;
1264
            }
1265
1266
            int operation = this.paintOperation;
1267
            boolean lineIn = prevInfo.lineIn;
1268
            boolean lineOut = prevInfo.lineOut;
1269
            
1270
            LOG.log(Level.FINE, "Merging {0} with {1}: ", new Object[] { this, prevInfo });
1271
            if (prevInfo.getPaintOperation() == PAINT_END_MARK) {
1272
                // merge with start|single -> start mark + line-in
1273
                lineIn = true;
1274
            } else {
1275
                operation = PAINT_MARK;
1276
            }
1277
1278
            int level1 = Math.min(prevInfo.innerLevel, innerLevel);
1279
            int level2 = prevInfo.outgoingLevel;
1280
1281
            if (getPaintOperation() == PAINT_END_MARK 
1282
                && innerLevel == prevInfo.outgoingLevel) {
1283
                // if merging end marker at the last level, update to the new outgoing level
1284
                level2 = outgoingLevel;
1285
            } else if (!isCollapsed) {
1286
                level2 = Math.max(prevInfo.outgoingLevel, outgoingLevel);
1287
            }
1288
1289
            if (prevInfo.getInnerLevel() < getInnerLevel()) {
1290
                int paintFrom = Math.min(prevInfo.paintY, paintY);
1291
                int paintTo = Math.max(prevInfo.paintY + prevInfo.paintHeight, paintY + paintHeight);
1292
                // at least one collapsed -> paint plus sign
1293
                boolean collapsed = prevInfo.isCollapsed() || isCollapsed();
1294
                int offsetFrom = Math.min(prevInfo.startOffset, startOffset);
1295
                int offsetTo = Math.max(prevInfo.endOffset, endOffset);
1296
                
1297
                this.paintY = paintFrom;
1298
                this.paintHeight = paintTo - paintFrom;
1299
                this.isCollapsed = collapsed;
1300
                this.startOffset = offsetFrom;
1301
                this.endOffset = offsetTo;
1302
            }
1303
            this.paintOperation = operation;
1304
            this.allCollapsed = prevInfo.allCollapsed && allCollapsed;
1305
            this.innerLevel = level1;
1306
            this.outgoingLevel = level2;
1307
            this.lineIn |= lineIn;
1308
            this.lineOut |= lineOut;
1309
            
1310
            this.signActive |= prevInfo.signActive;
1311
            this.lineInActive |= prevInfo.lineInActive;
1312
            this.lineOutActive |= prevInfo.lineOutActive;
1313
            
1314
            LOG.log(Level.FINE, "Merged result: {0}", this);
1315
        }
1316
    }
1317
    
1318
    /** Keeps info of visible folding mark */
1319
    public class Mark{
1320
        public int x;
1321
        public int y;
1322
        public int size;
1323
        public boolean isFolded;
1324
        
1325
        public Mark(int x, int y, int size, boolean isFolded){
1326
            this.x = x;
1327
            this.y = y;
1328
            this.size = size;
1329
            this.isFolded = isFolded;
1330
        }
1331
    }
1332
    
1333
    private final class Listener extends MouseAdapter implements FoldHierarchyListener, DocumentListener, Runnable {
1334
    
1335
        public Listener(){
1336
        }
1337
1338
        // --------------------------------------------------------------------
1339
        // FoldHierarchyListener implementation
1340
        // --------------------------------------------------------------------
1341
1342
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
1343
            refresh();
1344
        }
1345
1346
        // --------------------------------------------------------------------
1347
        // DocumentListener implementation
1348
        // --------------------------------------------------------------------
1349
1350
        public void insertUpdate(DocumentEvent evt) {
1351
            if (!(evt instanceof BaseDocumentEvent)) return;
1352
1353
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1354
            if (bevt.getLFCount() > 0) { // one or more lines inserted
1355
                refresh();
1356
            }
1357
        }
1358
1359
        public void removeUpdate(DocumentEvent evt) {
1360
            if (!(evt instanceof BaseDocumentEvent)) return;
1361
1362
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1363
            if (bevt.getLFCount() > 0) { // one or more lines removed
1364
                refresh();
1365
            }
1366
        }
1367
1368
        public void changedUpdate(DocumentEvent evt) {
1369
        }
1370
1371
        // --------------------------------------------------------------------
1372
        // MouseListener implementation
1373
        // --------------------------------------------------------------------
1374
1375
        @Override
1376
        public void mousePressed (MouseEvent e) {
1377
            Mark mark = getClickedMark(e);
1378
            if (mark!=null){
1379
                e.consume();
1380
                performAction(mark, (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
1381
            }
1382
        }
1383
1384
        @Override
1385
        public void mouseClicked(MouseEvent e) {
1386
            // #102288 - missing event consuming caused quick doubleclicks to break
1387
            // fold expanding/collapsing and move caret to the particular line
1388
            if (e.getClickCount() > 1) {
1389
                LOG.log(Level.FINEST, "Mouse {0}click at {1}", new Object[] { e.getClickCount(), e.getY()});
1390
                Mark mark = getClickedMark(e);
1391
                try {
1392
                    performActionAt(mark, e.getY());
1393
                } catch (BadLocationException ex) {
1394
                    LOG.log(Level.WARNING, "Error during fold expansion using sideline", ex);
1395
                }
1396
            } else {
1397
                e.consume();
1398
            }
1399
        }
1400
1401
        private void refreshIfMouseOutside(Point pt) {
1402
            mousePoint = (int)pt.getY();
1403
            if (LOG.isLoggable(Level.FINEST)) {
1404
                if (mouseBoundary == null) {
1405
                    LOG.log(Level.FINEST, "Mouse boundary not set, refreshing: {0}", mousePoint);
1406
                } else {
1407
                    LOG.log(Level.FINEST, "Mouse {0} inside known mouse boundary: {1}-{2}", 
1408
                            new Object[] { mousePoint, mouseBoundary.y, mouseBoundary.getMaxY() });
1409
                }
1410
            }
1411
            if (mouseBoundary == null || mousePoint < mouseBoundary.y || mousePoint > mouseBoundary.getMaxY()) {
1412
                refresh();
1413
            }
1414
        }
1415
        
1416
        @Override
1417
        public void mouseMoved(MouseEvent e) {
1418
            refreshIfMouseOutside(e.getPoint());
1419
        }
1420
        
1421
        public void mouseEntered(MouseEvent e) {
1422
            refreshIfMouseOutside(e.getPoint());
1423
        }
1424
        
1425
        public void mouseExited(MouseEvent e) {
1426
            mousePoint = NO_MOUSE_POINT;
1427
            refresh();
1428
        }
1429
        
1430
1431
        // --------------------------------------------------------------------
1432
        // private implementation
1433
        // --------------------------------------------------------------------
1434
1435
        private Mark getClickedMark(MouseEvent e){
1436
            if (e == null || !SwingUtilities.isLeftMouseButton(e)) {
1437
                return null;
1438
            }
1439
            
1440
            int x = e.getX();
1441
            int y = e.getY();
1442
            for (Mark mark : visibleMarks) {
1443
                if (x >= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)) {
1444
                    return mark;
1445
                }
1446
            }
1447
            return null;
1448
        }
1449
1450
        private void refresh() {
1451
            SwingUtilities.invokeLater(this);
1452
        }
1453
1454
        @Override
1455
        public void run() {
1456
            if (getPreferredSize().width == 0 && enabled) {
1457
                updatePreferredSize();
1458
            }
1459
            repaint();
1460
        }
1461
    } // End of Listener class
1462
    
1463
    @Override
1464
    public AccessibleContext getAccessibleContext() {
1465
        if (accessibleContext == null) {
1466
            accessibleContext = new AccessibleJComponent() {
1467
                public @Override AccessibleRole getAccessibleRole() {
1468
                    return AccessibleRole.PANEL;
1469
                }
1470
            };
1471
            accessibleContext.setAccessibleName(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSN_CodeFoldingSideBar")); //NOI18N
1472
        accessibleContext.setAccessibleDescription(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSD_CodeFoldingSideBar")); //NOI18N
1473
        }
1474
        return accessibleContext;
1475
    }
1476
1477
    private Coloring getColoring() {
1478
        if (attribs == null) {
1479
            if (fcsLookupResult == null) {
1480
                fcsLookupResult = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component))
1481
                        .lookupResult(FontColorSettings.class);
1482
                fcsLookupResult.addLookupListener(WeakListeners.create(LookupListener.class, fcsTracker, fcsLookupResult));
1483
            }
1484
            
1485
            FontColorSettings fcs = fcsLookupResult.allInstances().iterator().next();
1486
            AttributeSet attr = fcs.getFontColors(FontColorNames.CODE_FOLDING_BAR_COLORING);
1487
            if (attr == null) {
1488
                attr = fcs.getFontColors(FontColorNames.DEFAULT_COLORING);
1489
            } else {
1490
                attr = AttributesUtilities.createComposite(attr, fcs.getFontColors(FontColorNames.DEFAULT_COLORING));
1491
            }
1492
            attribs = attr;
1493
        }        
1494
        return Coloring.fromAttributeSet(attribs);
1495
    }
1496
1497
    /**
1498
     * Factory for the sidebars. Use {@link FoldUtilities#getFoldingSidebarFactory()} to
1499
     * obtain an instance.
1500
     */
1501
    public static class Factory implements SideBarFactory {
1502
        @Override
1503
        public JComponent createSideBar(JTextComponent target) {
1504
            return new CodeFoldingSideBar(target);
1505
        }
1506
    }
1507
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/FoldToolTip.java (+152 lines)
Line 0 Link Here
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-2006 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
45
package org.netbeans.modules.editor.fold.ui;
46
47
import java.awt.BorderLayout;
48
import java.awt.Color;
49
import java.awt.Dimension;
50
import java.lang.reflect.InvocationTargetException;
51
import java.lang.reflect.Method;
52
import javax.swing.JComponent;
53
import javax.swing.JEditorPane;
54
import javax.swing.JPanel;
55
import javax.swing.border.LineBorder;
56
import javax.swing.event.AncestorEvent;
57
import javax.swing.event.AncestorListener;
58
import javax.swing.text.JTextComponent;
59
import org.netbeans.modules.editor.lib2.view.DocumentView;
60
import org.openide.util.Exceptions;
61
import org.openide.util.Lookup;
62
63
/**
64
 * Component that displays a collapsed fold preview.
65
 *
66
 * @author Miloslav Metelka
67
 */
68
final class FoldToolTip extends JPanel {
69
    private int editorPaneWidth;
70
71
    public FoldToolTip(JEditorPane editorPane, final JEditorPane foldPreviewPane, Color borderColor) {
72
        setLayout(new BorderLayout());
73
        add(foldPreviewPane, BorderLayout.CENTER);
74
        putClientProperty("tooltip-type", "fold-preview"); // Checked in NbToolTip
75
76
        addGlyphGutter(foldPreviewPane);
77
        
78
        addAncestorListener(new AncestorListener() {
79
            @Override
80
            public void ancestorAdded(AncestorEvent event) {
81
            }
82
83
            @Override
84
            public void ancestorRemoved(AncestorEvent event) {
85
                // Deactivate the view hierarchy immediately for foldPreviewPane
86
                final DocumentView docView = DocumentView.get(foldPreviewPane);
87
                if (docView != null) {
88
                    docView.runTransaction(new Runnable() {
89
                        @Override
90
                        public void run() {
91
                            docView.updateLengthyAtomicEdit(+100); // Effectively disable any VH updates
92
                        }
93
                    });
94
                }
95
                // Remove the listener
96
                FoldToolTip.this.removeAncestorListener(this);
97
            }
98
99
            @Override
100
            public void ancestorMoved(AncestorEvent event) {
101
            }
102
        });
103
104
        editorPaneWidth = editorPane.getSize().width;
105
106
        setBorder(new LineBorder(borderColor));
107
        setOpaque(true);
108
    }
109
    
110
    private void addGlyphGutter(JTextComponent jtx) {
111
        ClassLoader cls = Lookup.getDefault().lookup(ClassLoader.class);
112
        Class clazz;
113
        Class editorUiClass;
114
        
115
        JComponent gutter = null;
116
        try {
117
            clazz = Class.forName("org.netbeans.editor.GlyphGutter", true, cls); // NOI18N
118
            editorUiClass = Class.forName("org.netbeans.editor.EditorUI", true, cls); // NOI18N
119
            // get the factory instance
120
            Object o = clazz.newInstance();
121
            Method m = clazz.getDeclaredMethod("createSideBar", JTextComponent.class); // NOI18N
122
            gutter = (JComponent)m.invoke(o, jtx);
123
        } catch (IllegalArgumentException ex) {
124
            Exceptions.printStackTrace(ex);
125
        } catch (InvocationTargetException ex) {
126
            Exceptions.printStackTrace(ex);
127
        } catch (NoSuchMethodException ex) {
128
            Exceptions.printStackTrace(ex);
129
        } catch (SecurityException ex) {
130
            Exceptions.printStackTrace(ex);
131
        } catch (InstantiationException ex) {
132
            Exceptions.printStackTrace(ex);
133
        } catch (IllegalAccessException ex) {
134
            Exceptions.printStackTrace(ex);
135
        } catch (ClassNotFoundException ex) {
136
            Exceptions.printStackTrace(ex);
137
        }
138
        if (gutter != null) {
139
            add(gutter, BorderLayout.WEST);
140
        }
141
    }
142
143
    @Override
144
    public Dimension getPreferredSize() {
145
        Dimension prefSize = super.getPreferredSize();
146
        // Return width like for editor pane which forces the PopupManager to display
147
        // the tooltip to align exacty with the text (below/above).
148
        prefSize.width = Math.min(prefSize.width, editorPaneWidth);
149
        return prefSize;
150
    }
151
152
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldView.java (-1 / +1 lines)
Lines 42-48 Link Here
42
 * made subject to such option by the copyright holder.
42
 * made subject to such option by the copyright holder.
43
 */
43
 */
44
44
45
package org.netbeans.modules.editor.fold;
45
package org.netbeans.modules.editor.fold.ui;
46
46
47
import java.awt.Color;
47
import java.awt.Color;
48
import java.awt.Container;
48
import java.awt.Container;
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldViewFactory.java (-2 / +2 lines)
Lines 42-48 Link Here
42
 * made subject to such option by the copyright holder.
42
 * made subject to such option by the copyright holder.
43
 */
43
 */
44
44
45
package org.netbeans.modules.editor.fold;
45
package org.netbeans.modules.editor.fold.ui;
46
46
47
import java.util.Iterator;
47
import java.util.Iterator;
48
import java.util.logging.Level;
48
import java.util.logging.Level;
Lines 85-91 Link Here
85
    // -J-Dorg.netbeans.modules.editor.fold.FoldViewFactory.level=FINE
85
    // -J-Dorg.netbeans.modules.editor.fold.FoldViewFactory.level=FINE
86
    private static final Logger LOG = Logger.getLogger(FoldViewFactory.class.getName());
86
    private static final Logger LOG = Logger.getLogger(FoldViewFactory.class.getName());
87
87
88
    static void register() {
88
    public static void register() {
89
        EditorViewFactory.registerFactory(new FoldFactory());
89
        EditorViewFactory.registerFactory(new FoldFactory());
90
    }
90
    }
91
91
(-)a/editor.fold/test/unit/src/org/netbeans/modules/editor/fold/BaseCaretTest.java (+374 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2013 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
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2013 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.fold;
43
44
import java.awt.AWTEvent;
45
import java.awt.Component;
46
import java.awt.Rectangle;
47
import java.awt.Window;
48
import java.awt.event.InputEvent;
49
import java.awt.event.MouseEvent;
50
import java.beans.PropertyVetoException;
51
import java.io.IOException;
52
import java.lang.reflect.Method;
53
import java.net.URL;
54
import java.util.ArrayList;
55
import java.util.Collection;
56
import java.util.Enumeration;
57
import java.util.HashMap;
58
import java.util.Map;
59
import java.util.concurrent.Callable;
60
import java.util.prefs.AbstractPreferences;
61
import java.util.prefs.BackingStoreException;
62
import javax.swing.JEditorPane;
63
import javax.swing.JFrame;
64
import javax.swing.SwingUtilities;
65
import javax.swing.event.DocumentEvent;
66
import javax.swing.text.BadLocationException;
67
import org.netbeans.api.editor.fold.Fold;
68
import org.netbeans.api.editor.fold.FoldType;
69
import org.netbeans.api.editor.fold.FoldUtilities;
70
import org.netbeans.editor.BaseCaret;
71
import org.netbeans.editor.BaseKit;
72
import org.netbeans.editor.ext.ExtKit;
73
import org.netbeans.junit.NbTestCase;
74
import org.netbeans.modules.editor.NbEditorKit;
75
//import org.netbeans.modules.editor.NbEditorKit;
76
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
77
import org.netbeans.spi.editor.fold.FoldManager;
78
import org.netbeans.spi.editor.fold.FoldManagerFactory;
79
import org.netbeans.spi.editor.fold.FoldOperation;
80
import org.openide.filesystems.Repository;
81
import org.openide.filesystems.XMLFileSystem;
82
import org.openide.util.Exceptions;
83
import org.openide.util.Lookup;
84
import org.openide.util.lookup.Lookups;
85
import org.openide.util.lookup.ProxyLookup;
86
import org.xml.sax.SAXException;
87
88
import static junit.framework.Assert.assertEquals;
89
90
/**
91
 * Checks that the folding succeeds in plugging into BaseCaret's operation.
92
 * It checks that a doubleclick on a folded place does not select the surrounding text.
93
 * Likewise it checks, that a doubleclick on an unfolded place WILL select the surrounding text
94
 * 
95
 * @author sdedic
96
 */
97
public class BaseCaretTest extends NbTestCase implements Callable<Boolean> {
98
    private JEditorPane pane;
99
    private FoldHierarchyTestEnv env;
100
    public static Lkp DEFAULT_LOOKUP = null;
101
    
102
    public BaseCaretTest(String name) {
103
        super(name);
104
    }
105
    
106
    protected boolean runInEQ() {
107
        return true;
108
    }
109
110
    public static void setLookup(Object[] instances, ClassLoader cl) {
111
        DEFAULT_LOOKUP.doSetLookups(new Lookup[] {
112
            Lookups.fixed(instances),
113
            Lookups.metaInfServices(cl),
114
            Lookups.singleton(cl),
115
        });
116
    }
117
    
118
    public static class Lkp extends ProxyLookup {
119
        {
120
            DEFAULT_LOOKUP = this;
121
        }
122
        
123
        public void doSetLookups(Lookup... lkps) {
124
            super.setLookups(lkps);
125
        }
126
    }
127
    
128
    private static final Method PROCESS_EVENT_METHOD;
129
    
130
    static {
131
        System.setProperty("org.openide.util.Lookup", BaseCaretTest.Lkp.class.getName());
132
        Method m = null;
133
        
134
        try {
135
            m = Component.class.getDeclaredMethod("processEvent", new Class[] { AWTEvent.class });
136
            m.setAccessible(true);
137
        } catch (Exception ex) {
138
            Exceptions.printStackTrace(ex);
139
        }
140
        PROCESS_EVENT_METHOD = m;
141
    }
142
143
    public static void prepareTest(String[] additionalLayers, Object[] additionalLookupContent) throws IOException, SAXException, PropertyVetoException {
144
        Collection<URL> allUrls = new ArrayList<URL>();
145
        for (String u : additionalLayers) {
146
            if (u.charAt(0) == '/') {
147
                u = u.substring(1);
148
            }
149
            for (Enumeration<URL> en = Thread.currentThread().getContextClassLoader().getResources(u); en.hasMoreElements(); ) {
150
                allUrls.add(en.nextElement());
151
            }
152
        }
153
        XMLFileSystem system = new XMLFileSystem();
154
        system.setXmlUrls(allUrls.toArray(new URL[allUrls.size()]));
155
        
156
        Repository repository = new Repository(system);
157
        Object[] lookupContent = new Object[additionalLookupContent.length + 1];
158
        
159
        System.arraycopy(additionalLookupContent, 0, lookupContent, 1, additionalLookupContent.length);
160
        
161
        lookupContent[0] = repository;
162
        
163
        setLookup(lookupContent, BaseCaretTest.class.getClassLoader());
164
    }
165
    
166
    private FM fm;
167
    
168
    private Fold fold;
169
    
170
    private Callable<Boolean> delegate;
171
172
    public void setUp() throws Exception {
173
        super.setUp();
174
        Lookup.getDefault();
175
        env = new FoldHierarchyTestEnv(new FoldManagerFactory() {
176
            @Override
177
            public FoldManager createFoldManager() {
178
                return fm = new FM();
179
            }
180
        });
181
        prepareTest(new String[] {
182
                "/org/netbeans/modules/editor/resources/layer.xml",
183
                "/META-INF/generated-layer.xml",
184
                "/org/netbeans/modules/defaults/mf-layer.xml",
185
        },
186
        new Object[0]);
187
        pane = env.getPane();
188
        env.getPane().setEditorKit(new NbEditorKit());
189
        env.getDocument().insertString(0, "123456789-123456789-123 aa 89-123456789-1234567890", null);
190
        
191
        // ensure initialized
192
        env.getHierarchy();
193
        // cannot be done in FoldManager, as setEditorKit() will replace document and reinitialize folds in 2nd thread, which may
194
        // complete even faster than insertString(), so BLE could be thrown.
195
        FoldHierarchyTransaction tran = fm.op.openTransaction();
196
        fm.op.addToHierarchy(
197
                FT, 
198
                "", 
199
                true, 
200
                10, 
201
                40, 
202
                1, 1, 
203
                null, 
204
                tran);
205
        tran.commit();
206
                    
207
        fold = FoldUtilities.findOffsetFold(env.getHierarchy(), 25);
208
       // get x/y of the folded part:
209
        JFrame fr = new JFrame("test");
210
        fr.setBounds(0, 0, 400, 100);
211
        fr.add(pane);
212
        fr.setVisible(true);
213
        
214
        delegate = (Callable<Boolean>)pane.getClientProperty("org.netbeans.api.fold.expander");
215
        pane.putClientProperty("org.netbeans.api.fold.expander", this);
216
        pane.requestFocus();
217
    }
218
219
    public void tearDown() throws Exception {
220
        Window w = SwingUtilities.getWindowAncestor(pane);
221
        w.setVisible(false);
222
        super.tearDown();
223
    }
224
    
225
    private void processEvent(AWTEvent e) throws Exception{
226
        PROCESS_EVENT_METHOD.invoke(pane, e);
227
    }
228
    
229
    private boolean retValue;
230
231
    /**
232
     * Intercept the real callable
233
     */
234
    @Override
235
    public Boolean call() throws Exception {
236
        return retValue = delegate.call();
237
    }
238
    
239
    /**
240
     * Folded region is double-clicked; it should be unfolded, rather than word-selected.
241
     */
242
    public void testSelectFoldedRegion() throws Exception {
243
        env.getHierarchy().collapse(fold);
244
        Rectangle r = pane.modelToView(25);
245
        
246
        // 1st mouseclick on the pane, to position the caret, as if the 1st click in doubleclick was done:
247
        MouseEvent firstPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 
248
                InputEvent.BUTTON1_MASK, r.x, r.y, 1, false);
249
        processEvent(firstPress);
250
        
251
        // second press of the doubleclick
252
        MouseEvent secondPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 
253
                InputEvent.BUTTON1_MASK, r.x, r.y, 2, false);
254
        processEvent(secondPress);
255
        
256
        // check that no selection is present:
257
        assertEquals(pane.getSelectionEnd(), pane.getSelectionStart());
258
        assertNull(pane.getSelectedText());
259
        // check that even the callable did the job:
260
        assertFalse(fold.isCollapsed());
261
    }
262
    
263
    public void testSelectUnfoldedRegion() throws Exception {
264
        Rectangle r = pane.modelToView(25);
265
        
266
        // 1st mouseclick on the pane, to position the caret, as if the 1st click in doubleclick was done:
267
        MouseEvent firstPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 
268
                InputEvent.BUTTON1_MASK, r.x, r.y, 1, false);
269
        processEvent(firstPress);
270
        
271
        // second press of the doubleclick
272
        MouseEvent secondPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 
273
                InputEvent.BUTTON1_MASK, r.x, r.y, 2, false);
274
        
275
        processEvent(secondPress);
276
        
277
        int start = pane.getSelectionStart();
278
        int end = pane.getSelectionEnd();
279
        assertTrue(start < end);
280
    }
281
    
282
    private static final FoldType FT = new FoldType("test");
283
    
284
    private static class FM implements FoldManager {
285
        FoldOperation op;
286
        
287
        @Override
288
        public void init(FoldOperation operation) {
289
            this.op = operation;
290
        }
291
292
        @Override
293
        public void initFolds(FoldHierarchyTransaction transaction) {
294
        }
295
296
        @Override
297
        public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
298
        }
299
300
        @Override
301
        public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
302
        }
303
304
        @Override
305
        public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
306
        }
307
308
        @Override
309
        public void removeEmptyNotify(Fold epmtyFold) {
310
        }
311
312
        @Override
313
        public void removeDamagedNotify(Fold damagedFold) {
314
        }
315
316
        @Override
317
        public void expandNotify(Fold expandedFold) {
318
        }
319
320
        @Override
321
        public void release() {
322
        }
323
    }
324
    
325
    private static class P extends AbstractPreferences {
326
        private Map<String, String>  m = new HashMap<String, String>();
327
328
        public P(AbstractPreferences parent, String name) {
329
            super(parent, name);
330
        }
331
        
332
        @Override
333
        protected void putSpi(String key, String value) {
334
            m.put(key, value);
335
        }
336
337
        @Override
338
        protected String getSpi(String key) {
339
            return m.get(key);
340
        }
341
342
        @Override
343
        protected void removeSpi(String key) {
344
            m.remove(key);
345
        }
346
347
        @Override
348
        protected void removeNodeSpi() throws BackingStoreException {
349
        }
350
351
        @Override
352
        protected String[] keysSpi() throws BackingStoreException {
353
            return new String[0];
354
        }
355
356
        @Override
357
        protected String[] childrenNamesSpi() throws BackingStoreException {
358
            return new String[0];
359
        }
360
361
        @Override
362
        protected AbstractPreferences childSpi(String name) {
363
            return null;
364
        }
365
366
        @Override
367
        protected void syncSpi() throws BackingStoreException {
368
        }
369
370
        @Override
371
        protected void flushSpi() throws BackingStoreException {
372
        }
373
    }
374
}
(-)a/editor.lib/apichanges.xml (+30 lines)
Lines 107-112 Link Here
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
108
108
109
    <changes>
109
    <changes>
110
        <change id="caret-fold-cleanup">
111
            <summary>Removed dependencies on Folding</summary>
112
            <version major="3" minor="37"/>
113
            <date day="26" month="2" year="2013"/>
114
            <author login="sdedic"/>
115
            <compatibility binary="incompatible" source="incompatible" semantic="compatible" addition="yes"/>
116
            <description>
117
                <p>
118
                    Inheritance of FoldHierarchyListener was removed, as it was the last dependency on 
119
                    fold API from the editor.lib module. 
120
                </p>
121
            </description>
122
            <class name="BaseCaret" package="org.netbeans.editor"/>
123
            <issue number="226368"/>
124
        </change>
125
        <change id="caret-refresh">
126
            <summary>Allow to refresh editor view so that caret is visible</summary>
127
            <version major="3" minor="37"/>
128
            <date day="26" month="2" year="2013"/>
129
            <author login="sdedic"/>
130
            <compatibility binary="compatible" source="compatible" semantic="compatible" addition="yes"/>
131
            <description>
132
                <p>
133
                    The method helps to ensure that the caret, if it was originally visible on the screen,
134
                    will remain visible after some view hierarchy change (i.e. define a new fold).
135
                </p>
136
            </description>
137
            <class name="BaseCaret" package="org.netbeans.editor"/>
138
            <issue number="226368"/>
139
        </change>
110
        <change id="moving-find">
140
        <change id="moving-find">
111
            <summary>Moving find implementations to module editor.search</summary>
141
            <summary>Moving find implementations to module editor.search</summary>
112
            <version major="3" minor="36"/>
142
            <version major="3" minor="36"/>
(-)a/editor.lib/arch.xml (+9 lines)
Lines 632-637 Link Here
632
        To specify modifiers for which the hyperlinking should be enabled, or to switch the hyperlinking off.
632
        To specify modifiers for which the hyperlinking should be enabled, or to switch the hyperlinking off.
633
        Valid values are "[CSMA]+" (to specify combination of modifiers) or "off" (to switch hyperlinking off).
633
        Valid values are "[CSMA]+" (to specify combination of modifiers) or "off" (to switch hyperlinking off).
634
    </api>
634
    </api>
635
    <api type="import" group="clientproperty" name="org.netbeans.api.fold.expander" category="friend">
636
        The client property must be defined on JTextComponent managed by the NetBeans editor.
637
        <p/>
638
        Mouse gestures require to determine whether the point at caret is folded or not. Plain text is then selected.
639
        The client property org.netbeans.api.fold.expander (if defined) should contains a Callable&lt;Boolean> that
640
        returns false, if the point is a plaintext, true otherwise. Fold expansion should be handled by the Callable.
641
        <p/>
642
        editor.fold module uses this client property to hook into BaseCaret processing.
643
    </api>
635
</answer>
644
</answer>
636
645
637
646
(-)a/editor.lib/module-auto-deps.xml (+10 lines)
Lines 62-66 Link Here
62
                </result>
62
                </result>
63
            </implies>
63
            </implies>
64
        </transformation>
64
        </transformation>
65
        <transformation>
66
            <trigger-dependency type="older">
67
                <module-dependency codenamebase="org.netbeans.modules.editor.lib" major="1" spec="3.36"/>
68
            </trigger-dependency>
69
            <implies>
70
                <result>
71
                    <module-dependency codenamebase="org.netbeans.modules.editor.fold" major="1" spec="1.34"/>
72
                </result>
73
            </implies>
74
        </transformation>
65
    </transformationgroup>
75
    </transformationgroup>
66
</transformations>
76
</transformations>
(-)a/editor.lib/nbproject/project.properties (-1 / +1 lines)
Lines 42-48 Link Here
42
42
43
javac.compilerargs=-Xlint:unchecked
43
javac.compilerargs=-Xlint:unchecked
44
javac.source=1.6
44
javac.source=1.6
45
spec.version.base=3.36.0
45
spec.version.base=3.37.0
46
is.autoload=true
46
is.autoload=true
47
47
48
javadoc.arch=${basedir}/arch.xml
48
javadoc.arch=${basedir}/arch.xml
(-)a/editor.lib/nbproject/project.xml (-8 lines)
Lines 59-72 Link Here
59
                    </run-dependency>
59
                    </run-dependency>
60
                </dependency>
60
                </dependency>
61
                <dependency>
61
                <dependency>
62
                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
63
                    <build-prerequisite/>
64
                    <compile-dependency/>
65
                    <run-dependency>
66
                        <release-version>1</release-version>
67
                    </run-dependency>
68
                </dependency>
69
                <dependency>
70
                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
62
                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
71
                    <build-prerequisite/>
63
                    <build-prerequisite/>
72
                    <compile-dependency/>
64
                    <compile-dependency/>
(-)a/editor.lib/src/org/netbeans/editor/ActionFactory.java (-193 / +7 lines)
Lines 84-93 Link Here
84
import org.netbeans.api.editor.EditorActionNames;
84
import org.netbeans.api.editor.EditorActionNames;
85
import org.netbeans.api.editor.EditorActionRegistration;
85
import org.netbeans.api.editor.EditorActionRegistration;
86
import org.netbeans.api.editor.EditorActionRegistrations;
86
import org.netbeans.api.editor.EditorActionRegistrations;
87
import org.netbeans.api.editor.fold.Fold;
88
import org.netbeans.api.editor.fold.FoldHierarchy;
89
import org.netbeans.api.editor.fold.FoldUtilities;
90
import org.netbeans.api.lexer.TokenHierarchy;
91
import org.netbeans.api.progress.ProgressUtils;
87
import org.netbeans.api.progress.ProgressUtils;
92
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
88
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
93
import org.netbeans.lib.editor.util.swing.PositionRegion;
89
import org.netbeans.lib.editor.util.swing.PositionRegion;
Lines 2106-2297 Link Here
2106
    }
2102
    }
2107
2103
2108
2104
2109
    /** Returns the fold that should be collapsed/expanded in the caret row
2110
     *  @param hierarchy hierarchy under which all folds should be collapsed/expanded.
2111
     *  @param dot caret position offset
2112
     *  @param lineStart offset of the start of line
2113
     *  @param lineEnd offset of the end of line
2114
     *  @return the fold that meet common criteria in accordance with the caret position
2115
     */
2116
    private static Fold getLineFold(FoldHierarchy hierarchy, int dot, int lineStart, int lineEnd){
2117
        Fold caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy, dot);
2118
2119
        // beginning searching from the lineStart
2120
        Fold fold = FoldUtilities.findNearestFold(hierarchy, lineStart);  
2121
        
2122
        while (fold!=null && 
2123
                  (fold.getEndOffset()<=dot || // find next available fold if the 'fold' is one-line
2124
                      // or it has children and the caret is in the fold body
2125
                      // i.e. class A{ |public void method foo(){}}
2126
                      (!fold.isCollapsed() && fold.getFoldCount() > 0  && fold.getStartOffset()+1<dot) 
2127
                   )
2128
               ){
2129
2130
                   // look for next fold in forward direction
2131
                   Fold nextFold = FoldUtilities.findNearestFold(hierarchy,
2132
                       (fold.getFoldCount()>0) ? fold.getStartOffset()+1 : fold.getEndOffset());
2133
                   if (nextFold!=null && nextFold.getStartOffset()<lineEnd){
2134
                       if (nextFold == fold) return fold;
2135
                       fold = nextFold;
2136
                   }else{
2137
                       break;
2138
                   }
2139
        }
2140
2141
        
2142
        // a fold on the next line was found, returning fold at offset (in most cases inner class)
2143
        if (fold == null || fold.getStartOffset()>lineEnd) {
2144
2145
            // in the case:
2146
            // class A{
2147
            // }     |
2148
            // try to find an offset fold on the offset of the line beginning
2149
            if (caretOffsetFold == null){
2150
                caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy, lineStart);
2151
            }
2152
            
2153
            return caretOffsetFold;
2154
        }
2155
        
2156
        // no fold at offset found, in this case return the fold
2157
        if (caretOffsetFold == null) return fold;
2158
        
2159
        // skip possible inner class members validating if the innerclass fold is collapsed
2160
        if (caretOffsetFold.isCollapsed()) return caretOffsetFold;
2161
        
2162
        // in the case:
2163
        // class A{
2164
        // public vo|id foo(){} }
2165
        // 'fold' (in this case fold of the method foo) will be returned
2166
        if ( caretOffsetFold.getEndOffset()>fold.getEndOffset() && 
2167
             fold.getEndOffset()>dot){
2168
            return fold;
2169
        }
2170
        
2171
        // class A{
2172
        // |} public void method foo(){}
2173
        // inner class fold will be returned
2174
        if (fold.getStartOffset()>caretOffsetFold.getEndOffset()) return caretOffsetFold;
2175
        
2176
        // class A{
2177
        // public void foo(){} |}
2178
        // returning innerclass fold
2179
        if (fold.getEndOffset()<dot) return caretOffsetFold;
2180
        
2181
        return fold;
2182
    }
2183
    
2184
    /** Collapse a fold. Depends on the current caret position. */
2185
    @EditorActionRegistration(name = BaseKit.collapseFoldAction,
2186
            menuText = "#" + BaseKit.collapseFoldAction + "_menu_text")
2187
    public static class CollapseFold extends LocalBaseAction {
2188
        public CollapseFold(){
2189
        }
2190
        
2191
        private boolean dotInFoldArea(JTextComponent target, Fold fold, int dot) throws BadLocationException{
2192
            int foldStart = fold.getStartOffset();
2193
            int foldEnd = fold.getEndOffset();
2194
            int foldRowStart = javax.swing.text.Utilities.getRowStart(target, foldStart);
2195
            int foldRowEnd = javax.swing.text.Utilities.getRowEnd(target, foldEnd);
2196
            if (foldRowStart > dot || foldRowEnd < dot) return false; // it's not fold encapsulating dot
2197
            return true;
2198
            }
2199
2200
        
2201
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2202
            Document doc = target.getDocument();
2203
            doc.render(new Runnable() {
2204
                @Override
2205
                public void run() {
2206
                    FoldHierarchy hierarchy = FoldHierarchy.get(target);
2207
                    int dot = target.getCaret().getDot();
2208
                    hierarchy.lock();
2209
                    try{
2210
                        try{
2211
                            int rowStart = javax.swing.text.Utilities.getRowStart(target, dot);
2212
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(target, dot);
2213
                            Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
2214
                            fold = getLineFold(hierarchy, dot, rowStart, rowEnd);
2215
                            if (fold==null){
2216
                                return; // no success
2217
                            }
2218
                            // ensure we' got the right fold
2219
                            if (dotInFoldArea(target, fold, dot)){
2220
                                hierarchy.collapse(fold);
2221
                            }
2222
                        }catch(BadLocationException ble){
2223
                            ble.printStackTrace();
2224
                        }
2225
                    }finally {
2226
                        hierarchy.unlock();
2227
                    }
2228
                }
2229
            });
2230
        }
2231
    }
2232
    
2233
    /** Expand a fold. Depends on the current caret position. */
2234
    @EditorActionRegistration(name = BaseKit.expandFoldAction,
2235
            menuText = "#" + BaseKit.expandFoldAction + "_menu_text")
2236
    public static class ExpandFold extends LocalBaseAction {
2237
        public ExpandFold(){
2238
        }
2239
        
2240
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2241
            Document doc = target.getDocument();
2242
            doc.render(new Runnable() {
2243
                @Override
2244
                public void run() {
2245
                    FoldHierarchy hierarchy = FoldHierarchy.get(target);
2246
                    int dot = target.getCaret().getDot();
2247
                    hierarchy.lock();
2248
                    try {
2249
                        try {
2250
                            int rowStart = javax.swing.text.Utilities.getRowStart(target, dot);
2251
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(target, dot);
2252
                            Fold fold = getLineFold(hierarchy, dot, rowStart, rowEnd);
2253
                            if (fold != null) {
2254
                                hierarchy.expand(fold);
2255
                            }
2256
                        } catch (BadLocationException ble) {
2257
                            ble.printStackTrace();
2258
                        }
2259
                    } finally {
2260
                        hierarchy.unlock();
2261
                    }
2262
                }
2263
            });
2264
        }
2265
    }
2266
    
2267
    /** Collapse all existing folds in the document. */
2268
    @EditorActionRegistration(name = BaseKit.collapseAllFoldsAction,
2269
            menuText = "#" + BaseKit.collapseAllFoldsAction + "_menu_text")
2270
    public static class CollapseAllFolds extends LocalBaseAction {
2271
        public CollapseAllFolds(){
2272
        }
2273
        
2274
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2275
            FoldHierarchy hierarchy = FoldHierarchy.get(target);
2276
            // Hierarchy locking done in the utility method
2277
            FoldUtilities.collapseAll(hierarchy);
2278
        }
2279
    }
2280
2281
    /** Expand all existing folds in the document. */
2282
    @EditorActionRegistration(name = BaseKit.expandAllFoldsAction,
2283
            menuText = "#" + BaseKit.expandAllFoldsAction + "_menu_text")
2284
    public static class ExpandAllFolds extends LocalBaseAction {
2285
        public ExpandAllFolds(){
2286
        }
2287
        
2288
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2289
            FoldHierarchy hierarchy = FoldHierarchy.get(target);
2290
            // Hierarchy locking done in the utility method
2291
            FoldUtilities.expandAll(hierarchy);
2292
        }
2293
    }
2294
2295
    /** Expand all existing folds in the document. */
2105
    /** Expand all existing folds in the document. */
2296
    @EditorActionRegistration(name = "dump-view-hierarchy")
2106
    @EditorActionRegistration(name = "dump-view-hierarchy")
2297
    public static class DumpViewHierarchyAction extends LocalBaseAction {
2107
    public static class DumpViewHierarchyAction extends LocalBaseAction {
Lines 2303-2317 Link Here
2303
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2113
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2304
            AbstractDocument adoc = (AbstractDocument)target.getDocument();
2114
            AbstractDocument adoc = (AbstractDocument)target.getDocument();
2305
2115
2116
            /*
2117
             * Folding has moved to editor.fold module. It must somehow hook
2118
             * into this dump to provide the information.
2119
2306
            // Dump fold hierarchy
2120
            // Dump fold hierarchy
2307
            FoldHierarchy hierarchy = FoldHierarchy.get(target);
2121
            FoldHierarchy hierarchy = FoldHierarchy.get(target);
2308
            adoc.readLock();
2122
            adoc.readLock();
2309
            try {
2123
            try {
2310
                hierarchy.lock();
2124
                hierarchy.lock();
2311
                try {
2125
                try {
2312
                    /*DEBUG*/System.err.println("FOLD HIERARCHY DUMP:\n" + hierarchy); // NOI18N
2126
                    /*DEBUG* /System.err.println("FOLD HIERARCHY DUMP:\n" + hierarchy); // NOI18N
2313
                    TokenHierarchy<?> th = TokenHierarchy.get(adoc);
2127
                    TokenHierarchy<?> th = TokenHierarchy.get(adoc);
2314
                    /*DEBUG*/System.err.println("TOKEN HIERARCHY DUMP:\n" + (th != null ? th : "<NULL-TH>")); // NOI18N
2128
                    /*DEBUG* /System.err.println("TOKEN HIERARCHY DUMP:\n" + (th != null ? th : "<NULL-TH>")); // NOI18N
2315
2129
2316
                } finally {
2130
                } finally {
2317
                    hierarchy.unlock();
2131
                    hierarchy.unlock();
Lines 2319-2325 Link Here
2319
            } finally {
2133
            } finally {
2320
                adoc.readUnlock();
2134
                adoc.readUnlock();
2321
            }
2135
            }
2322
2136
            */
2323
            View rootView = null;
2137
            View rootView = null;
2324
            TextUI textUI = target.getUI();
2138
            TextUI textUI = target.getUI();
2325
            if (textUI != null) {
2139
            if (textUI != null) {
(-)a/editor.lib/src/org/netbeans/editor/BaseCaret.java (-145 / +80 lines)
Lines 78-83 Link Here
78
import java.io.IOException;
78
import java.io.IOException;
79
import java.util.ArrayList;
79
import java.util.ArrayList;
80
import java.util.List;
80
import java.util.List;
81
import java.util.concurrent.Callable;
81
import java.util.logging.Level;
82
import java.util.logging.Level;
82
import java.util.logging.Logger;
83
import java.util.logging.Logger;
83
import java.util.prefs.PreferenceChangeEvent;
84
import java.util.prefs.PreferenceChangeEvent;
Lines 105-116 Link Here
105
import javax.swing.text.AttributeSet;
106
import javax.swing.text.AttributeSet;
106
import javax.swing.text.Position;
107
import javax.swing.text.Position;
107
import javax.swing.text.StyleConstants;
108
import javax.swing.text.StyleConstants;
108
import org.netbeans.api.editor.fold.Fold;
109
import org.netbeans.api.editor.fold.FoldHierarchy;
110
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
111
import org.netbeans.api.editor.fold.FoldHierarchyListener;
112
import org.netbeans.api.editor.fold.FoldStateChange;
113
import org.netbeans.api.editor.fold.FoldUtilities;
114
import org.netbeans.api.editor.mimelookup.MimeLookup;
109
import org.netbeans.api.editor.mimelookup.MimeLookup;
115
import org.netbeans.api.editor.settings.FontColorNames;
110
import org.netbeans.api.editor.settings.FontColorNames;
116
import org.netbeans.api.editor.settings.FontColorSettings;
111
import org.netbeans.api.editor.settings.FontColorSettings;
Lines 136-142 Link Here
136
public class BaseCaret implements Caret,
131
public class BaseCaret implements Caret,
137
MouseListener, MouseMotionListener, PropertyChangeListener,
132
MouseListener, MouseMotionListener, PropertyChangeListener,
138
DocumentListener, ActionListener, 
133
DocumentListener, ActionListener, 
139
AtomicLockListener, FoldHierarchyListener {
134
AtomicLockListener {
140
135
141
    /** Caret type representing block covering current character */
136
    /** Caret type representing block covering current character */
142
    public static final String BLOCK_CARET = EditorPreferencesDefaults.BLOCK_CARET; // NOI18N
137
    public static final String BLOCK_CARET = EditorPreferencesDefaults.BLOCK_CARET; // NOI18N
Lines 279-285 Link Here
279
     * its relative visual position on the screen.
274
     * its relative visual position on the screen.
280
     */
275
     */
281
    private boolean updateAfterFoldHierarchyChange;
276
    private boolean updateAfterFoldHierarchyChange;
282
    private FoldHierarchyListener weakFHListener;
283
    
277
    
284
    /**
278
    /**
285
     * Whether at least one typing change occurred during possibly several atomic operations.
279
     * Whether at least one typing change occurred during possibly several atomic operations.
Lines 530-542 Link Here
530
        EditorUI editorUI = Utilities.getEditorUI(c);
524
        EditorUI editorUI = Utilities.getEditorUI(c);
531
        editorUI.removePropertyChangeListener(this);
525
        editorUI.removePropertyChangeListener(this);
532
526
533
        if (weakFHListener != null) {
534
            FoldHierarchy hierarchy = FoldHierarchy.get(c);
535
            if (hierarchy != null) {
536
                hierarchy.removeFoldHierarchyListener(weakFHListener);
537
            }
538
        }
539
540
        modelChanged(listenDoc, null);
527
        modelChanged(listenDoc, null);
541
    }
528
    }
542
529
Lines 1231-1257 Link Here
1231
                        caretPos = doc.createPosition(offset);
1218
                        caretPos = doc.createPosition(offset);
1232
                        markPos = doc.createPosition(offset);
1219
                        markPos = doc.createPosition(offset);
1233
1220
1234
                        FoldHierarchy hierarchy = FoldHierarchy.get(c);
1221
                        Callable<Boolean> cc = (Callable<Boolean>)c.getClientProperty("org.netbeans.api.fold.expander");
1235
                        // hook the listener if not already done
1222
                        if (cc != null) {
1236
                        if (weakFHListener == null) {
1223
                            // the caretPos/markPos were already called.
1237
                            weakFHListener = WeakListeners.create(FoldHierarchyListener.class, this, hierarchy);
1224
                            // nothing except the document is locked at this moment.
1238
                            hierarchy.addFoldHierarchyListener(weakFHListener);
1225
                            try {
1239
                        }
1226
                                cc.call();
1240
1227
                            } catch (Exception ex) {
1241
                        // Unfold fold
1228
                                Exceptions.printStackTrace(ex);
1242
                        hierarchy.lock();
1243
                        try {
1244
                            Fold collapsed = null;
1245
                            while (expandFold && (collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset)) != null && collapsed.getStartOffset() < offset &&
1246
                                collapsed.getEndOffset() > offset) {
1247
                                hierarchy.expand(collapsed);
1248
                            }
1229
                            }
1249
                        } finally {
1250
                            hierarchy.unlock();
1251
                        }
1230
                        }
1252
                        if (rectangularSelection) {
1231
                        if (rectangularSelection) {
1253
                            setRectangularSelectionToDotAndMark();
1232
                            setRectangularSelectionToDotAndMark();
1254
                        }
1233
                        }
1234
                        
1255
                    } catch (BadLocationException e) {
1235
                    } catch (BadLocationException e) {
1256
                        throw new IllegalStateException(e.toString());
1236
                        throw new IllegalStateException(e.toString());
1257
                        // setting the caret to wrong position leaves it at current position
1237
                        // setting the caret to wrong position leaves it at current position
Lines 1510-1546 Link Here
1510
                    // Disable drag which would otherwise occur when mouse would be over text
1490
                    // Disable drag which would otherwise occur when mouse would be over text
1511
                    c.setDragEnabled(false);
1491
                    c.setDragEnabled(false);
1512
                    // Check possible fold expansion
1492
                    // Check possible fold expansion
1513
                    FoldHierarchy hierarchy = FoldHierarchy.get(c);
1493
                    try {
1514
                    Document doc = c.getDocument();
1494
                        // hack, to get knowledge of possible expansion. Editor depends on Folding, so it's not really possible
1515
                    if (doc instanceof AbstractDocument) {
1495
                        // to have Folding depend on BaseCaret (= a cycle). If BaseCaret moves to editor.lib2, this contract
1516
                        AbstractDocument adoc = (AbstractDocument) doc;
1496
                        // can be formalized as an interface.
1517
                        adoc.readLock();
1497
                        Callable<Boolean> cc = (Callable<Boolean>)c.getClientProperty("org.netbeans.api.fold.expander");
1518
                        try {
1498
                        if (cc == null || !cc.call()) {
1519
                            hierarchy.lock();
1499
                            if (selectWordAction == null) {
1520
                            try {
1500
                                selectWordAction = ((BaseKit) c.getUI().getEditorKit(
1521
                                Fold collapsed = FoldUtilities.findCollapsedFold(
1501
                                        c)).getActionByName(BaseKit.selectWordAction);
1522
                                        hierarchy, offset, offset);
1523
                                if (collapsed != null && collapsed.getStartOffset() <= offset
1524
                                        && collapsed.getEndOffset() >= offset) {
1525
                                    hierarchy.expand(collapsed);
1526
                                } else {
1527
                                    if (selectWordAction == null) {
1528
                                        selectWordAction = ((BaseKit) c.getUI().getEditorKit(
1529
                                                c)).getActionByName(BaseKit.selectWordAction);
1530
                                    }
1531
                                    if (selectWordAction != null) {
1532
                                        selectWordAction.actionPerformed(null);
1533
                                    }
1534
                                    // Select word action selects forward i.e. dot > mark
1535
                                    minSelectionStartOffset = getMark();
1536
                                    minSelectionEndOffset = getDot();
1537
                                }
1538
                            } finally {
1539
                                hierarchy.unlock();
1540
                            }
1502
                            }
1541
                        } finally {
1503
                            if (selectWordAction != null) {
1542
                            adoc.readUnlock();
1504
                                selectWordAction.actionPerformed(null);
1505
                            }
1506
                            // Select word action selects forward i.e. dot > mark
1507
                            minSelectionStartOffset = getMark();
1508
                            minSelectionEndOffset = getDot();
1543
                        }
1509
                        }
1510
                    } catch (Exception ex) {
1511
                        Exceptions.printStackTrace(ex);
1544
                    }
1512
                    }
1545
                    break;
1513
                    break;
1546
                    
1514
                    
Lines 1617-1653 Link Here
1617
        JTextComponent c = component;
1585
        JTextComponent c = component;
1618
        if (c != null) {
1586
        if (c != null) {
1619
            if (isMiddleMouseButtonExt(evt)) {
1587
            if (isMiddleMouseButtonExt(evt)) {
1620
		if (evt.getClickCount() == 1) {
1588
                if (evt.getClickCount() == 1) {
1621
		    if (c == null) return;
1589
                    if (c == null) {
1590
                        return;
1591
                    }
1622
                    Clipboard buffer = getSystemSelection();
1592
                    Clipboard buffer = getSystemSelection();
1623
                    
1593
1624
                    if (buffer == null) return;
1594
                    if (buffer == null) {
1595
                        return;
1596
                    }
1625
1597
1626
                    Transferable trans = buffer.getContents(null);
1598
                    Transferable trans = buffer.getContents(null);
1627
                    if (trans == null) return;
1599
                    if (trans == null) {
1600
                        return;
1601
                    }
1628
1602
1629
                    final BaseDocument doc = (BaseDocument)c.getDocument();
1603
                    final BaseDocument doc = (BaseDocument) c.getDocument();
1630
                    if (doc == null) return;
1604
                    if (doc == null) {
1631
                    
1605
                        return;
1632
                    final int offset = ((BaseTextUI)c.getUI()).viewToModel(c,
1606
                    }
1633
                                    evt.getX(), evt.getY());
1634
1607
1635
                    try{
1608
                    final int offset = ((BaseTextUI) c.getUI()).viewToModel(c,
1636
                        final String pastingString = (String)trans.getTransferData(DataFlavor.stringFlavor);
1609
                            evt.getX(), evt.getY());
1637
                        if (pastingString == null) return;
1610
1638
                        doc.runAtomicAsUser (new Runnable () {
1611
                    try {
1639
                            public @Override void run () {
1612
                        final String pastingString = (String) trans.getTransferData(DataFlavor.stringFlavor);
1640
                                 try {
1613
                        if (pastingString == null) {
1641
                                     doc.insertString(offset, pastingString, null);
1614
                            return;
1642
                                     setDot(offset+pastingString.length());
1615
                        }
1643
                                 } catch( BadLocationException exc ) {
1616
                        doc.runAtomicAsUser(new Runnable() {
1644
                                 }
1617
                            public @Override
1618
                            void run() {
1619
                                try {
1620
                                    doc.insertString(offset, pastingString, null);
1621
                                    setDot(offset + pastingString.length());
1622
                                } catch (BadLocationException exc) {
1623
                                }
1645
                            }
1624
                            }
1646
                        });
1625
                        });
1647
                    }catch(UnsupportedFlavorException ufe){
1626
                    } catch (UnsupportedFlavorException ufe) {
1648
                    }catch(IOException ioe){
1627
                    } catch (IOException ioe) {
1649
                    }
1628
                    }
1650
		}
1629
                }
1651
            }
1630
            }
1652
        }
1631
        }
1653
    }
1632
    }
Lines 2110-2176 Link Here
2110
        }
2089
        }
2111
    }
2090
    }
2112
2091
2113
    public @Override void foldHierarchyChanged(FoldHierarchyEvent evt) {
2114
        int caretOffset = getDot();
2115
        final int addedFoldCnt = evt.getAddedFoldCount();
2116
        final boolean scrollToView;
2117
        LOG.finest("Received fold hierarchy change");
2118
        if (addedFoldCnt > 0) {
2119
            FoldHierarchy hierarchy = (FoldHierarchy) evt.getSource();
2120
            Fold collapsed = null;
2121
            boolean wasExpanded = false;
2122
            while ((collapsed = FoldUtilities.findCollapsedFold(hierarchy, caretOffset, caretOffset)) != null && collapsed.getStartOffset() < caretOffset &&
2123
                    collapsed.getEndOffset() > caretOffset) {
2124
                        hierarchy.expand(collapsed);
2125
                        wasExpanded = true;
2126
                    }
2127
                    // prevent unneeded scrolling; the user may have scrolled out using mouse already
2128
                    // so scroll only if the added fold may affect Y axis. Actually it's unclear why
2129
                    // we should reveal the current position on fold events except when caret is positioned in now-collapsed fold
2130
                    scrollToView = wasExpanded;
2131
        } else {
2132
            int startOffset = Integer.MAX_VALUE;
2133
            // Set the caret's offset to the end of just collapsed fold if necessary
2134
            if (evt.getAffectedStartOffset() <= caretOffset && evt.getAffectedEndOffset() >= caretOffset) {
2135
                for (int i = 0; i < evt.getFoldStateChangeCount(); i++) {
2136
                    FoldStateChange change = evt.getFoldStateChange(i);
2137
                    if (change.isCollapsedChanged()) {
2138
                        Fold fold = change.getFold();
2139
                        if (fold.isCollapsed() && fold.getStartOffset() <= caretOffset && fold.getEndOffset() >= caretOffset) {
2140
                            if (fold.getStartOffset() < startOffset) {
2141
                                startOffset = fold.getStartOffset();
2142
                            }
2143
                        }
2144
                    }
2145
                }
2146
                if (startOffset != Integer.MAX_VALUE) {
2147
                    setDot(startOffset, false);
2148
                }
2149
            }
2150
            scrollToView = false;
2151
        }
2152
        // Update caret's visual position
2153
        // Post the caret update asynchronously since the fold hierarchy is updated before
2154
        // the view hierarchy and the views so the dispatchUpdate() could be picking obsolete
2155
        // view information.
2156
        SwingUtilities.invokeLater(new Runnable() {
2157
            public @Override void run() {
2158
                LOG.finest("Updating after fold hierarchy change");
2159
                if (component == null) {
2160
                    return;
2161
                }
2162
                // see #217867
2163
                Rectangle b = caretBounds;
2164
                updateAfterFoldHierarchyChange = b != null;
2165
                boolean wasInView = b != null && component.getVisibleRect().intersects(b);
2166
                // scroll if:
2167
                // a/ a fold was added and the caret was originally in the view
2168
                // b/ scrollToView is true (= caret was positioned within a new now collapsed fold)
2169
                dispatchUpdate((addedFoldCnt > 1 && wasInView) || scrollToView);
2170
            }
2171
        });
2172
    }
2173
    
2174
    void scheduleCaretUpdate() {
2092
    void scheduleCaretUpdate() {
2175
        if (!caretUpdatePending) {
2093
        if (!caretUpdatePending) {
2176
            caretUpdatePending = true;
2094
            caretUpdatePending = true;
Lines 2304-2308 Link Here
2304
        DRAG_SELECTION  // Drag is being done (text selection existed at the mouse press)
2222
        DRAG_SELECTION  // Drag is being done (text selection existed at the mouse press)
2305
        
2223
        
2306
    }
2224
    }
2307
    
2225
2226
    /**
2227
     * Refreshes caret display on the screen.
2228
     * Some height or view changes may result in the caret going off the screen. In some cases, this is not desirable,
2229
     * as the user's work may be interrupted by e.g. an automatic refresh. This method repositions the view so the
2230
     * caret remains visible.
2231
     * <p/>
2232
     * The method has two modes: it can reposition the view just if it originally displayed the caret and the caret became
2233
     * invisible, and it can scroll the caret into view unconditionally.
2234
     * @param retainInView true to scroll only if the caret was visible. False to refresh regardless of visibility.
2235
     */
2236
    public void refresh(boolean retainInView) {
2237
        Rectangle b = caretBounds;
2238
        updateAfterFoldHierarchyChange = b != null;
2239
        boolean wasInView = b != null && component.getVisibleRect().intersects(b);
2240
        update(!retainInView || wasInView);
2241
    }
2242
2308
}
2243
}
(-)a/editor.lib/src/org/netbeans/editor/Bundle.properties (-12 lines)
Lines 59-72 Link Here
59
bracket-match=Match Bracket
59
bracket-match=Match Bracket
60
build-popup-menu=Build Popup Menu
60
build-popup-menu=Build Popup Menu
61
dump-view-hierarchy=Dump View Hierarchy
61
dump-view-hierarchy=Dump View Hierarchy
62
collapse-all-folds_menu_text=Co&llapse All
63
collapse-fold_menu_text=&Collapse Fold
64
expand-all-folds_menu_text=E&xpand All
65
expand-fold_menu_text=&Expand Fold
66
collapse-all-folds=Collapse All
67
collapse-fold=Collapse Fold
68
expand-all-folds=Expand All
69
expand-fold=Expand Fold
70
build-tool-tip=Build Tool Tip
62
build-tool-tip=Build Tool Tip
71
caret-backward=Insertion Point Backward
63
caret-backward=Insertion Point Backward
72
caret-begin-line=Insertion Point to Beginning of Text on Line
64
caret-begin-line=Insertion Point to Beginning of Text on Line
Lines 311-320 Link Here
311
## ext.Completion.java
303
## ext.Completion.java
312
ext.Completion.wait=Please wait...
304
ext.Completion.wait=Please wait...
313
305
314
# CodeFoldingSideBar
315
ACSN_CodeFoldingSideBar=Code folding side bar
316
ACSD_CodeFoldingSideBar=Code folding side bar shows text folds and allows their collapsing and expanding.
317
318
# Format Action
306
# Format Action
319
Format_in_progress=Formatting text, please wait...
307
Format_in_progress=Formatting text, please wait...
320
308
(-)a/editor.lib/src/org/netbeans/editor/CodeFoldingSideBar.java (-1472 lines)
Lines 1-1472 Link Here
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
45
package org.netbeans.editor;
46
47
import java.awt.BasicStroke;
48
import java.awt.Color;
49
import java.awt.Dimension;
50
import java.awt.Font;
51
import java.awt.FontMetrics;
52
import java.awt.Graphics;
53
import java.awt.Graphics2D;
54
import java.awt.Point;
55
import java.awt.Rectangle;
56
import java.awt.Stroke;
57
import java.awt.event.MouseAdapter;
58
import java.awt.event.MouseEvent;
59
import java.util.ArrayList;
60
import java.util.Collections;
61
import java.util.List;
62
import java.util.Map;
63
import java.util.NavigableMap;
64
import java.util.TreeMap;
65
import java.util.logging.Level;
66
import java.util.logging.Logger;
67
import java.util.prefs.PreferenceChangeEvent;
68
import java.util.prefs.PreferenceChangeListener;
69
import java.util.prefs.Preferences;
70
import javax.accessibility.Accessible;
71
import javax.accessibility.AccessibleContext;
72
import javax.accessibility.AccessibleRole;
73
import javax.swing.JComponent;
74
import javax.swing.SwingUtilities;
75
import javax.swing.event.DocumentEvent;
76
import javax.swing.event.DocumentListener;
77
import javax.swing.text.AbstractDocument;
78
import javax.swing.text.AttributeSet;
79
import javax.swing.text.BadLocationException;
80
import javax.swing.text.Document;
81
import javax.swing.text.JTextComponent;
82
import javax.swing.text.View;
83
import org.netbeans.api.editor.fold.Fold;
84
import org.netbeans.api.editor.fold.FoldHierarchy;
85
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
86
import org.netbeans.api.editor.fold.FoldHierarchyListener;
87
import org.netbeans.api.editor.fold.FoldUtilities;
88
import org.netbeans.api.editor.mimelookup.MimeLookup;
89
import org.netbeans.api.editor.settings.AttributesUtilities;
90
import org.netbeans.api.editor.settings.FontColorNames;
91
import org.netbeans.api.editor.settings.FontColorSettings;
92
import org.netbeans.api.editor.settings.SimpleValueNames;
93
import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
94
import org.netbeans.modules.editor.lib.SettingsConversions;
95
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
96
import org.netbeans.modules.editor.lib2.view.ParagraphViewDescriptor;
97
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
98
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
99
import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
100
import org.openide.util.Lookup;
101
import org.openide.util.LookupEvent;
102
import org.openide.util.LookupListener;
103
import org.openide.util.NbBundle;
104
import org.openide.util.WeakListeners;
105
106
/**
107
 *  Code Folding Side Bar. Component responsible for drawing folding signs and responding 
108
 *  on user fold/unfold action.
109
 *
110
 *  @author  Martin Roskanin
111
 */
112
public class CodeFoldingSideBar extends JComponent implements Accessible {
113
114
    private static final Logger LOG = Logger.getLogger(CodeFoldingSideBar.class.getName());
115
116
    /** This field should be treated as final. Subclasses are forbidden to change it. 
117
     * @deprecated Without any replacement.
118
     */
119
    protected Color backColor;
120
    /** This field should be treated as final. Subclasses are forbidden to change it. 
121
     * @deprecated Without any replacement.
122
     */
123
    protected Color foreColor;
124
    /** This field should be treated as final. Subclasses are forbidden to change it. 
125
     * @deprecated Without any replacement.
126
     */
127
    protected Font font;
128
    
129
    /** This field should be treated as final. Subclasses are forbidden to change it. */
130
    protected /*final*/ JTextComponent component;
131
    private volatile AttributeSet attribs;
132
    private Lookup.Result<? extends FontColorSettings> fcsLookupResult;
133
    private final LookupListener fcsTracker = new LookupListener() {
134
        public void resultChanged(LookupEvent ev) {
135
            attribs = null;
136
            SwingUtilities.invokeLater(new Runnable() {
137
                public void run() {
138
                    //EMI: This is needed as maybe the DEFAULT_COLORING is changed, the font is different
139
                    // and while getMarkSize() is used in paint() and will make the artifacts bigger,
140
                    // the component itself will be the same size and it must be changed.
141
                    // See http://www.netbeans.org/issues/show_bug.cgi?id=153316
142
                    updatePreferredSize();
143
                    CodeFoldingSideBar.this.repaint();
144
                }
145
            });
146
        }
147
    };
148
    private final Listener listener = new Listener();
149
    
150
    private boolean enabled = false;
151
    
152
    protected List<Mark> visibleMarks = new ArrayList<Mark>();
153
    
154
    /**
155
     * Mouse moved point, possibly {@code null}. Set from mouse-moved, mouse-entered
156
     * handlers, so that painting will paint this fold in bold. -1, if mouse is not
157
     * in the sidebar region. The value is used to compute highlighted portions of the 
158
     * folding outline.
159
     */
160
    private int   mousePoint = -1;
161
    
162
    /**
163
     * if true, the {@link #mousePoint} has been already used to make a PaintInfo active.
164
     * The flag is tested by {@link #traverseForward} and {@link #traverseBackward} after children
165
     * of the current fold are processed and cleared if the {@link #mousePoint} falls to the fold area -
166
     * fields of PaintInfo are set accordingly.
167
     * It's also used to compute (current) mouseBoundary, so mouse movement does not trigger 
168
     * refreshes eagerly
169
     */
170
    private boolean mousePointConsumed;
171
    
172
    /**
173
     * Boundaries of the current area under the mouse. Can be eiher the span of the
174
     * current fold (or part of it), or the span not occupied by any fold. Serves as an optimization
175
     * for mouse handler, which does not trigger painting (refresh) unless mouse 
176
     * leaves this region.
177
     */
178
    private Rectangle   mouseBoundary;
179
    
180
    /**
181
     * Y-end of the nearest fold that ends above the {@link #mousePoint}. Undefined if mousePoint is null.
182
     * These two variables are initialized at each level of folds, and help to compute {@link #mouseBoundary} for
183
     * the case the mousePointer is OUTSIDE all children (or outside all folds). 
184
     */
185
    private int lowestAboveMouse = -1;
186
187
    /**
188
     * Y-begin of the nearest fold, which starts below the {@link #mousePoint}. Undefined if mousePoint is null
189
     */
190
    private int topmostBelowMouse = Integer.MAX_VALUE;
191
    
192
    /** Paint operations */
193
    public static final int PAINT_NOOP             = 0;
194
    /**
195
     * Normal opening +- marker
196
     */
197
    public static final int PAINT_MARK             = 1;
198
    
199
    /**
200
     * Vertical line - typically at the end of the screen
201
     */
202
    public static final int PAINT_LINE             = 2;
203
    
204
    /**
205
     * End angled line, without a sign
206
     */
207
    public static final int PAINT_END_MARK         = 3;
208
    
209
    /**
210
     * Single-line marker, both start and end
211
     */
212
    public static final int SINGLE_PAINT_MARK      = 4;
213
    
214
    /**
215
     * Marker value for {@link #mousePoint} indicating that mouse is outside the Component.
216
     */
217
    private static final int NO_MOUSE_POINT = -1;
218
    
219
    /**
220
     * Stroke used to draw inactive (regular) fold outlines.
221
     */
222
    private static Stroke LINE_DASHED = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 
223
            1f, new float[] { 1f, 1f }, 0f);
224
    
225
    /**
226
     * Stroke used to draw outlines for 'active' fold
227
     */
228
    private static final Stroke LINE_BOLD = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);
229
    
230
    private final Preferences prefs;
231
    private final PreferenceChangeListener prefsListener = new PreferenceChangeListener() {
232
        public void preferenceChange(PreferenceChangeEvent evt) {
233
            String key = evt == null ? null : evt.getKey();
234
            if (key == null || SimpleValueNames.CODE_FOLDING_ENABLE.equals(key)) {
235
                updateColors();
236
                
237
                boolean newEnabled = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, EditorPreferencesDefaults.defaultCodeFoldingEnable);
238
                if (enabled != newEnabled) {
239
                    enabled = newEnabled;
240
                    updatePreferredSize();
241
                }
242
            }
243
            SettingsConversions.callSettingsChange(CodeFoldingSideBar.this);
244
        }
245
    };
246
    
247
    private void checkRepaint(ViewHierarchyEvent vhe) {
248
        if (!vhe.isChangeY()) {
249
            // does not obscur sidebar graphics
250
            return;
251
        }
252
        
253
        SwingUtilities.invokeLater(new Runnable() {
254
            public void run() {
255
                updatePreferredSize();
256
                CodeFoldingSideBar.this.repaint();
257
            }
258
        });
259
    }
260
    
261
    /**
262
     * @deprecated Don't use this constructor, it does nothing!
263
     */
264
    public CodeFoldingSideBar() {
265
        component = null;
266
        prefs = null;
267
        throw new IllegalStateException("Do not use this constructor!"); //NOI18N
268
    }
269
270
    public CodeFoldingSideBar(JTextComponent component){
271
        super();
272
        this.component = component;
273
274
        addMouseListener(listener);
275
        addMouseMotionListener(listener);
276
277
        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
278
        foldHierarchy.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, listener, foldHierarchy));
279
280
        Document doc = getDocument();
281
        doc.addDocumentListener(WeakListeners.document(listener, doc));
282
        setOpaque(true);
283
        
284
        prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
285
        prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, prefsListener, prefs));
286
        prefsListener.preferenceChange(null);
287
        
288
        ViewHierarchy.get(component).addViewHierarchyListener(new ViewHierarchyListener() {
289
290
            @Override
291
            public void viewHierarchyChanged(ViewHierarchyEvent evt) {
292
                checkRepaint(evt);
293
            }
294
            
295
        });
296
    }
297
    
298
    private void updatePreferredSize() {
299
        if (enabled) {
300
            setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
301
            setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
302
        }else{
303
            setPreferredSize(new Dimension(0,0));
304
            setMaximumSize(new Dimension(0,0));
305
        }
306
        revalidate();
307
    }
308
309
    private void updateColors() {
310
        Coloring c = getColoring();
311
        this.backColor = c.getBackColor();
312
        this.foreColor = c.getForeColor();
313
        this.font = c.getFont();
314
    }
315
316
    /**
317
     * This method should be treated as final. Subclasses are forbidden to override it.
318
     * @return The background color used for painting this component.
319
     * @deprecated Without any replacement.
320
     */
321
    protected Color getBackColor() {
322
        if (backColor == null) {
323
            updateColors();
324
        }
325
        return backColor;
326
    }
327
    
328
    /**
329
     * This method should be treated as final. Subclasses are forbidden to override it.
330
     * @return The foreground color used for painting this component.
331
     * @deprecated Without any replacement.
332
     */
333
    protected Color getForeColor() {
334
        if (foreColor == null) {
335
            updateColors();
336
        }
337
        return foreColor;
338
    }
339
    
340
    /**
341
     * This method should be treated as final. Subclasses are forbidden to override it.
342
     * @return The font used for painting this component.
343
     * @