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 226413
Collapse All | Expand All

(-)a/csl.api/nbproject/project.properties (-1 / +1 lines)
Lines 40-46 Link Here
40
# Version 2 license, then the option applies only if the new code is
40
# Version 2 license, then the option applies only if the new code is
41
# made subject to such option by the copyright holder.
41
# made subject to such option by the copyright holder.
42
42
43
spec.version.base=2.34.0
43
spec.version.base=2.35.0
44
is.autoload=true
44
is.autoload=true
45
javac.source=1.6
45
javac.source=1.6
46
46
(-)a/csl.api/nbproject/project.xml (-1 / +1 lines)
Lines 119-125 Link Here
119
                    <compile-dependency/>
119
                    <compile-dependency/>
120
                    <run-dependency>
120
                    <run-dependency>
121
                        <release-version>1</release-version>
121
                        <release-version>1</release-version>
122
                        <specification-version>1.6</specification-version>
122
                        <specification-version>1.34</specification-version>
123
                    </run-dependency>
123
                    </run-dependency>
124
                </dependency>
124
                </dependency>
125
                <dependency>
125
                <dependency>
(-)a/csl.api/src/org/netbeans/modules/csl/api/StructureScanner.java (-1 / +15 lines)
Lines 50-55 Link Here
50
import org.netbeans.api.annotations.common.CheckForNull;
50
import org.netbeans.api.annotations.common.CheckForNull;
51
import org.netbeans.api.annotations.common.NonNull;
51
import org.netbeans.api.annotations.common.NonNull;
52
import org.netbeans.modules.csl.spi.ParserResult;
52
import org.netbeans.modules.csl.spi.ParserResult;
53
import org.netbeans.api.editor.fold.FoldType;
54
import org.netbeans.spi.editor.fold.FoldTypeProvider;
53
55
54
/**
56
/**
55
 * Given a parse tree, scan its structure and produce a flat list of
57
 * Given a parse tree, scan its structure and produce a flat list of
Lines 69-76 Link Here
69
    @NonNull List<? extends StructureItem> scan(@NonNull ParserResult info);
71
    @NonNull List<? extends StructureItem> scan(@NonNull ParserResult info);
70
    
72
    
71
    /**
73
    /**
72
     * @todo Do this in the same pass as the structure scan?
73
     * Compute a list of foldable regions, named "codeblocks", "comments", "imports", "initial-comment", ...
74
     * Compute a list of foldable regions, named "codeblocks", "comments", "imports", "initial-comment", ...
75
     * The returned Map must be keyed by {@link FoldType#code}. For backward compatibility, the following
76
     * tokens are temporarily supported although no FoldType is registered explicitly.
77
     * <ul>
78
     * <li>codeblocks
79
     * <li>comments
80
     * <li>initial-comment
81
     * <li>imports
82
     * <li>tags
83
     * <li>inner-classes
84
     * <li>othercodeblocks
85
     * </ul>
86
     * This additional support will cease to exist after NB-8.0. Language owners are required to register
87
     * their {@link FoldTypeProvider} and define their own folding.
74
     */
88
     */
75
    @NonNull Map<String,List<OffsetRange>> folds(@NonNull ParserResult info);
89
    @NonNull Map<String,List<OffsetRange>> folds(@NonNull ParserResult info);
76
90
(-)a/csl.api/src/org/netbeans/modules/csl/editor/fold/GsfFoldManager.java (-338 / +185 lines)
Lines 51-59 Link Here
51
import java.util.Collection;
51
import java.util.Collection;
52
import java.util.Collections;
52
import java.util.Collections;
53
import java.util.HashMap;
53
import java.util.HashMap;
54
import java.util.HashSet;
54
import java.util.Iterator;
55
import java.util.Iterator;
55
import java.util.List;
56
import java.util.List;
56
import java.util.Map;
57
import java.util.Map;
58
import java.util.Set;
57
import java.util.TreeMap;
59
import java.util.TreeMap;
58
import java.util.TreeSet;
60
import java.util.TreeSet;
59
import java.util.WeakHashMap;
61
import java.util.WeakHashMap;
Lines 65-73 Link Here
65
import javax.swing.event.DocumentEvent;
67
import javax.swing.event.DocumentEvent;
66
import javax.swing.text.BadLocationException;
68
import javax.swing.text.BadLocationException;
67
import javax.swing.text.Document;
69
import javax.swing.text.Document;
68
import javax.swing.text.Position;
69
import org.netbeans.api.editor.fold.Fold;
70
import org.netbeans.api.editor.fold.Fold;
71
import org.netbeans.spi.editor.fold.FoldInfo;
72
import org.netbeans.api.editor.fold.FoldTemplate;
70
import org.netbeans.api.editor.fold.FoldType;
73
import org.netbeans.api.editor.fold.FoldType;
74
import org.netbeans.api.editor.fold.FoldUtilities;
71
import org.netbeans.api.editor.mimelookup.MimeLookup;
75
import org.netbeans.api.editor.mimelookup.MimeLookup;
72
import org.netbeans.modules.csl.api.OffsetRange;
76
import org.netbeans.modules.csl.api.OffsetRange;
73
import org.netbeans.modules.csl.api.Severity;
77
import org.netbeans.modules.csl.api.Severity;
Lines 92-98 Link Here
92
import org.netbeans.modules.parsing.api.Source;
96
import org.netbeans.modules.parsing.api.Source;
93
import org.netbeans.modules.parsing.api.UserTask;
97
import org.netbeans.modules.parsing.api.UserTask;
94
import org.netbeans.modules.parsing.spi.*;
98
import org.netbeans.modules.parsing.spi.*;
95
import org.openide.text.NbDocument;
99
import org.openide.util.NbBundle;
100
101
import static org.netbeans.modules.csl.editor.fold.Bundle.*;
96
102
97
/**
103
/**
98
 * This file is originally from Retouche, the Java Support 
104
 * This file is originally from Retouche, the Java Support 
Lines 100-106 Link Here
100
 * as possible to make merging Retouche fixes back as simple as
106
 * as possible to make merging Retouche fixes back as simple as
101
 * possible. 
107
 * possible. 
102
 * 
108
 * 
103
 * Copied from both JavaFoldManager and JavaElementFoldManager
109
 * Copied from both JavaFoldManager and JavaElementFoldManager.
110
 * <p/>
111
 * 
112
 * From introduction of {@link FoldType}, this Manager accepts these Strings
113
 * from {@link StructureScanner#folds}:<ul>
114
 * <li>codes of FoldTypes registered for the language, or
115
 * <li>legacy FoldType codes.
116
 * </ul>
117
 * The registered FoldTypes take precedence; that is if the SPI returns
118
 * "code-block" and a FoldType is registered with that code, that FoldType (and its FoldTemplate)
119
 * will be used to create the Fold. If no such FoldType exists, the GsfFoldManager will still
120
 * create the fold in a bw compatible mode with an <b>unregistered</b> fold type. But because
121
 * of implementation peculiarities, the FoldType will eventually default to one of the generic
122
 * settings.
123
 * <p/>
124
 * If a code other than registered FoldTypes and bw compatible constants is used, 
125
 * a warning will be printed - to detect programmer errors.
104
 * 
126
 * 
105
 *
127
 *
106
 * @author Jan Lahoda
128
 * @author Jan Lahoda
Lines 110-238 Link Here
110
132
111
    private static final Logger LOG = Logger.getLogger(GsfFoldManager.class.getName());
133
    private static final Logger LOG = Logger.getLogger(GsfFoldManager.class.getName());
112
    
134
    
113
    public static final FoldType CODE_BLOCK_FOLD_TYPE = new FoldType("code-block"); // NOI18N
135
    private static final FoldTemplate TEMPLATE_CODEBLOCK = new org.netbeans.api.editor.fold.FoldTemplate(1, 1, "{...}"); // NOI18N
114
    public static final FoldType INITIAL_COMMENT_FOLD_TYPE = new FoldType("initial-comment"); // NOI18N
136
   
115
    public static final FoldType IMPORTS_FOLD_TYPE = new FoldType("imports"); // NOI18N
137
    /**
116
    public static final FoldType JAVADOC_FOLD_TYPE = new FoldType("javadoc"); // NOI18N
138
     * This definition and all Fold types defined in CSL are deprecated and for backward
117
    public static final FoldType TAG_FOLD_TYPE = new FoldType("tag"); // NOI18N
139
     * compatibility only. 
118
    public static final FoldType INNER_CLASS_FOLD_TYPE = new FoldType("inner-class"); // NOI18N
140
     */
119
141
    @Deprecated
142
    public static final FoldType CODE_BLOCK_FOLD_TYPE = FoldType.CODE_BLOCK;
120
    
143
    
121
    private static final String IMPORTS_FOLD_DESCRIPTION = "..."; // NOI18N
144
    @Deprecated
122
145
    public static final FoldType INITIAL_COMMENT_FOLD_TYPE = FoldType.INITIAL_COMMENT;
123
    private static final String COMMENT_FOLD_DESCRIPTION = "..."; // NOI18N
124
125
    private static final String JAVADOC_FOLD_DESCRIPTION = "..."; // NOI18N
126
    
127
    private static final String CODE_BLOCK_FOLD_DESCRIPTION = "{...}"; // NOI18N
128
129
    private static final String TAG_FOLD_DESCRIPTION = "<.../>"; // NOI18N
130
131
132
    public static final FoldTemplate CODE_BLOCK_FOLD_TEMPLATE
133
        = new FoldTemplate(CODE_BLOCK_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
134
    
135
    public static final FoldTemplate INITIAL_COMMENT_FOLD_TEMPLATE
136
        = new FoldTemplate(INITIAL_COMMENT_FOLD_TYPE, COMMENT_FOLD_DESCRIPTION, 2, 2);
137
138
    public static final FoldTemplate IMPORTS_FOLD_TEMPLATE
139
        = new FoldTemplate(IMPORTS_FOLD_TYPE, IMPORTS_FOLD_DESCRIPTION, 0, 0);
140
141
    public static final FoldTemplate JAVADOC_FOLD_TEMPLATE
142
        = new FoldTemplate(JAVADOC_FOLD_TYPE, JAVADOC_FOLD_DESCRIPTION, 3, 2);
143
144
    public static final FoldTemplate TAG_FOLD_TEMPLATE
145
        = new FoldTemplate(TAG_FOLD_TYPE, TAG_FOLD_DESCRIPTION, 0, 0);
146
147
    public static final FoldTemplate INNER_CLASS_FOLD_TEMPLATE
148
        = new FoldTemplate(INNER_CLASS_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 0, 0);
149
150
    
151
    public static final String CODE_FOLDING_ENABLE = "code-folding-enable"; //NOI18N
152
    
153
    /** Collapse methods by default
154
     * NOTE: This must be kept in sync with string literal in editor/options
155
     */
156
    public static final String CODE_FOLDING_COLLAPSE_METHOD = "code-folding-collapse-method"; //NOI18N
157
    
146
    
158
    /**
147
    /**
159
     * Collapse inner classes by default 
148
     * Note: this FoldType's code was changed from 'imports' to 'import' to match the used preference key.
160
     * NOTE: This must be kept in sync with string literal in editor/options
161
     */
149
     */
162
    public static final String CODE_FOLDING_COLLAPSE_INNERCLASS = "code-folding-collapse-innerclass"; //NOI18N
150
    @NbBundle.Messages("FT_label_imports=Imports or Includes")
151
    @Deprecated
152
    public static final FoldType IMPORTS_FOLD_TYPE = FoldType.IMPORT;
153
154
    @Deprecated
155
    public static final FoldType JAVADOC_FOLD_TYPE = FoldType.DOCUMENTATION;
156
    
157
    @Deprecated
158
    public static final FoldType TAG_FOLD_TYPE = FoldType.TAG;
163
    
159
    
164
    /**
160
    /**
165
     * Collapse import section default
161
     * This type's code was renamed from inner-class to innerclass to match the preference value
166
     * NOTE: This must be kept in sync with string literal in editor/options
167
     */
162
     */
168
    public static final String CODE_FOLDING_COLLAPSE_IMPORT = "code-folding-collapse-import"; //NOI18N
163
    @NbBundle.Messages("FT_label_innerclass=Inner Classes")
164
    @Deprecated
165
    public static final FoldType INNER_CLASS_FOLD_TYPE = FoldType.create("innerclass", FT_label_innerclass(), TEMPLATE_CODEBLOCK);
169
    
166
    
170
    /**
167
    @NbBundle.Messages("FT_label_othercodeblocks=Other code blocks")
171
     * Collapse javadoc comment by default
168
    @Deprecated
172
     * NOTE: This must be kept in sync with string literal in editor/options
169
    public static final FoldType OTHER_CODEBLOCKS_FOLD_TYPE = FoldType.TAG.derive("othercodeblocks", FT_label_othercodeblocks(), 
173
     */
170
            TEMPLATE_CODEBLOCK);
174
    public static final String CODE_FOLDING_COLLAPSE_JAVADOC = "code-folding-collapse-javadoc"; //NOI18N
175
176
    /**
177
     * Collapse initial comment by default
178
     * NOTE: This must be kept in sync with string literal in editor/options
179
     */
180
    public static final String CODE_FOLDING_COLLAPSE_INITIAL_COMMENT = "code-folding-collapse-initial-comment"; //NOI18N
181
    
171
    
182
     /**
172
    private static final Set<String> LEGACY_FOLD_TAGS = new HashSet<String>(11);
183
     * Collapse tags by default
184
     * NOTE: This must be kept in sync with string literal in editor/options
185
     */
186
    public static final String CODE_FOLDING_COLLAPSE_TAGS = "code-folding-collapse-tags"; //NOI18N
187
188
    
173
    
189
    protected static final class FoldTemplate {
174
    static {
190
175
        LEGACY_FOLD_TAGS.add(OTHER_CODEBLOCKS_FOLD_TYPE.code());
191
        private FoldType type;
176
        LEGACY_FOLD_TAGS.add(INNER_CLASS_FOLD_TYPE.code());
192
177
        LEGACY_FOLD_TAGS.add(TAG_FOLD_TYPE.code());
193
        private String description;
178
        LEGACY_FOLD_TAGS.add(JAVADOC_FOLD_TYPE.code());
194
179
        LEGACY_FOLD_TAGS.add(IMPORTS_FOLD_TYPE.code());
195
        private int startGuardedLength;
180
        LEGACY_FOLD_TAGS.add(INITIAL_COMMENT_FOLD_TYPE.code());
196
181
        LEGACY_FOLD_TAGS.add(CODE_BLOCK_FOLD_TYPE.code());
197
        private int endGuardedLength;
198
199
        protected FoldTemplate(FoldType type, String description,
200
        int startGuardedLength, int endGuardedLength) {
201
            this.type = type;
202
            this.description = description;
203
            this.startGuardedLength = startGuardedLength;
204
            this.endGuardedLength = endGuardedLength;
205
        }
206
207
        public FoldType getType() {
208
            return type;
209
        }
210
211
        public String getDescription() {
212
            return description;
213
        }
214
215
        public int getStartGuardedLength() {
216
            return startGuardedLength;
217
        }
218
219
        public int getEndGuardedLength() {
220
            return endGuardedLength;
221
        }
222
223
    }
182
    }
224
    
183
    
225
    private FoldOperation operation;
184
    private FoldOperation operation;
226
    private FileObject    file;
185
    private FileObject    file;
227
    private JavaElementFoldTask task;
186
    private JavaElementFoldTask task;
228
    
187
    
229
//    // Folding presets
188
    private volatile Preferences prefs;
230
//    private boolean foldImportsPreset;
231
//    private boolean foldInnerClassesPreset;
232
//    private boolean foldJavadocsPreset;
233
//    private boolean foldCodeBlocksPreset;
234
//    private boolean foldInitialCommentsPreset;
235
    private static volatile Preferences prefs;
236
    
189
    
237
    /** Creates a new instance of GsfFoldManager */
190
    /** Creates a new instance of GsfFoldManager */
238
    public GsfFoldManager() {
191
    public GsfFoldManager() {
Lines 253-259 Link Here
253
        file = DataLoadersBridge.getDefault().getFileObject(doc);
206
        file = DataLoadersBridge.getDefault().getFileObject(doc);
254
        
207
        
255
        if (file != null) {
208
        if (file != null) {
256
            currentFolds = new HashMap<FoldInfo, Fold>();
257
            task = JavaElementFoldTask.getTask(file);
209
            task = JavaElementFoldTask.getTask(file);
258
            task.setGsfFoldManager(GsfFoldManager.this, file);
210
            task.setGsfFoldManager(GsfFoldManager.this, file);
259
        }
211
        }
Lines 278-284 Link Here
278
230
279
    @Override
231
    @Override
280
    public void removeDamagedNotify(Fold damagedFold) {
232
    public void removeDamagedNotify(Fold damagedFold) {
281
        currentFolds.remove(operation.getExtraInfo(damagedFold));
282
        if (importsFold == damagedFold) {
233
        if (importsFold == damagedFold) {
283
            importsFold = null;//not sure if this is correct...
234
            importsFold = null;//not sure if this is correct...
284
        }
235
        }
Lines 297-324 Link Here
297
        
248
        
298
        task         = null;
249
        task         = null;
299
        file         = null;
250
        file         = null;
300
        currentFolds = null;
301
        importsFold  = null;
251
        importsFold  = null;
302
        initialCommentFold = null;
252
        initialCommentFold = null;
303
    }
253
    }
304
    
254
    
305
//    public void settingsChange(SettingsChangeEvent evt) {
306
//        // Get folding presets
307
//        foldInitialCommentsPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_INITIAL_COMMENT);
308
//        foldImportsPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_IMPORT);
309
//        foldCodeBlocksPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_METHOD);
310
//        foldInnerClassesPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_INNERCLASS);
311
//        foldJavadocsPreset = getSetting(GsfOptions.CODE_FOLDING_COLLAPSE_JAVADOC);
312
//    }
313
    
314
    private static boolean getSetting(String settingName) {
315
        return prefs.getBoolean(settingName, false);
316
    }
317
    
318
    static final class JavaElementFoldTask extends IndexingAwareParserResultTask<ParserResult> {
255
    static final class JavaElementFoldTask extends IndexingAwareParserResultTask<ParserResult> {
319
256
320
        private final AtomicBoolean cancelled = new AtomicBoolean(false);
257
        private final AtomicBoolean cancelled = new AtomicBoolean(false);
321
        
258
        
259
        private FoldInfo initComment;
260
        
261
        private FoldInfo imports;
262
        
322
        public JavaElementFoldTask() {
263
        public JavaElementFoldTask() {
323
            super(TaskIndexingMode.ALLOWED_DURING_SCAN);
264
            super(TaskIndexingMode.ALLOWED_DURING_SCAN);
324
        }
265
        }
Lines 397-403 Link Here
397
                return;
338
                return;
398
            }
339
            }
399
340
400
            final TreeSet<FoldInfo> folds = new TreeSet<FoldInfo>();
341
            final Collection<FoldInfo> folds = new HashSet<FoldInfo>();
401
            final Document doc = info.getSnapshot().getSource().getDocument(false);
342
            final Document doc = info.getSnapshot().getSource().getDocument(false);
402
            if (doc == null) {
343
            if (doc == null) {
403
                return;
344
                return;
Lines 408-420 Link Here
408
            }
349
            }
409
            
350
            
410
            if (mgrs instanceof GsfFoldManager) {
351
            if (mgrs instanceof GsfFoldManager) {
411
                SwingUtilities.invokeLater(((GsfFoldManager)mgrs).new CommitFolds(folds, doc, info.getSnapshot().getSource()));
352
                SwingUtilities.invokeLater(((GsfFoldManager)mgrs).createCommit(
353
                        folds, 
354
                        initComment, imports, doc, info.getSnapshot().getSource()));
412
            } else {
355
            } else {
413
                SwingUtilities.invokeLater(new Runnable() {
356
                SwingUtilities.invokeLater(new Runnable() {
414
                    Collection<GsfFoldManager> jefms = (Collection<GsfFoldManager>)mgrs;
357
                    Collection<GsfFoldManager> jefms = (Collection<GsfFoldManager>)mgrs;
415
                    public void run() {
358
                    public void run() {
416
                        for (GsfFoldManager jefm : jefms) {
359
                        for (GsfFoldManager jefm : jefms) {
417
                            jefm.new CommitFolds(folds, doc, info.getSnapshot().getSource()).run();
360
                            jefm.createCommit(folds, initComment, imports, 
361
                                    doc, info.getSnapshot().getSource()).run();
418
                        }
362
                        }
419
                }});
363
                }});
420
            }
364
            }
Lines 430-436 Link Here
430
         * 
374
         * 
431
         * @return true If folds were found, false if cancelled
375
         * @return true If folds were found, false if cancelled
432
         */
376
         */
433
        private boolean gsfFoldScan(final Document doc, ParserResult info, final TreeSet<FoldInfo> folds) {
377
        private boolean gsfFoldScan(final Document doc, ParserResult info, final Collection<FoldInfo> folds) {
434
            final boolean [] success = new boolean [] { false };
378
            final boolean [] success = new boolean [] { false };
435
            Source source = info.getSnapshot().getSource();
379
            Source source = info.getSnapshot().getSource();
436
380
Lines 482-488 Link Here
482
            return success[0];
426
            return success[0];
483
        }
427
        }
484
428
485
        private boolean checkInitialFold(final Document doc, final TreeSet<FoldInfo> folds) {
429
        private boolean checkInitialFold(final Document doc, final Collection<FoldInfo> folds) {
486
            final boolean[] ret = new boolean[1];
430
            final boolean[] ret = new boolean[1];
487
            ret[0] = true;
431
            ret[0] = true;
488
            final TokenHierarchy<?> th = TokenHierarchy.get(doc);
432
            final TokenHierarchy<?> th = TokenHierarchy.get(doc);
Lines 492-543 Link Here
492
            doc.render(new Runnable() {
436
            doc.render(new Runnable() {
493
                @Override
437
                @Override
494
                public void run() {
438
                public void run() {
495
                    try {
439
                    TokenSequence<?> ts = th.tokenSequence();
496
                        TokenSequence<?> ts = th.tokenSequence();
440
                    if (ts == null) {
497
                        if (ts == null) {
441
                        return;
442
                    }
443
                    while (ts.moveNext()) {
444
                        Token<?> token = ts.token();
445
                        String category = token.id().primaryCategory();
446
                        if ("comment".equals(category)) { // NOI18N
447
                            int startOffset = ts.offset();
448
                            int endOffset = startOffset + token.length();
449
450
                            // Find end - could be a block of single-line statements
451
                            while (ts.moveNext()) {
452
                                token = ts.token();
453
                                category = token.id().primaryCategory();
454
                                if ("comment".equals(category)) { // NOI18N
455
                                    endOffset = ts.offset() + token.length();
456
                                } else if (!"whitespace".equals(category)) { // NOI18N
457
                                    break;
458
                                }
459
                            }
460
461
                            try {
462
                                // Start the fold at the END of the line
463
                                startOffset = org.netbeans.editor.Utilities.getRowEnd((BaseDocument) doc, startOffset);
464
                                if (startOffset >= endOffset) {
465
                                    return;
466
                                }
467
                            } catch (BadLocationException ex) {
468
                                LOG.log(Level.WARNING, null, ex);
469
                            }
470
471
                            folds.add(initComment = FoldInfo.range(startOffset, endOffset, INITIAL_COMMENT_FOLD_TYPE));
498
                            return;
472
                            return;
499
                        }
473
                        }
500
                        while (ts.moveNext()) {
474
                        if (!"whitespace".equals(category)) { // NOI18N
501
                            Token<?> token = ts.token();
475
                            break;
502
                            String category = token.id().primaryCategory();
503
                            if ("comment".equals(category)) { // NOI18N
504
                                int startOffset = ts.offset();
505
                                int endOffset = startOffset + token.length();
506
                                boolean collapsed = getSetting(CODE_FOLDING_COLLAPSE_INITIAL_COMMENT); //foldInitialCommentsPreset;
507
508
509
                                // Find end - could be a block of single-line statements
510
511
                                while (ts.moveNext()) {
512
                                    token = ts.token();
513
                                    category = token.id().primaryCategory();
514
                                    if ("comment".equals(category)) { // NOI18N
515
                                        endOffset = ts.offset() + token.length();
516
                                    } else if (!"whitespace".equals(category)) { // NOI18N
517
                                        break;
518
                                    }
519
                                }
520
521
                                try {
522
                                    // Start the fold at the END of the line
523
                                    startOffset = org.netbeans.editor.Utilities.getRowEnd((BaseDocument) doc, startOffset);
524
                                    if (startOffset >= endOffset) {
525
                                        return;
526
                                    }
527
                                } catch (BadLocationException ex) {
528
                                    LOG.log(Level.WARNING, null, ex);
529
                                }
530
531
                                folds.add(new FoldInfo(doc, startOffset, endOffset, INITIAL_COMMENT_FOLD_TEMPLATE, collapsed));
532
                                return;
533
                            }
534
                            if (!"whitespace".equals(category)) { // NOI18N
535
                                break;
536
                            }
537
                            
538
                        }
476
                        }
539
                    } catch (BadLocationException e) {
540
                        ret[0] = false;
541
                    }
477
                    }
542
                }
478
                }
543
            });
479
            });
Lines 545-551 Link Here
545
        }
481
        }
546
        
482
        
547
        private void scan(final ParserResult info,
483
        private void scan(final ParserResult info,
548
            final TreeSet<FoldInfo> folds, final Document doc, final
484
            final Collection<FoldInfo> folds, final Document doc, final
549
            StructureScanner scanner) {
485
            StructureScanner scanner) {
550
            
486
            
551
            doc.render(new Runnable() {
487
            doc.render(new Runnable() {
Lines 556-623 Link Here
556
            });
492
            });
557
        }
493
        }
558
        
494
        
559
        private void addFoldsOfType(
495
        private boolean addFoldsOfType(
560
                    StructureScanner scanner,
561
                    String type, Map<String,List<OffsetRange>> folds,
496
                    String type, Map<String,List<OffsetRange>> folds,
562
                    TreeSet<FoldInfo> result,
497
                    Collection<FoldInfo> result,
563
                    Document doc, 
498
                    Document doc, 
564
                    String collapsedOptionName,
499
                    FoldType foldType) {
565
                    FoldTemplate template) {
566
            
500
            
567
            List<OffsetRange> ranges = folds.get(type); //NOI18N
501
            List<OffsetRange> ranges = folds.get(type); //NOI18N
568
            if (ranges != null) {
502
            if (ranges != null) {
569
                boolean collapseByDefault = getSetting(collapsedOptionName);
570
                if (LOG.isLoggable(Level.FINEST)) {
503
                if (LOG.isLoggable(Level.FINEST)) {
571
                    LOG.log(Level.FINEST, "Creating folds {0}, collapsed: {1}", new Object[] {
504
                    LOG.log(Level.FINEST, "Creating folds {0}", new Object[] {
572
                        type, collapseByDefault
505
                        type
573
                    });
506
                    });
574
                }
507
                }
575
                for (OffsetRange range : ranges) {
508
                for (OffsetRange range : ranges) {
576
                    try {
509
                    if (LOG.isLoggable(Level.FINEST)) {
577
                        if (LOG.isLoggable(Level.FINEST)) {
510
                        LOG.log(Level.FINEST, "Fold: {0}", range);
578
                            LOG.log(Level.FINEST, "Fold: {0}", range);
579
                        }
580
                        addFold(range, result, doc, collapseByDefault, template);
581
                    } catch (BadLocationException ble) {
582
                        LOG.log(Level.WARNING, "StructureScanner " + scanner + " supplied invalid fold " + range, ble); //NOI18N
583
                    }
511
                    }
512
                    addFold(range, result, doc, foldType);
584
                }
513
                }
514
                folds.remove(type);
515
                return true;
585
            } else {
516
            } else {
586
                LOG.log(Level.FINEST, "No folds of type {0}", type);
517
                LOG.log(Level.FINEST, "No folds of type {0}", type);
518
                return false;
587
            }
519
            }
588
        }
520
        }
589
        
521
        
590
        private void addTree(TreeSet<FoldInfo> result, ParserResult info, Document doc, StructureScanner scanner) {
522
        private void addTree(Collection<FoldInfo> result, ParserResult info, Document doc, StructureScanner scanner) {
591
            // #217322, disabled folding -> no folds will be created
523
            // #217322, disabled folding -> no folds will be created
592
            if (!getSetting(CODE_FOLDING_ENABLE)) {
524
            String mime = info.getSnapshot().getMimeType();
525
            
526
            if (!FoldUtilities.isFoldingEnabled(mime)) {
593
                return;
527
                return;
594
            }
528
            }
595
            Map<String,List<OffsetRange>> folds = scanner.folds(info);
529
            Map<String,List<OffsetRange>> folds = scanner.folds(info);
596
            if (cancelled.get()) {
530
            if (cancelled.get()) {
597
                return;
531
                return;
598
            }
532
            }
599
            addFoldsOfType(scanner, "codeblocks", folds, result, doc, 
533
            Collection<? extends FoldType> ftypes = FoldUtilities.getFoldTypes(mime).values();
600
                    CODE_FOLDING_COLLAPSE_METHOD, CODE_BLOCK_FOLD_TEMPLATE);
534
            for (FoldType ft : ftypes) {
601
            addFoldsOfType(scanner, "comments", folds, result, doc, 
535
                addFoldsOfType(ft.code(), folds, result, doc, ft);
602
                    CODE_FOLDING_COLLAPSE_JAVADOC, JAVADOC_FOLD_TEMPLATE);
536
            }
603
            addFoldsOfType(scanner, "initial-comment", folds, result, doc, 
537
            // fallback: transform the legacy keys into FoldInfos
604
                    CODE_FOLDING_COLLAPSE_INITIAL_COMMENT, INITIAL_COMMENT_FOLD_TEMPLATE);
538
            addFoldsOfType("codeblocks", folds, result, doc, 
605
            addFoldsOfType(scanner, "imports", folds, result, doc, 
539
                    CODE_BLOCK_FOLD_TYPE);
606
                    CODE_FOLDING_COLLAPSE_IMPORT, IMPORTS_FOLD_TEMPLATE);
540
            addFoldsOfType("comments", folds, result, doc, 
607
            addFoldsOfType(scanner, "tags", folds, result, doc, 
541
                    JAVADOC_FOLD_TYPE);
608
                    CODE_FOLDING_COLLAPSE_TAGS, TAG_FOLD_TEMPLATE);
542
            addFoldsOfType("initial-comment", folds, result, doc, 
609
            addFoldsOfType(scanner, "othercodeblocks", folds, result, doc, 
543
                    INITIAL_COMMENT_FOLD_TYPE);
610
                    CODE_FOLDING_COLLAPSE_TAGS, CODE_BLOCK_FOLD_TEMPLATE);
544
            addFoldsOfType("imports", folds, result, doc, 
611
            addFoldsOfType(scanner, "inner-classes", folds, result, doc, 
545
                    IMPORTS_FOLD_TYPE);
612
                    CODE_FOLDING_COLLAPSE_INNERCLASS, INNER_CLASS_FOLD_TEMPLATE);
546
            addFoldsOfType("tags", folds, result, doc, 
547
                    TAG_FOLD_TYPE);
548
            addFoldsOfType("othercodeblocks", folds, result, doc, 
549
                    CODE_BLOCK_FOLD_TYPE);
550
            addFoldsOfType("inner-classes", folds, result, doc, 
551
                    INNER_CLASS_FOLD_TYPE);
552
            
553
            if (folds.size() > 0) {
554
                LOG.log(Level.WARNING, "Undefined fold types used in {0}: {1}", new Object[] {
555
                    info, folds.keySet()
556
                });
557
            }
613
        }
558
        }
614
        
559
        
615
        private void addFold(OffsetRange range, TreeSet<FoldInfo> folds, Document doc, boolean collapseByDefault, FoldTemplate template) throws BadLocationException {
560
        private void addFold(OffsetRange range, Collection<FoldInfo> folds, Document doc, FoldType type) {
616
            if (range != OffsetRange.NONE) {
561
            if (range != OffsetRange.NONE) {
617
                int start = range.getStart();
562
                int start = range.getStart();
618
                int end = range.getEnd();
563
                int end = range.getEnd();
619
                if (start != (-1) && end != (-1) && end <= doc.getLength()) {
564
                if (start != (-1) && end != (-1) && end <= doc.getLength()) {
620
                    folds.add(new FoldInfo(doc, start, end, template, collapseByDefault));
565
                    FoldInfo fi = FoldInfo.range(start, end, type);
566
                    // hack for singular imports fold
567
                    if (fi.getType() == IMPORTS_FOLD_TYPE && imports == null) {
568
                        imports = fi;
569
                    }
570
                    folds.add(fi);
621
                }
571
                }
622
            }
572
            }
623
        }
573
        }
Lines 639-655 Link Here
639
589
640
    }
590
    }
641
    
591
    
592
    private Runnable createCommit(Collection<FoldInfo> folds, FoldInfo initComment, FoldInfo imports, Document d, Source s) {
593
        return new CommitFolds(folds, initComment, imports, d, s);
594
    }
595
    
642
    private class CommitFolds implements Runnable {
596
    private class CommitFolds implements Runnable {
643
        private Document scannedDocument;
597
        private final Document scannedDocument;
644
        private Source scanSource;
598
        private Source  scanSource;
645
        
599
        
646
        private boolean insideRender;
600
        private boolean insideRender;
647
        private TreeSet<FoldInfo> infos;
601
        private Collection<FoldInfo> infos;
648
        private long startTime;
602
        private long startTime;
603
        private FoldInfo    initComment;
604
        private FoldInfo    imports;
649
        
605
        
650
        public CommitFolds(TreeSet<FoldInfo> infos, Document scanedDocument, Source s) {
606
        public CommitFolds(Collection<FoldInfo> infos, FoldInfo initComment, FoldInfo imports, Document scannedDocument, Source s) {
651
            this.infos = infos;
607
            this.infos = infos;
652
            this.scannedDocument = scanedDocument;
608
            this.initComment = initComment;
609
            this.imports = imports;
610
            this.scannedDocument = scannedDocument;
653
            this.scanSource = s;
611
            this.scanSource = s;
654
        }
612
        }
655
        
613
        
Lines 658-674 Link Here
658
         * ignores the default state, and takes it from the actual state of
616
         * ignores the default state, and takes it from the actual state of
659
         * existing fold.
617
         * existing fold.
660
         */
618
         */
661
        private boolean mergeSpecialFoldState(FoldInfo fi) {
619
        private void mergeSpecialFoldState(FoldInfo fi, Fold f) {
662
            if (fi.template == IMPORTS_FOLD_TEMPLATE) {
620
            if (fi != null && f != null) {
663
                if (importsFold != null) {
621
                fi.collapsed(f.isCollapsed());
664
                    return importsFold.isCollapsed();
665
                }
666
            } else if (fi.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
667
                if (initialCommentFold != null) {
668
                    return initialCommentFold.isCollapsed();
669
                }
670
            }
622
            }
671
            return fi.collapseByDefault;
672
        }
623
        }
673
624
674
        public void run() {
625
        public void run() {
Lines 682-687 Link Here
682
            }
633
            }
683
            
634
            
684
            operation.getHierarchy().lock();
635
            operation.getHierarchy().lock();
636
            if (task == null) {
637
                return;
638
            }
685
            if (operation.getHierarchy().getComponent().getDocument() != this.scannedDocument) {
639
            if (operation.getHierarchy().getComponent().getDocument() != this.scannedDocument) {
686
                Throwable t = (Throwable)scannedDocument.getProperty("Issue-222763-debug");
640
                Throwable t = (Throwable)scannedDocument.getProperty("Issue-222763-debug");
687
                StringWriter sw = new StringWriter();
641
                StringWriter sw = new StringWriter();
Lines 704-774 Link Here
704
                return;
658
                return;
705
            }
659
            }
706
            try {
660
            try {
707
                FoldHierarchyTransaction tr = operation.openTransaction();
708
                
709
                try {
661
                try {
710
                    if (currentFolds == null) {
662
                    mergeSpecialFoldState(imports, importsFold);
711
                        return;
663
                    mergeSpecialFoldState(initComment, initialCommentFold);
664
                    
665
                    Map<FoldInfo, Fold> newState = operation.update(infos, null, null);
666
                    if (imports != null) {
667
                        importsFold = newState.get(imports);
712
                    }
668
                    }
713
                    
669
                    if (initComment != null) {
714
                    Map<FoldInfo, Fold> added   = new TreeMap<FoldInfo, Fold>();
670
                        initialCommentFold = newState.get(initComment);
715
                    // TODO - use map duplication here instead?
716
                    TreeSet<FoldInfo> removed = new TreeSet<FoldInfo>(currentFolds.keySet());
717
                    int documentLength = document.getLength();
718
                    
719
                    for (FoldInfo i : infos) {
720
                        if (removed.remove(i)) {
721
                            continue ;
722
                        }
723
                        
724
                        int start = i.start.getOffset();
725
                        int end   = i.end.getOffset();
726
                        
727
                        if (end > documentLength) {
728
                            continue;
729
                        }
730
                        
731
                        if (end > start && (end - start) > (i.template.getStartGuardedLength() + i.template.getEndGuardedLength())) {
732
                            Fold f    = operation.addToHierarchy(i.template.getType(),
733
                                                                 i.template.getDescription(),
734
                                                                 mergeSpecialFoldState(i),
735
                                                                 start,
736
                                                                 end,
737
                                                                 i.template.getStartGuardedLength(),
738
                                                                 i.template.getEndGuardedLength(),
739
                                                                 i,
740
                                                                 tr);
741
                            
742
                            added.put(i, f);
743
                            
744
                            if (i.template == IMPORTS_FOLD_TEMPLATE) {
745
                                importsFold = f;
746
                            }
747
                            if (i.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
748
                                initialCommentFold = f;
749
                            }
750
                        }
751
                    }
671
                    }
752
                    
753
                    for (FoldInfo i : removed) {
754
                        Fold f = currentFolds.remove(i);
755
                        
756
                        operation.removeFromHierarchy(f, tr);
757
                        
758
                        if (importsFold == f ) {
759
                            importsFold = null;
760
                        }
761
                        
762
                        if (initialCommentFold == f) {
763
                            initialCommentFold = f;
764
                        }
765
                    }
766
                    
767
                    currentFolds.putAll(added);
768
                } catch (BadLocationException e) {
672
                } catch (BadLocationException e) {
769
                    LOG.log(Level.WARNING, null, e);
673
                    LOG.log(Level.WARNING, null, e);
770
                } finally {
771
                    tr.commit();
772
                }
674
                }
773
            } finally {
675
            } finally {
774
                operation.getHierarchy().unlock();
676
                operation.getHierarchy().unlock();
Lines 781-843 Link Here
781
        }
683
        }
782
    }
684
    }
783
    
685
    
784
    private Map<FoldInfo, Fold> currentFolds;
785
    private Fold initialCommentFold;
686
    private Fold initialCommentFold;
786
    private Fold importsFold;
687
    private Fold importsFold;
787
    
788
    protected static final class FoldInfo implements Comparable {
789
        
790
        private Position start;
791
        private Position end;
792
        private FoldTemplate template;
793
        private boolean collapseByDefault;
794
        
795
        public FoldInfo(Document doc, int start, int end, FoldTemplate template, boolean collapseByDefault) throws BadLocationException {
796
            this.start = doc.createPosition(start);
797
            // see issue #216378; while Fold.end Position is manually updated by FoldHierarchyTransactionImpl, the
798
            // FoldInfos are left alone, and end marker must stick with the content, so characters typed after it does
799
            // not extend the FoldInfo
800
            this.end   = NbDocument.createPosition(doc, end, Position.Bias.Backward);
801
            this.template = template;
802
            this.collapseByDefault = collapseByDefault;
803
        }
804
        
805
        @Override
806
        public int hashCode() {
807
            return 1;
808
        }
809
        
810
        @Override
811
        public boolean equals(Object o) {
812
            if (!(o instanceof FoldInfo)) {
813
                return false;
814
            }
815
            
816
            return compareTo(o) == 0;
817
        }
818
        
819
        public int compareTo(Object o) {
820
            FoldInfo remote = (FoldInfo) o;
821
            
822
            if (start.getOffset() < remote.start.getOffset()) {
823
                return -1;
824
            }
825
            
826
            if (start.getOffset() > remote.start.getOffset()) {
827
                return 1;
828
            }
829
            
830
            if (end.getOffset() < remote.end.getOffset()) {
831
                return -1;
832
            }
833
            
834
            if (end.getOffset() > remote.end.getOffset()) {
835
                return 1;
836
            }
837
            
838
            return 0;
839
        }
840
    }
841
688
842
    private static boolean hasErrors(ParserResult r) {
689
    private static boolean hasErrors(ParserResult r) {
843
        for(org.netbeans.modules.csl.api.Error e : r.getDiagnostics()) {
690
        for(org.netbeans.modules.csl.api.Error e : r.getDiagnostics()) {
(-)a/editor.codetemplates/nbproject/org-netbeans-modules-editor-codetemplates.sig (-1 / +1 lines)
Lines 1-5 Link Here
1
#Signature file v4.1
1
#Signature file v4.1
2
#Version 1.26.1
2
#Version 1.31.0
3
3
4
CLSS public java.lang.Object
4
CLSS public java.lang.Object
5
cons public init()
5
cons public init()
(-)a/editor.codetemplates/nbproject/project.properties (-1 / +1 lines)
Lines 45-50 Link Here
45
#javadoc.name=EditorCodeTemplates
45
#javadoc.name=EditorCodeTemplates
46
javadoc.apichanges=${basedir}/apichanges.xml
46
javadoc.apichanges=${basedir}/apichanges.xml
47
javadoc.arch=${basedir}/arch.xml
47
javadoc.arch=${basedir}/arch.xml
48
spec.version.base=1.31.0
48
spec.version.base=1.32.0
49
49
50
test.config.stableBTD.includes=**/*Test.class
50
test.config.stableBTD.includes=**/*Test.class
(-)a/editor.codetemplates/nbproject/project.xml (-1 / +1 lines)
Lines 99-105 Link Here
99
                    <compile-dependency/>
99
                    <compile-dependency/>
100
                    <run-dependency>
100
                    <run-dependency>
101
                        <release-version>1</release-version>
101
                        <release-version>1</release-version>
102
                        <specification-version>1.20</specification-version>
102
                        <specification-version>1.31</specification-version>
103
                    </run-dependency>
103
                    </run-dependency>
104
                </dependency>
104
                </dependency>
105
                <dependency>
105
                <dependency>
(-)a/editor.codetemplates/src/org/netbeans/lib/editor/codetemplates/storage/SettingsProvider.java (-30 / +4 lines)
Lines 106-116 Link Here
106
            this.ic = ic;
106
            this.ic = ic;
107
            
107
            
108
            // Start listening
108
            // Start listening
109
            MimePath [] allPaths = computeInheritedMimePaths(mimePath);
109
            List<MimePath> allPaths = mimePath.getIncludedPaths();
110
            this.allCtsi = new CodeTemplateSettingsImpl[allPaths.length];
110
            this.allCtsi = new CodeTemplateSettingsImpl[allPaths.size()];
111
            
111
            
112
            for(int i = 0; i < allPaths.length; i++) {
112
            for(int i = 0; i < allPaths.size(); i++) {
113
                this.allCtsi[i] = CodeTemplateSettingsImpl.get(allPaths[i]);
113
                this.allCtsi[i] = CodeTemplateSettingsImpl.get(allPaths.get(i));
114
                this.allCtsi[i].addPropertyChangeListener(WeakListeners.propertyChange(this, this.allCtsi[i]));
114
                this.allCtsi[i].addPropertyChangeListener(WeakListeners.propertyChange(this, this.allCtsi[i]));
115
            }
115
            }
116
        }
116
        }
Lines 170-199 Link Here
170
        }
170
        }
171
        
171
        
172
    } // End of CompositeCTS class
172
    } // End of CompositeCTS class
173
    
174
    private static MimePath [] computeInheritedMimePaths(MimePath mimePath) {
175
        List<String> paths = null;
176
        try {
177
            Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
178
            m.setAccessible(true);
179
            @SuppressWarnings("unchecked")
180
            List<String> ret = (List<String>) m.invoke(mimePath, null, null);
181
            paths = ret;
182
        } catch (Exception e) {
183
            LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
184
        }
185
186
        if (paths != null) {
187
            ArrayList<MimePath> mimePaths = new ArrayList<MimePath>(paths.size());
188
189
            for (String path : paths) {
190
                mimePaths.add(MimePath.parse(path));
191
            }
192
193
            return mimePaths.toArray(new MimePath[mimePaths.size()]);
194
        } else {
195
            return new MimePath [] { mimePath, MimePath.EMPTY };
196
        }
197
    }
198
    
199
}
173
}
(-)a/editor.fold/arch.xml (+116 lines)
Lines 450-455 Link Here
450
    }
450
    }
451
</pre>
451
</pre>
452
452
453
<h3>Updating Fold hierarchy</h3>
454
In the preceding cases, maintaining Folds was the FoldManager's responsibility. The FoldManager typically
455
held a copy of the Folds added to the hierarchy, and during the refresh, it compared them to the new data
456
and decided what folds to remove.
457
For simple cases, which only create/remove folds based on text positions, part of the work can be offloaded to the
458
FoldOperation:
459
460
<pre>
461
    // create new fold positional information for all folds.
462
    Collection&lt;FoldInfo> newInfos = ...;
463
    
464
    // create FoldInfo for each of the fold
465
    newInfos.add(
466
        FoldInfo.range(start, end, type).
467
            withTemplate(customTemplate).
468
            withDescription(veryCustomDescription).
469
            collapse(true)
470
    );
471
    
472
    // the hierarchy must be locked prior to update
473
    
474
    doc.readLock();
475
    hierarchy.lock();
476
    try {
477
        operation.update(newInfos, null, null);
478
    } finally {
479
    }
480
</pre>
481
482
The <code>update()</code> operation performs a diff, creates new folds, discards old ones, and updates the folds, which
483
prevailed.
484
485
<h3>Accessing folds</h3>
486
Instead of keeping a copy of created folds, the FoldManager may call <code>operation.foldIterator</code>. The iterator
487
will enumerate all folds, including (recursively) blocked ones.
453
488
454
</answer>
489
</answer>
455
490
Lines 1204-1207 Link Here
1204
No.
1239
No.
1205
</answer>
1240
</answer>
1206
1241
1242
1243
1244
1245
<!--
1246
        <question id="arch-where" when="impl">
1247
            Where one can find sources for your module?
1248
            <hint>
1249
                Please provide link to the Hg web client at
1250
                http://hg.netbeans.org/
1251
                or just use tag defaultanswer generate='here'
1252
            </hint>
1253
        </question>
1254
-->
1255
 <answer id="arch-where">
1256
  <defaultanswer generate='here' />
1257
 </answer>
1258
1259
1260
1261
<!--
1262
        <question id="compat-deprecation" when="init">
1263
            How the introduction of your project influences functionality
1264
            provided by previous version of the product?
1265
            <hint>
1266
            If you are planning to deprecate/remove/change any existing APIs,
1267
            list them here accompanied with the reason explaining why you
1268
            are doing so.
1269
            </hint>
1270
        </question>
1271
-->
1272
 <answer id="compat-deprecation">
1273
  <p>
1274
   XXX no answer for compat-deprecation
1275
  </p>
1276
 </answer>
1277
1278
1279
1280
<!--
1281
        <question id="exec-ant-tasks" when="impl">
1282
            Do you define or register any ant tasks that other can use?
1283
            
1284
            <hint>
1285
            If you provide an ant task that users can use, you need to be very
1286
            careful about its syntax and behaviour, as it most likely forms an
1287
	          API for end users and as there is a lot of end users, their reaction
1288
            when such API gets broken can be pretty strong.
1289
            </hint>
1290
        </question>
1291
-->
1292
 <answer id="exec-ant-tasks">
1293
  <p>
1294
   XXX no answer for exec-ant-tasks
1295
  </p>
1296
 </answer>
1297
1298
1299
1300
<!--
1301
        <question id="resources-preferences" when="final">
1302
            Does your module uses preferences via Preferences API? Does your module use NbPreferences or
1303
            or regular JDK Preferences ? Does it read, write or both ? 
1304
            Does it share preferences with other modules ? If so, then why ?
1305
            <hint>
1306
                You may use
1307
                    &lt;api type="export" group="preferences"
1308
                    name="preference node name" category="private"&gt;
1309
                    description of individual keys, where it is used, what it
1310
                    influences, whether the module reads/write it, etc.
1311
                    &lt;/api&gt;
1312
                Due to XML ID restrictions, rather than /org/netbeans/modules/foo give the "name" as org.netbeans.modules.foo.
1313
                Note that if you use NbPreferences this name will then be the same as the code name base of the module.
1314
            </hint>
1315
        </question>
1316
-->
1317
 <answer id="resources-preferences">
1318
  <p>
1319
   XXX no answer for resources-preferences
1320
  </p>
1321
 </answer>
1322
1207
</api-answers>
1323
</api-answers>
(-)a/editor.fold/nbproject/org-netbeans-modules-editor-fold.sig (-1 / +1 lines)
Lines 1-5 Link Here
1
#Signature file v4.1
1
#Signature file v4.1
2
#Version 1.29.2
2
#Version 1.33.0
3
3
4
CLSS public abstract interface java.io.Serializable
4
CLSS public abstract interface java.io.Serializable
5
5
(-)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 (-1 / +49 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.33</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 64-70 Link Here
64
                    <compile-dependency/>
73
                    <compile-dependency/>
65
                    <run-dependency>
74
                    <run-dependency>
66
                        <release-version>1</release-version>
75
                        <release-version>1</release-version>
67
                        <specification-version>1.20</specification-version>
76
                        <specification-version>1.31</specification-version>
68
                    </run-dependency>
77
                    </run-dependency>
69
                </dependency>
78
                </dependency>
70
                <dependency>
79
                <dependency>
Lines 77-82 Link Here
77
                    </run-dependency>
86
                    </run-dependency>
78
                </dependency>
87
                </dependency>
79
                <dependency>
88
                <dependency>
89
                    <code-name-base>org.netbeans.modules.editor.settings.storage</code-name-base>
90
                    <build-prerequisite/>
91
                    <compile-dependency/>
92
                    <run-dependency>
93
                        <release-version>1</release-version>
94
                        <specification-version>1.38</specification-version>
95
                    </run-dependency>
96
                </dependency>
97
                <dependency>
80
                    <code-name-base>org.netbeans.modules.editor.util</code-name-base>
98
                    <code-name-base>org.netbeans.modules.editor.util</code-name-base>
81
                    <build-prerequisite/>
99
                    <build-prerequisite/>
82
                    <compile-dependency/>
100
                    <compile-dependency/>
Lines 85-90 Link Here
85
                    </run-dependency>
103
                    </run-dependency>
86
                </dependency>
104
                </dependency>
87
                <dependency>
105
                <dependency>
106
                    <code-name-base>org.netbeans.modules.lexer</code-name-base>
107
                    <build-prerequisite/>
108
                    <compile-dependency/>
109
                    <run-dependency>
110
                        <release-version>2</release-version>
111
                        <specification-version>1.49</specification-version>
112
                    </run-dependency>
113
                </dependency>
114
                <dependency>
115
                    <code-name-base>org.netbeans.modules.options.api</code-name-base>
116
                    <build-prerequisite/>
117
                    <compile-dependency/>
118
                    <run-dependency>
119
                        <release-version>1</release-version>
120
                        <specification-version>1.31</specification-version>
121
                    </run-dependency>
122
                </dependency>
123
                <dependency>
124
                    <code-name-base>org.openide.awt</code-name-base>
125
                    <build-prerequisite/>
126
                    <compile-dependency/>
127
                    <run-dependency>
128
                        <specification-version>7.55</specification-version>
129
                    </run-dependency>
130
                </dependency>
131
                <dependency>
88
                    <code-name-base>org.openide.filesystems</code-name-base>
132
                    <code-name-base>org.openide.filesystems</code-name-base>
89
                    <build-prerequisite/>
133
                    <build-prerequisite/>
90
                    <compile-dependency/>
134
                    <compile-dependency/>
Lines 147-154 Link Here
147
            </test-dependencies>
191
            </test-dependencies>
148
            <public-packages>
192
            <public-packages>
149
                <package>org.netbeans.api.editor.fold</package>
193
                <package>org.netbeans.api.editor.fold</package>
194
                <package>org.netbeans.editor</package>
150
                <package>org.netbeans.spi.editor.fold</package>
195
                <package>org.netbeans.spi.editor.fold</package>
151
            </public-packages>
196
            </public-packages>
152
        </data>
197
        </data>
198
        <spellchecker-wordlist xmlns="http://www.netbeans.org/ns/spellchecker-wordlist/1">
199
            <word>initially</word>
200
        </spellchecker-wordlist>
153
    </configuration>
201
    </configuration>
154
</project>
202
</project>
(-)a/editor.fold/src/org/netbeans/api/editor/fold/Fold.java (-2 / +35 lines)
Lines 48-53 Link Here
48
import javax.swing.text.BadLocationException;
48
import javax.swing.text.BadLocationException;
49
import javax.swing.text.Document;
49
import javax.swing.text.Document;
50
import javax.swing.text.Position;
50
import javax.swing.text.Position;
51
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
51
import org.netbeans.modules.editor.fold.FoldOperationImpl;
52
import org.netbeans.modules.editor.fold.FoldOperationImpl;
52
import org.netbeans.modules.editor.fold.FoldChildren;
53
import org.netbeans.modules.editor.fold.FoldChildren;
53
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
54
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
Lines 108-114 Link Here
108
    
109
    
109
    private Object extraInfo;
110
    private Object extraInfo;
110
    
111
    
111
112
    Fold(FoldOperationImpl operation,
112
    Fold(FoldOperationImpl operation,
113
    FoldType type, String description, boolean collapsed,
113
    FoldType type, String description, boolean collapsed,
114
    Document doc, int startOffset, int endOffset,
114
    Document doc, int startOffset, int endOffset,
Lines 404-409 Link Here
404
    public int getFoldIndex(Fold child) {
404
    public int getFoldIndex(Fold child) {
405
        return (children != null) ? children.getFoldIndex(child) : -1;
405
        return (children != null) ? children.getFoldIndex(child) : -1;
406
    }
406
    }
407
408
    /**
409
     * Start of the fold content past the guarded area.
410
     * For root fold, returns 0. For other folds, returns the offset
411
     * following the guarded start of the fold (typically a delimiter).
412
     * 
413
     * @return offset in document
414
     */
415
    public int getGuardedStart() {
416
        if (isRootFold()) {
417
            return 0;
418
        } else if (isZeroStartGuardedLength()) {
419
            return getStartOffset();
420
        } else {
421
            return getStartOffset() + startGuardedLength;
422
        }
423
    }
424
    
425
    /**
426
     * End of the fold content before the guarded area.
427
     * For root fold, returns 0. For other folds, returns the last offset
428
     * preceding the guarded start of the fold (typically a delimiter).
429
     * 
430
     * @return offset in document
431
     */
432
    public int getGuardedEnd() {
433
        if (isRootFold()) {
434
            return getEndOffset();
435
        } else if (isZeroStartGuardedLength()) {
436
            return getEndOffset();
437
        } else {
438
            return getEndOffset() - endGuardedLength;
439
        }
440
    }
407
    
441
    
408
    private void updateGuardedStartPos(Document doc, int startOffset) throws BadLocationException {
442
    private void updateGuardedStartPos(Document doc, int startOffset) throws BadLocationException {
409
        if (!isRootFold()) {
443
        if (!isRootFold()) {
Lines 530-536 Link Here
530
        this.rawIndex += rawIndexDelta;
564
        this.rawIndex += rawIndexDelta;
531
    }
565
    }
532
    
566
    
533
534
    public String toString() {
567
    public String toString() {
535
        return FoldUtilitiesImpl.foldToString(this) + ", [" + getInnerStartOffset() // NOI18N
568
        return FoldUtilitiesImpl.foldToString(this) + ", [" + getInnerStartOffset() // NOI18N
536
            + ", " + getInnerEndOffset() + "] {" // NOI18N
569
            + ", " + getInnerEndOffset() + "] {" // NOI18N
(-)a/editor.fold/src/org/netbeans/api/editor/fold/FoldHierarchy.java (-1 / +6 lines)
Lines 53-58 Link Here
53
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
53
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
54
import org.netbeans.modules.editor.fold.FoldHierarchyExecution;
54
import org.netbeans.modules.editor.fold.FoldHierarchyExecution;
55
import org.netbeans.modules.editor.fold.FoldOperationImpl;
55
import org.netbeans.modules.editor.fold.FoldOperationImpl;
56
import org.netbeans.spi.editor.fold.FoldTypeProvider;
56
57
57
/**
58
/**
58
 * Hierarchy of the folds for a single text component represents
59
 * Hierarchy of the folds for a single text component represents
Lines 122-128 Link Here
122
    static {
123
    static {
123
        ensureApiAccessorRegistered();
124
        ensureApiAccessorRegistered();
124
    }
125
    }
125
    
126
126
    private static void ensureApiAccessorRegistered() {
127
    private static void ensureApiAccessorRegistered() {
127
        if (!apiPackageAccessorRegistered) {
128
        if (!apiPackageAccessorRegistered) {
128
            apiPackageAccessorRegistered = true;
129
            apiPackageAccessorRegistered = true;
Lines 458-463 Link Here
458
            fsc.endOffsetChanged(originalEndOffset);
459
            fsc.endOffsetChanged(originalEndOffset);
459
        }
460
        }
460
        
461
        
462
        public FoldHierarchyExecution foldGetExecution(FoldHierarchy h) {
463
            return h.execution;
464
        }
465
        
461
    }
466
    }
462
467
463
}
468
}
(-)a/editor.fold/src/org/netbeans/api/editor/fold/FoldTemplate.java (+125 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 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 2011 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.fold;
43
44
import org.openide.util.NbBundle;
45
46
import static org.netbeans.api.editor.fold.Bundle.*;
47
48
/**
49
 * Template that describes how the fold should appear and interact with the
50
 * user. The FoldTemplate defines how many characters at the start (end) should
51
 * act as a fold guard: change to that area will remove the fold because it
52
 * becomes damaged. It also defines what placeholder should appear in place of the 
53
 * folded code.
54
 * <p/>
55
 * A template may be attached to a {@link FoldType} instance. Folds of that kind will
56
 * automatically use the attached Template, if not overriden explicitly.
57
 * <p/>
58
 * The string {@code "..."} ({@link #CONTENT_PLACEHOLDER}) is treated as a placeholder. If a 
59
 * ContentReader is available for the {@link FoldType}, the placeholder will be replaced by 
60
 * the product of the Reader. Otherwise the placeholder will remain in the displayText 
61
 * and will be presented.
62
 * 
63
 * @since 1.34
64
 * @author sdedic
65
 */
66
public final class FoldTemplate {
67
    /**
68
     * The default template for folded text: no markers before+after, ellipsis
69
     * shown.
70
     */
71
    @NbBundle.Messages("FT_DefaultTemplate=...")
72
    public static final FoldTemplate DEFAULT = new FoldTemplate(0, 0, FT_DefaultTemplate()); // NOI18N
73
    
74
    /**
75
     * A standard template, which represents a block of something.
76
     */
77
    @NbBundle.Messages("FT_DefaultBlockTemplate={...}")
78
    public static final FoldTemplate DEFAULT_BLOCK = new FoldTemplate(0, 0, FT_DefaultBlockTemplate()); // NOI18N
79
    
80
    /**
81
     * This string is interpreted as a placeholder for the infrastructure to inject 
82
     */
83
    public static final String CONTENT_PLACEHOLDER = FT_DefaultTemplate();
84
    
85
    /**
86
     * # of characters at the start of the fold, which serve as a marker that the fold
87
     * has been damaged/destroyed
88
     */
89
    private int     guardedStart;
90
    
91
    /**
92
     * The guarded portion at the end of the fold
93
     */
94
    private int     guardedEnd;
95
    
96
    /**
97
     * Description that appears in the folded area.
98
     */
99
    private String  displayText;
100
    
101
    /**
102
     * Creates a FoldTemplate with a fixed description.
103
     * 
104
     * @param guardedStart length of the start marker, or -1 if no start marker is present
105
     * @param guardedEnd length of the end marker, or -1 if no end marker is present
106
     * @param displayText text which should be displayed in place of the folded content
107
     */
108
    public FoldTemplate(int guardedStart, int guardedEnd, String displayText) {
109
        this.guardedStart = guardedStart;
110
        this.guardedEnd = guardedEnd;
111
        this.displayText = displayText;
112
    }
113
114
    public String getDescription() {
115
        return displayText;
116
    }
117
118
    public int getGuardedEnd() {
119
        return guardedEnd;
120
    }
121
122
    public int getGuardedStart() {
123
        return guardedStart;
124
    }
125
}
(-)a/editor.fold/src/org/netbeans/api/editor/fold/FoldType.java (-12 / +303 lines)
Lines 44-91 Link Here
44
44
45
package org.netbeans.api.editor.fold;
45
package org.netbeans.api.editor.fold;
46
46
47
import java.util.Collection;
48
import javax.swing.event.ChangeListener;
49
import org.openide.util.NbBundle;
50
import org.openide.util.Parameters;
51
52
import static org.netbeans.api.editor.fold.Bundle.*;
53
47
/**
54
/**
48
 * Each {@link Fold} is identified by its fold type.
55
 * Each {@link Fold} is identified by its fold type.
49
 * <br>
56
 * <br>
50
 * Each fold type presents a fold type acceptor as well
57
 * Each fold type presents a fold type acceptor as well
51
 * accepting just itself.
58
 * accepting just itself.
52
 *
59
 *
53
 * <p>
60
 * <p/>
61
 * <strike>
54
 * As the <code>equals()</code> method is declared final
62
 * As the <code>equals()</code> method is declared final
55
 * (delegating to <code>super</code>) the fold types
63
 * (delegating to <code>super</code>) the fold types
56
 * can directly be compared by using <code>==</code>
64
 * can directly be compared by using <code>==</code>
57
 * operator.
65
 * operator.
66
 * </strike>
67
  * <p/>
68
 * FoldTypes are MIME-type specific. Sets of FoldType, which apply
69
 * to a certain MIME type are collected in a {@link FoldType.Domain},
70
 * which can be obtained by {@link FoldUtilities#getFoldTypes}. FoldTypes for a MIME type are
71
 * collected with the help of {@link org.netbeans.spi.editor.fold.FoldTypeProvider}.
72
 * <p/>
73
 * Several generalized types of folds are defined in this class as constants. 
74
 * When creating custom FoldTypes, please check carefully whether the new FoldType is not, 
75
 * in fact, one of these general ones,
76
 * or is not a specialization of it. If a general action (e.g. auto-collapse all folds XXX) would
77
 * make sense for the new fold, consider to {@link #derive} it from an existing one. 
78
 * <p/>
79
 * Each FoldType has a specific FoldTemplate instance that controls how it is presented. If a
80
 * new Fold has same semantics as an existing one, but a different {@link FoldTemplate} is needed,
81
 * use {@link #override} to create a new FoldType with appropriate properties. FoldTypes
82
 * defined here can be <b>reused</b> in individual MIME types, if their configuration is appropriate.
83
 * In general, FoldTypes from more general MIME types (e.g. text/xml) can be reused in specialized ones
84
 * (e.g. text/x-ant+xml).
85
 * <p/>
86
 * FoldTypes can form a hierarchy of generalization. For example java Method fold can have a more general
87
 * 'parent', {@link #MEMBER}. 
88
 * <p/>
89
 * The generalization is designed to allow several kinds of the same concept operated separately at the
90
 * level of the language, but managed at once using general options or operations. 
91
 * If no settings are specified for Method folding, {@link #MEMBER} settings are
92
 * applied. Likewise, a general-purpose folding operation can work on {@link #MEMBER} fold type,
93
 * which will affect both FUNCTION in PHP or METHOD in java (and other fold types in other languages).
94
 * <p/>
95
 * Because of this, please be careful
96
 * to use {@link #isKindOf} to check whether a FoldType is of a specific type. This method respects the
97
 * generalization. == can be still used to check for exact type match. When using ==, javadoc comments
98
 * may be evaluated as different from general 'documentation' and yet different from PHP docs. Comparison using 
99
 * == are safe only when applied on FoldType instances defined for the same MIME type
100
* <p/>
58
 *
101
 *
59
 * <p>
102
 * @author sdedic
60
 * Fold providers should export all the fold types
61
 * that they provide in their APIs so that the
62
 * clients can use these fold types in operations
63
 * with the fold hierarchy.
64
 *
65
 * @author Miloslav Metelka
103
 * @author Miloslav Metelka
66
 * @version 1.00
104
 * @version 1.00
67
 */
105
 */
68
106
69
public final class FoldType {
107
public final class FoldType {
108
    /**
109
     * A generic code block. Note that other categories are defined that represent
110
     * method, class, member or nested symbol levels
111
     * @since 1.34
112
     */
113
    @NbBundle.Messages({
114
        "FT_Label_code-block=Code Blocks",
115
        "FT_display_code-block={...}",
116
        "FT_display_default=..."
117
    })
118
    public static final FoldType    CODE_BLOCK = create("code-block", FT_Label_code_block(), 
119
            new FoldTemplate(1, 1, FT_display_code_block()));
120
121
    /**
122
     * Documentation comment, as defined by the language
123
     * @since 1.34
124
     */
125
    @NbBundle.Messages("FT_Label_javadoc=Documentation")
126
    public static final FoldType    DOCUMENTATION = create("documentation", FT_Label_javadoc(), 
127
            FoldTemplate.DEFAULT);
70
    
128
    
71
    private final String description;
129
    /**
130
     * Unspecified type of comment
131
     * @since 1.34
132
     */
133
    @NbBundle.Messages("FT_Label_comment=Comments")
134
    public static final FoldType    COMMENT = create("comment", FT_Label_comment(), 
135
            FoldTemplate.DEFAULT);
72
    
136
    
73
    /**
137
    /**
138
     * Initial file-level comment. Either documentation comment or comment containing
139
     * copyright. Defaults to 'comment'
140
     * @since 1.34
141
     */
142
    @NbBundle.Messages("FT_Label_initial-comment=Initial Comment")
143
    public static final FoldType    INITIAL_COMMENT = create("initial-comment", FT_Label_initial_comment(),
144
            FoldTemplate.DEFAULT);
145
146
    /**
147
     * Tag in markup languages
148
     * @since 1.34
149
     */
150
    @NbBundle.Messages({
151
        "FT_Label_tag=Tags and Markup",
152
        "FT_display_tag=<.../>"
153
    })
154
    public static final FoldType    TAG = create("tag", FT_Label_tag(), 
155
            new FoldTemplate(1, 2, FT_display_tag()));
156
    
157
    /**
158
     * Nested part; for example an embedded type, or nested namespace. Do not use
159
     * this category for top-level symbols. This type is different from a 'member',
160
     * as type may have many member types, nested types form worlds of their own
161
     * @since 1.34
162
     */
163
    @NbBundle.Messages("FT_Label_inner-class=Nested Blocks")
164
    public static final FoldType    NESTED = create("nested", FT_Label_inner_class(), 
165
            FoldTemplate.DEFAULT);
166
    
167
    /**
168
     * Member of a symbol (type, class, object)
169
     * @since 1.34
170
     */
171
    @NbBundle.Messages("FT_Label_member=Member")
172
    public static final FoldType    MEMBER = create("member", FT_Label_member(), 
173
            FoldTemplate.DEFAULT);
174
175
    /**
176
     * Various import, includes and references to sibling files.
177
     * 
178
     * @since 1.34
179
     */
180
    @NbBundle.Messages("FT_Label_import=Imports and Includes")
181
    public static final FoldType    IMPORT = create("import", FT_Label_import(), 
182
            FoldTemplate.DEFAULT);
183
    
184
    /**
185
     * User-defined fold, recoded using a special comment
186
     * @since 1.34
187
     */
188
    @NbBundle.Messages("FT_Label_user-defined=User defined")
189
    public static final FoldType    USER = create("user", FT_Label_user_defined(), null);
190
191
    /**
74
     * Construct fold type with the given description.
192
     * Construct fold type with the given description.
75
     *
193
     *
76
     * @param description textual description of the fold type.
194
     * @param description textual description of the fold type.
77
     *  Two fold types with the same description are not equal.
195
     *  Two fold types with the same description are not equal.
196
     * @deprecated Use the {link create} static method.
78
     */
197
     */
198
    @Deprecated
79
    public FoldType(String description) {
199
    public FoldType(String description) {
80
        this.description = description;
200
        this(description, null, null, null);
81
    }
201
    }
82
    
202
    
83
    public boolean accepts(FoldType type) {
203
    private FoldType(String code, String label, FoldTemplate template, FoldType parent) {
84
        return (type == this);
204
        this.code = code;
205
        this.parent = parent;
206
        this.label = label;
207
        this.template = template != null ? template : FoldTemplate.DEFAULT;
208
    }
209
210
    /**
211
     * Creates an instance of FoldType.
212
     * The code, label and template parameters will be assigned to the FoldType's properties.
213
     * 
214
     * @param code code used to form, e.g. persistent keys for info related to the FoldType
215
     * @param label human-readable label that identifies the FoldType
216
     * @param template describes how the fold is presented
217
     * @return FoldType instance
218
     * @since 1.34
219
     */
220
    public static FoldType create(String code, String label, FoldTemplate template) {
221
        Parameters.notWhitespace("code", code);
222
        Parameters.notWhitespace("label", label);
223
        return new FoldType(code, label, template, null);
85
    }
224
    }
86
    
225
    
226
    /**
227
     * Creates a FoldType, overriding its appearance.
228
     * The new FoldType will use the same code, but label and/or template can be changed.
229
     * 
230
     * @param label human-readable label that describes the FoldType
231
     * @param template human-readable label that describes the FoldType
232
     * @return an instance of FoldType initialized according to parameters
233
     * @since 1.34
234
     */
235
    public FoldType override(String label, FoldTemplate template) {
236
        return derive(code(), label, template);
237
    }
238
    
239
    /**
240
     * Derives a FoldType which acts as a child of this instance.
241
     * The FoldType will be treated as a special case of this instance. If A is the returned
242
     * instance and B is this instance, then A.isKindOf(B) will return true.
243
     * <p/>
244
     * 
245
     * @param code new code for the FoldType
246
     * @param label human-readable label that describes the FoldType
247
     * @param template human-readable label that describes the FoldType
248
     * @return an instance of child FoldType
249
     * @since 1.34
250
     */
251
    public FoldType derive(String code, String label, FoldTemplate template) {
252
        Parameters.notWhitespace("code", code);
253
        Parameters.notWhitespace("label", label);
254
        return new FoldType(code, label, template, this);
255
    }
256
    
257
    /**
258
     * @param type
259
     * @return
260
     * @deprecated Use {@link #isKindOf} 
261
     */
262
    @Deprecated
263
    public boolean accepts(FoldType type) {
264
        return isKindOf(type);
265
    }
266
    
267
    @Override
87
    public String toString() {
268
    public String toString() {
88
        return description;
269
        return code();
270
    }
271
    
272
    /**
273
     * Provides human-readable label for the type
274
     * @return 
275
     * @since 1.34
276
     */
277
    public String getLabel() {
278
        return label;
279
    }
280
    
281
    /**
282
     * Provides FoldTemplate instance for this type.
283
     * The FoldTemplate is the default template used when creating a Fold. Specific FoldTemplate
284
     * may be provided when creating the Fold instance.
285
     * 
286
     * @return fold template for the type.
287
     */
288
    public FoldTemplate getTemplate() {
289
        return template;
290
    }
291
    
292
    /**
293
     * Checks whether the fold can act as the 'other' type.
294
     * This check respect parent relationship (see {@link #parent}). The method returns true,
295
     * if semantics, operations, settings,... applicable to 'other' could be also applied on
296
     * this FoldType.
297
     * 
298
     * @param other the type to check
299
     * @return true, if this FoldType should be affected
300
     * @since 1.34
301
     */
302
    public boolean isKindOf(FoldType other) {
303
        return other == this || parent != null && parent.isKindOf(other);
304
    }
305
    
306
    /**
307
     * Provides parent of this FoldType. Returns {@code null}, if no parent is defined.
308
     * The parent is a FoldType, which is more general and could be used as a fallback when no information
309
     * is provided/available on this FoldType instance.
310
     * 
311
     * @return parent instance or {@code null}
312
     * @since 1.34
313
     */
314
    public FoldType parent() {
315
        return parent;
316
    }
317
   
318
    /**
319
     * Persistable value of FoldType.
320
     * Use {@link FoldType.Domain#valueOf} to convert the value back to FoldType instance. The value
321
     * can (and is) used to compose preference setting keys.
322
     * 
323
     * @return value of FoldType.
324
     * @since 1.34
325
     */
326
    public String code() {
327
        return code;
89
    }
328
    }
90
329
330
    /**
331
     * Represents a value set of {@link FoldType}s for one MIME type.
332
     * The instance collects all FoldTypes defined for a certain MIME type. "" mime type
333
     * represents 'global' FoldTypes.
334
     * <p/>
335
     * The instance will fire change events when the set of fold types change, e.g. as a result
336
     * of enabled/disabled modules (appearance of {@link FoldTypeProvider}).
337
     * @since 1.34
338
     */
339
    public interface Domain {
340
        /**
341
         * Enumerates FoldTypes. Returns empty collection, if no fold types are known.
342
         * @return fold types.
343
         */
344
        Collection<FoldType>    values();
345
        
346
        /**
347
         * Translates String code into FoldType. Returns null if the fold type is not found.
348
         * The String should have been obtained by a call to FoldType.code().
349
         * 
350
         * @param s code value
351
         * @return the FoldType instance.
352
         */
353
        FoldType                valueOf(String s);
354
355
        /**
356
         * Attaches listener to be notified when set of FoldTypes change.
357
         * 
358
         * @param l listener instance.
359
         */
360
        void addChangeListener(ChangeListener l);
361
        
362
        /**
363
         * Detaches the change listener
364
         * @param l listener instance.
365
         */
366
        void removeChangeListener(ChangeListener l);
367
    }
368
    
369
    private final String code;
370
    
371
    private final FoldType parent;
372
373
    /**
374
     * Display name for the fold type, for presentation in UI
375
     */
376
    private final String label;
377
    
378
    /**
379
     * The default fold template
380
     */
381
    private final FoldTemplate template;
91
}
382
}
(-)a/editor.fold/src/org/netbeans/api/editor/fold/FoldUtilities.java (+39 lines)
Lines 48-53 Link Here
48
import java.util.Collections;
48
import java.util.Collections;
49
import java.util.Iterator;
49
import java.util.Iterator;
50
import java.util.List;
50
import java.util.List;
51
import org.netbeans.api.editor.mimelookup.MimePath;
52
import org.netbeans.modules.editor.fold.FoldRegistry;
51
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
53
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
52
54
53
/**
55
/**
Lines 440-443 Link Here
440
        return FoldUtilitiesImpl.collapsedFoldIterator(hierarchy, startOffset, endOffset);
442
        return FoldUtilitiesImpl.collapsedFoldIterator(hierarchy, startOffset, endOffset);
441
    }
443
    }
442
    
444
    
445
    /**
446
     * Obtains available FoldType values for the specified MIME type.
447
     * 
448
     * @param mimeType mime type of the content
449
     * @return available FoldTypes
450
     * @since 1.34
451
     */
452
    public static FoldType.Domain  getFoldTypes(String mimeType) {
453
        return FoldRegistry.get().getDomain(MimePath.parse(mimeType));
454
    }
455
    
456
    /**
457
     * Determines whether folds of that type are should be initially collapsed.
458
     * The FoldType is evaluated in the context of a specific FoldHierarchy, that is a Component
459
     * with a content of a certain MIME type.
460
     * 
461
     * @param ft FoldType to inspect
462
     * @param hierarchy context for evaluation
463
     * @return true, if folds of FoldType should be initially collapsed.
464
     * @since 1.34
465
     */
466
    public static boolean isAutoCollapsed(FoldType ft, FoldHierarchy hierarchy) {
467
        return FoldUtilitiesImpl.isAutoCollapsed(ft, hierarchy);
468
    }
469
    
470
    /**
471
     * Determines whether folding is enabled for a given MIME type.
472
     * Use {@link MimePath#EMPTY}.getMimeType() to query for the default (all languages)
473
     * setting.
474
     * 
475
     * @param mimeType the MIME type of the content. 
476
     * @return true, if folding is enabled, false otherwise.
477
     * @since 1.34
478
     */
479
    public static boolean isFoldingEnabled(String mimeType) {
480
        return FoldUtilitiesImpl.isFoldingEnabled(mimeType);
481
    }
443
}
482
}
(-)a/editor.fold/src/org/netbeans/api/editor/fold/FoldingSupport.java (+194 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.JavadocReader;
50
import org.netbeans.modules.editor.fold.ui.CodeFoldingSideBar;
51
import org.netbeans.spi.editor.fold.ContentReader;
52
import org.netbeans.spi.editor.fold.FoldManager;
53
import org.netbeans.spi.editor.fold.FoldManagerFactory;
54
import org.openide.util.Parameters;
55
56
/**
57
 * This utility class collects APIs to create default implementations
58
 * of various pieces of infrastructure.
59
 * 
60
 * @author sdedic
61
 */
62
public final class FoldingSupport {
63
    private static SideBarFactory FACTORY = null;
64
    
65
    private FoldingSupport() {}
66
67
    /**
68
     * Creates a default implementation of {@link ContentReader}.
69
     * 
70
     * The default implementation is modeled to work with Javadoc-like comments. It will
71
     * ignore markers at line start (first non-whitespace) - 'start' parameter. If the reader
72
     * encounters the 'stop' regex pattern, it stops scanning and returns {@code null}. Typically
73
     * some tags are placed at the end of the doc comment, and they are not informative enough
74
     * to put them into folded preview.
75
     * <p/>
76
     * Finally, if a suitable content is found, and it contains the 'terminator' pattern,
77
     * the content is only returned up to (excluding) the terminator.
78
     * <p/>
79
     * Javadoc (PHPdoc) reader can be constructed as <code>defaultReader("*", "\\.", "@");</code>
80
     *
81
     * @param start character sequence, which will be ignored at the beginning of the line. Can be {@code null}
82
     * @param terminator pattern, which marks the end of the summary line/sentence. Can be {@code null}
83
     * @param stop content search stop at the 'stop' pattern. Can be {@code null}
84
     */
85
    public static ContentReader contentReader(String start, String terminator, String stop, String prefix) {
86
        return new JavadocReader(start, terminator, stop, prefix);
87
    }
88
89
    /**
90
     * A variant of {@link #defaultReader} usable from FS layer.
91
     * See {@link #defaultReader} documentation for parameter explanation. The 
92
     * method expects those parameters as keys in the Map (values are all Strings).
93
     * An additional entry (key: 'type') is required in the map, it identifies the
94
     * FoldType for which the reader should work.
95
     * 
96
     * @param m configuration parameters for the reader factory.
97
     * @see #contentReader
98
     */
99
    public static ContentReader.Factory contentReaderFactory(Map m) {
100
        final String start = (String) m.get("start"); // NOI18N
101
        final String terminator = (String) m.get("terminator"); // NOI18N
102
        final String stop = (String) m.get("stop"); // NOI18N
103
        final String foldType = (String) m.get("type"); // NOI18N
104
        final String prefix = (String)m.get("prefix"); // NOI18N
105
        return new ContentReader.Factory() {
106
            @Override
107
            public ContentReader createReader(FoldType ft) {
108
                if (foldType != null && foldType.equals(ft.code()) || (foldType == null && ft.isKindOf(FoldType.DOCUMENTATION))) {
109
                    return contentReader(start, terminator, stop, prefix);
110
                } else {
111
                    return null;
112
                }
113
            }
114
        };
115
    }
116
117
    /**
118
     * Creates a component for the code folding sidebar.
119
     * Returns a standard folding sidebar component, which displays code folds. This sidebar implementation 
120
     * can be created only once per text component.
121
     *
122
     * @param textComponent the text component which should work with the Sidebar
123
     * @return Sidebar instance.
124
     */
125
    public static JComponent sidebarComponent(JTextComponent textComponent) {
126
        return new CodeFoldingSideBar(textComponent);
127
    }
128
129
    /**
130
     * Creates a user-defined fold manager, that processes specific token type.
131
     * The manager tries to find start/end markers within the token text. If found,
132
     * a Fold is created. The manager only looks in tokens, whose {@code primaryCategory}
133
     * starts with a String, which is stored under 'tokenId' key in the params map.
134
     * <p/>
135
     * The method is designed to be called from the filesystem layer as follows:
136
     * <code><pre>
137
     * &lt;file name="my-custom-foldmanager.instance">
138
     * &lt;attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldUtilities.createUserFoldManager"/>
139
     * &lt;attr name="tokenid" stringvalue="comment"/>
140
     * &lt;/file>
141
     * </pre></code>
142
     *
143
     * @param map the configuration parameters.
144
     * @return FoldManagerFactory instance
145
     */
146
    public static FoldManagerFactory userFoldManagerFactory(Map params) {
147
        final String s = (String) params.get("tokenId");
148
        return new FoldManagerFactory() {
149
            @Override
150
            public FoldManager createFoldManager() {
151
                return userFoldManager(s);
152
            }
153
        };
154
    }
155
    
156
    /**
157
     * Creates a user-defined fold manager, that processes specific token type.
158
     * The manager tries to find start/end markers within the token text. If found,
159
     * a Fold is created. The manager only looks in tokens, whose {@code primaryCategory}
160
     * starts with tokenId string.
161
     * <p/>
162
     * {@code Null} value of 'tokenId' means the default "comment" will be used.
163
     *
164
     * @param tokenId filter for prefix of the token's primaryCategory.
165
     * @return FoldManager instance
166
     */
167
    public static FoldManager userFoldManager(String tokenId) {
168
        if (tokenId != null) {
169
            return new CustomFoldManager(tokenId);
170
        } else {
171
            return new CustomFoldManager();
172
        }
173
    }
174
175
    /**
176
     * Obtains an instance of folding sidebar factory. This method should
177
     * be used in layer, in the MIME lookup area, to register a sidebar with
178
     * an editor for a specific MIMEtype.
179
     * <p/>
180
     * There's a default sidebar instance registered for all MIME types. 
181
     *
182
     * @return shared sidebar factory instance
183
     */
184
    public static SideBarFactory foldingSidebarFactory() {
185
        if (FACTORY != null) {
186
            return FACTORY;
187
        }
188
        return FACTORY = new CodeFoldingSideBar.Factory();
189
    }
190
191
    public static void disableCodeFoldingSidebar(JTextComponent text) {
192
        text.putClientProperty(CodeFoldingSideBar.PROP_SIDEBAR_MARK, true);
193
    }
194
}
(-)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 (+3 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;
Lines 142-146 Link Here
142
    
143
    
143
    public abstract void foldStateChangeEndOffsetChanged(FoldStateChange fsc,
144
    public abstract void foldStateChangeEndOffsetChanged(FoldStateChange fsc,
144
    int originalEndOffset);
145
    int originalEndOffset);
146
    
147
    public abstract FoldHierarchyExecution foldGetExecution(FoldHierarchy fh);
145
148
146
}
149
}
(-)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/DefaultFoldProvider.java (+83 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.util.ArrayList;
45
import java.util.Collection;
46
import org.netbeans.api.editor.fold.FoldType;
47
import org.netbeans.api.editor.mimelookup.MimeRegistration;
48
import org.netbeans.spi.editor.fold.FoldTypeProvider;
49
50
/**
51
 * Default folds, provided by the infrastructure.
52
 * These fold types do not propagate to individual languages, only serve as
53
 * a common base for "all languages" configuration.
54
 * 
55
 * @author sdedic
56
 */
57
@MimeRegistration(mimeType = "", service = FoldTypeProvider.class)
58
public class DefaultFoldProvider implements FoldTypeProvider {
59
    private final Collection<FoldType>  defaultTypes;
60
    
61
    public DefaultFoldProvider() {
62
        defaultTypes = new ArrayList<FoldType>(7);
63
        defaultTypes.add(FoldType.CODE_BLOCK);
64
        defaultTypes.add(FoldType.COMMENT);
65
        defaultTypes.add(FoldType.DOCUMENTATION);
66
        defaultTypes.add(FoldType.INITIAL_COMMENT);
67
        defaultTypes.add(FoldType.MEMBER);
68
        defaultTypes.add(FoldType.NESTED);
69
        defaultTypes.add(FoldType.TAG);
70
        defaultTypes.add(FoldType.USER);
71
    }
72
    
73
    @Override
74
    public Collection getValues(Class type) {
75
        return type == FoldType.class ? defaultTypes : null;
76
    }
77
    
78
    @Override
79
    public boolean inheritable() {
80
        return false;
81
    }
82
    
83
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldContentReaders.java (+161 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.util.ArrayList;
45
import java.util.Collection;
46
import java.util.Collections;
47
import java.util.HashMap;
48
import java.util.List;
49
import java.util.Map;
50
import javax.swing.text.BadLocationException;
51
import javax.swing.text.Document;
52
import org.netbeans.api.editor.fold.Fold;
53
import org.netbeans.api.editor.fold.FoldTemplate;
54
import org.netbeans.api.editor.fold.FoldType;
55
import org.netbeans.api.editor.fold.FoldUtilities;
56
import org.netbeans.api.editor.mimelookup.MimeLookup;
57
import org.netbeans.spi.editor.fold.ContentReader;
58
import org.openide.util.Lookup;
59
import org.openide.util.LookupEvent;
60
import org.openide.util.LookupListener;
61
62
/**
63
 * Manages registered ContentReaders for individual Mime types.
64
 * The cache is flushed iff the set of ContentReader.Factory changes (e.g. during module (un)installation).
65
 *
66
 * @author sdedic
67
 */
68
public final class FoldContentReaders {
69
    private static FoldContentReaders INSTANCE = new FoldContentReaders();
70
    
71
    private volatile Map<String, N> mimeNodes = new HashMap<String, N>();
72
    
73
    public static FoldContentReaders get() {
74
        return INSTANCE;
75
    }
76
    
77
    public CharSequence readContent(String mime, Document d, Fold f, FoldTemplate ft) throws BadLocationException {
78
        List<ContentReader> readers = getReaders(mime, f.getType());
79
        for (ContentReader r : readers) {
80
            CharSequence chs = r.read(d, f, ft);
81
            if (chs != null) {
82
                return chs;
83
            }
84
        }
85
        return null;
86
    }
87
88
    private class N implements LookupListener {
89
        String  mime;
90
        Lookup.Result result;
91
        Map<FoldType, List<ContentReader>> readers = new HashMap<FoldType, List<ContentReader>>();
92
93
        public N(String mime, Lookup mimeLookup) {
94
            this.mime = mime;
95
            init(mimeLookup);
96
        }
97
        
98
        void clear() {
99
            result.removeLookupListener(this);
100
        }
101
102
        @Override
103
        public void resultChanged(LookupEvent ev) {
104
            flush();
105
        }
106
        
107
        private void init(Lookup mimeLookup) {
108
            Collection<? extends FoldType> types = 
109
                    new ArrayList<FoldType>((FoldUtilities.getFoldTypes(mime).values()));
110
            
111
            result = mimeLookup.lookupResult(ContentReader.Factory.class);
112
            Collection<? extends ContentReader.Factory> factories = result.allInstances();
113
114
            List<ContentReader> l;
115
            for (FoldType ft : types) {
116
                l = null;
117
                for (ContentReader.Factory f : factories) {
118
                    ContentReader cr = f.createReader(ft);
119
                    if (cr != null) {
120
                        if (l == null) {
121
                            l = new ArrayList<ContentReader>(3);
122
                        }
123
                        l.add(cr);
124
                    }
125
                }
126
                if (l != null) {
127
                    readers.put(ft, l);
128
                }
129
            }
130
            result.addLookupListener(this);
131
        }
132
        
133
        List<ContentReader> readers(FoldType ft) {
134
            List<ContentReader> r = readers.get(ft);
135
            return r == null ? Collections.<ContentReader>emptyList() : r;
136
        }
137
    }
138
    
139
    public List<ContentReader> getReaders(String mime, FoldType ft) {
140
        N node = mimeNodes.get(mime);
141
        if (node == null) {
142
            synchronized (mimeNodes) {
143
                node = mimeNodes.get(mime);
144
                if (node == null) {
145
                    node = new N(mime, MimeLookup.getLookup(mime));
146
                    mimeNodes.put(mime, node);
147
                }
148
            }
149
        }
150
        return node.readers(ft);
151
    }
152
    
153
    private void flush() {
154
        synchronized (mimeNodes) {
155
            for (N n : mimeNodes.values()) {
156
                n.clear();
157
            }
158
            mimeNodes.clear();
159
        }
160
    }
161
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyExecution.java (-4 / +118 lines)
Lines 57-64 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.logging.Level;
60
import java.util.concurrent.TimeUnit;
61
import java.util.logging.Logger;
61
import java.util.logging.Logger;
62
import java.util.prefs.PreferenceChangeEvent;
63
import java.util.prefs.PreferenceChangeListener;
62
import java.util.prefs.Preferences;
64
import java.util.prefs.Preferences;
63
import javax.swing.SwingUtilities;
65
import javax.swing.SwingUtilities;
64
import javax.swing.event.DocumentEvent;
66
import javax.swing.event.DocumentEvent;
Lines 67-79 Link Here
67
import javax.swing.text.AbstractDocument;
69
import javax.swing.text.AbstractDocument;
68
import javax.swing.text.BadLocationException;
70
import javax.swing.text.BadLocationException;
69
import javax.swing.text.Document;
71
import javax.swing.text.Document;
72
import javax.swing.text.EditorKit;
70
import javax.swing.text.JTextComponent;
73
import javax.swing.text.JTextComponent;
71
import org.netbeans.api.editor.fold.Fold;
74
import org.netbeans.api.editor.fold.Fold;
72
import org.netbeans.api.editor.fold.FoldHierarchy;
75
import org.netbeans.api.editor.fold.FoldHierarchy;
73
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
76
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
74
import org.netbeans.api.editor.fold.FoldHierarchyListener;
77
import org.netbeans.api.editor.fold.FoldHierarchyListener;
75
import org.netbeans.api.editor.fold.FoldStateChange;
78
import org.netbeans.api.editor.fold.FoldStateChange;
79
import org.netbeans.api.editor.fold.FoldType;
80
import org.netbeans.api.editor.fold.FoldUtilities;
76
import org.netbeans.api.editor.mimelookup.MimeLookup;
81
import org.netbeans.api.editor.mimelookup.MimeLookup;
82
import org.netbeans.api.editor.mimelookup.MimePath;
77
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
83
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
78
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
84
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
79
import org.netbeans.spi.editor.fold.FoldManager;
85
import org.netbeans.spi.editor.fold.FoldManager;
Lines 83-88 Link Here
83
import org.openide.ErrorManager;
89
import org.openide.ErrorManager;
84
import org.openide.util.RequestProcessor;
90
import org.openide.util.RequestProcessor;
85
import org.openide.util.Task;
91
import org.openide.util.Task;
92
import org.openide.util.WeakListeners;
86
93
87
/**
94
/**
88
 * Class backing the <code>FoldHierarchy</code> in one-to-one relationship.
95
 * Class backing the <code>FoldHierarchy</code> in one-to-one relationship.
Lines 117-123 Link Here
117
    
124
    
118
    private static final String PROPERTY_FOLD_HIERARCHY_MUTEX = "foldHierarchyMutex"; //NOI18N
125
    private static final String PROPERTY_FOLD_HIERARCHY_MUTEX = "foldHierarchyMutex"; //NOI18N
119
126
120
    private static final String PROPERTY_FOLDING_ENABLED = "code-folding-enable"; //NOI18N
127
    private static final String PROPERTY_FOLDING_ENABLED = FoldUtilitiesImpl.PREF_CODE_FOLDING_ENABLED; //NOI18N
121
128
122
    private static final boolean debug
129
    private static final boolean debug
123
        = Boolean.getBoolean("netbeans.debug.editor.fold"); //NOI18N
130
        = Boolean.getBoolean("netbeans.debug.editor.fold"); //NOI18N
Lines 174-179 Link Here
174
    
181
    
175
    private Task initTask;
182
    private Task initTask;
176
    
183
    
184
    private Preferences     foldPreferences;
185
    
186
    private FoldingEditorSupport editSupport;
187
    
177
    public static synchronized FoldHierarchy getOrCreateFoldHierarchy(JTextComponent component) {
188
    public static synchronized FoldHierarchy getOrCreateFoldHierarchy(JTextComponent component) {
178
        return getOrCreateFoldExecution(component).getHierarchy();
189
        return getOrCreateFoldExecution(component).getHierarchy();
179
    }
190
    }
Lines 256-261 Link Here
256
        } catch (BadLocationException e) {
267
        } catch (BadLocationException e) {
257
            ErrorManager.getDefault().notify(e);
268
            ErrorManager.getDefault().notify(e);
258
        }
269
        }
270
        
271
        editSupport = new FoldingEditorSupport(hierarchy, component);
272
259
    }
273
    }
260
    
274
    
261
    /* testing only */
275
    /* testing only */
Lines 263-268 Link Here
263
        getOrCreateFoldExecution(panel).getInitTask().waitFinished();
277
        getOrCreateFoldExecution(panel).getInitTask().waitFinished();
264
    }
278
    }
265
    
279
    
280
    /* testing only */
281
    static boolean waitAllTasks() throws InterruptedException {
282
        return RP.awaitTermination(30, TimeUnit.SECONDS);
283
    }
284
    
266
    private Task getInitTask() {
285
    private Task getInitTask() {
267
        return initTask;
286
        return initTask;
268
    }
287
    }
Lines 314-319 Link Here
314
        mutex.lock();
333
        mutex.lock();
315
    }
334
    }
316
    
335
    
336
    public final boolean isLockedByCaller() {
337
        return mutex.getLockThread() == Thread.currentThread();
338
    }
339
    
317
    /**
340
    /**
318
     * Unlock the hierarchy from exclusive use. This method must only
341
     * Unlock the hierarchy from exclusive use. This method must only
319
     * be used together with {@link #lock()} in <code>try..finally</code> block.
342
     * be used together with {@link #lock()} in <code>try..finally</code> block.
Lines 454-459 Link Here
454
            : null;
477
            : null;
455
    }
478
    }
456
    
479
    
480
    Set<Fold> getBlockedFolds(Fold f) {
481
        return (Set<Fold>)block2blockedSet.get(f);
482
    }
483
    
457
    /**
484
    /**
458
     * Mark given fold as blocked by the block fold.
485
     * Mark given fold as blocked by the block fold.
459
     */
486
     */
Lines 689-694 Link Here
689
            }
716
            }
690
        }
717
        }
691
    }
718
    }
719
    
720
    private String getMimeType() {
721
        EditorKit ek = component.getUI().getEditorKit(component);
722
        String mimeType;
723
724
        if (ek != null) {
725
            mimeType = ek.getContentType();
726
        } else {
727
            mimeType = "";
728
        }
729
        return mimeType;
730
    }
692
        
731
        
693
    /**
732
    /**
694
     * Rebuild (or release) the root folds of the hierarchy in the event dispatch thread.
733
     * Rebuild (or release) the root folds of the hierarchy in the event dispatch thread.
Lines 721-732 Link Here
721
        boolean ok = false;
760
        boolean ok = false;
722
        try {
761
        try {
723
            operations = new FoldOperationImpl[factoryListLength];
762
            operations = new FoldOperationImpl[factoryListLength];
724
            for (int i = 0; i < factoryListLength; i++) {
763
            int i;
764
            for (i = 0; i < factoryListLength; i++) {
725
                FoldManagerFactory factory = (FoldManagerFactory)factoryList.get(i);
765
                FoldManagerFactory factory = (FoldManagerFactory)factoryList.get(i);
726
                FoldManager manager = factory.createFoldManager();
766
                FoldManager manager = factory.createFoldManager();
767
                if (manager == null) {
768
                    continue;
769
                }
727
                operations[i] = new FoldOperationImpl(this, manager, priority);
770
                operations[i] = new FoldOperationImpl(this, manager, priority);
728
                priority--;
771
                priority--;
729
            }
772
            }
773
            // trim the array in the unlikely case
774
            if (i < factoryListLength) {
775
                FoldOperationImpl[] ops = new FoldOperationImpl[i];
776
                System.arraycopy(operations, 0, ops, 0, i);
777
                operations = ops;
778
            }
730
            ok = true;
779
            ok = true;
731
        } finally {
780
        } finally {
732
            if (!ok) {
781
            if (!ok) {
Lines 878-884 Link Here
878
        if (b == null && component.getDocument() != null) {
927
        if (b == null && component.getDocument() != null) {
879
            String mime = DocumentUtilities.getMimeType(component.getDocument());
928
            String mime = DocumentUtilities.getMimeType(component.getDocument());
880
            if (mime != null) {
929
            if (mime != null) {
881
                Preferences prefs = MimeLookup.getLookup(mime).lookup(Preferences.class);
930
                Preferences prefs = getFoldPreferences();
882
                b = prefs.getBoolean(PROPERTY_FOLDING_ENABLED, true);
931
                b = prefs.getBoolean(PROPERTY_FOLDING_ENABLED, true);
883
            }
932
            }
884
        }
933
        }
Lines 996-1000 Link Here
996
        
1045
        
997
        return sb.toString();
1046
        return sb.toString();
998
    }
1047
    }
1048
    
1049
    public boolean hasProviders() {
1050
        return operations.length > 0;
1051
    }
1052
    
1053
    /**
1054
     * Cache for initial states for individual FoldTypes. 
1055
     * The cache is invalidated iff the Preferences change in a key which starts with the collapse- prefix.
1056
     * The cache is NOT invalidated on the FoldType set change, as if the foldtype set changes, either new FoldTypes
1057
     * appear (they will enter the cache eventually), or the obsolete FoldTypes will not be used in the future,
1058
     * so they may rote in the cache until the Component is closed.
1059
     */
1060
    private volatile Map<FoldType, Boolean>  initialFoldState = new HashMap<FoldType, Boolean>();
1061
    
1062
    /**
1063
     * Listener on fold preferences.
1064
     */
1065
    private PreferenceChangeListener weakPrefL;
1066
    
1067
    /**
1068
     * Returns the cached value for initial folding state of the specific type. The method may be only
1069
     * called under a lock, since it populates the cache; no concurrency is permitted.
1070
     * 
1071
     * @param ft the FoldType to inspect
1072
     * @return true, if the fold should be collapsed initially.
1073
     */
1074
    public boolean getInitialFoldState(FoldType ft) {
1075
        Boolean b = initialFoldState.get(ft);
1076
        if (b != null) {
1077
            return b;
1078
        }
1079
        b = FoldUtilities.isAutoCollapsed(ft, hierarchy);
1080
        initialFoldState.put(ft, b);
1081
        return b;
1082
    }
1083
    
1084
    /**
1085
     * Obtains Preferences that control folding for this Hierarchy.
1086
     * 
1087
     * @return Preferences object
1088
     */
1089
    public Preferences getFoldPreferences() {
1090
        if (foldPreferences == null) {
1091
            String mimeType = getMimeType();
1092
            // internally does MimeLookup lookup(Preferences.class)
1093
            Preferences prefs = LegacySettingsSync.get().processMime(mimeType);
1094
            if ("".equals(mimeType)) {
1095
                // do not cache; typically the editor kit will be changed to something other
1096
                return prefs;
1097
            }
1098
            foldPreferences = prefs;
1099
            weakPrefL = WeakListeners.create(PreferenceChangeListener.class, new PreferenceChangeListener() {
1100
                @Override
1101
                public void preferenceChange(PreferenceChangeEvent evt) {
1102
                    if (evt.getKey().startsWith(FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX)) {
1103
                        if (!initialFoldState.isEmpty()) {
1104
                            initialFoldState = new HashMap<FoldType, Boolean>();
1105
                        }
1106
                    }
1107
                }
1108
            }, foldPreferences);
1109
            foldPreferences.addPreferenceChangeListener(weakPrefL);
1110
        }
1111
        return foldPreferences;
1112
    }
999
1113
1000
}
1114
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldHierarchyTransactionImpl.java (-1 / +1 lines)
Lines 242-248 Link Here
242
                updateAffectedOffsets(fold);
242
                updateAffectedOffsets(fold);
243
                int startOffset = change.getOriginalStartOffset();
243
                int startOffset = change.getOriginalStartOffset();
244
                int endOffset = change.getOriginalEndOffset();
244
                int endOffset = change.getOriginalEndOffset();
245
                assert (startOffset <= endOffset) : "startOffset=" + startOffset + " > endOffset=" + endOffset; // NOI18N;
245
                assert (endOffset < 0 || startOffset <= endOffset) : "startOffset=" + startOffset + " > endOffset=" + endOffset; // NOI18N;
246
                if (startOffset != -1) {
246
                if (startOffset != -1) {
247
                    updateAffectedStartOffset(startOffset);
247
                    updateAffectedStartOffset(startOffset);
248
                }
248
                }
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldOperationImpl.java (-1 / +477 lines)
Lines 44-59 Link Here
44
44
45
package org.netbeans.modules.editor.fold;
45
package org.netbeans.modules.editor.fold;
46
46
47
import java.util.ArrayList;
48
import java.util.Collection;
49
import java.util.Collections;
50
import java.util.Comparator;
51
import java.util.HashMap;
52
import java.util.Iterator;
53
import java.util.LinkedList;
54
import java.util.List;
55
import java.util.ListIterator;
56
import java.util.Map;
57
import java.util.NoSuchElementException;
58
import java.util.Set;
59
import java.util.Stack;
47
import java.util.logging.Level;
60
import java.util.logging.Level;
48
import java.util.logging.Logger;
61
import java.util.logging.Logger;
62
import java.util.prefs.Preferences;
49
import javax.swing.event.DocumentEvent;
63
import javax.swing.event.DocumentEvent;
50
import javax.swing.text.BadLocationException;
64
import javax.swing.text.BadLocationException;
51
import javax.swing.text.Document;
65
import javax.swing.text.Document;
66
import javax.swing.text.EditorKit;
67
import javax.swing.text.JTextComponent;
52
import org.netbeans.spi.editor.fold.FoldManager;
68
import org.netbeans.spi.editor.fold.FoldManager;
53
import org.netbeans.api.editor.fold.Fold;
69
import org.netbeans.api.editor.fold.Fold;
54
import org.netbeans.api.editor.fold.FoldHierarchy;
70
import org.netbeans.api.editor.fold.FoldHierarchy;
71
import org.netbeans.spi.editor.fold.FoldInfo;
72
import org.netbeans.api.editor.fold.FoldStateChange;
73
import org.netbeans.api.editor.fold.FoldTemplate;
55
import org.netbeans.spi.editor.fold.FoldOperation;
74
import org.netbeans.spi.editor.fold.FoldOperation;
56
import org.netbeans.api.editor.fold.FoldType;
75
import org.netbeans.api.editor.fold.FoldType;
76
import org.netbeans.api.editor.mimelookup.MimeLookup;
77
import org.netbeans.editor.BaseDocument;
78
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
79
import org.openide.util.Exceptions;
57
80
58
81
59
/**
82
/**
Lines 204-209 Link Here
204
    throws BadLocationException {
227
    throws BadLocationException {
205
        checkFoldOperation(fold);
228
        checkFoldOperation(fold);
206
        int origEndOffset = fold.getEndOffset();
229
        int origEndOffset = fold.getEndOffset();
230
        if (origEndOffset == endOffset) {
231
            return;
232
        }
207
        ApiPackageAccessor api = getAccessor();
233
        ApiPackageAccessor api = getAccessor();
208
        api.foldSetEndOffset(fold, getDocument(), endOffset);
234
        api.foldSetEndOffset(fold, getDocument(), endOffset);
209
        api.foldStateChangeEndOffsetChanged(transaction.getFoldStateChange(fold), origEndOffset);
235
        api.foldStateChangeEndOffsetChanged(transaction.getFoldStateChange(fold), origEndOffset);
Lines 235-241 Link Here
235
    public boolean isReleased() {
261
    public boolean isReleased() {
236
        return released;
262
        return released;
237
    }
263
    }
238
264
    
265
    /**
266
     * Enumerates all folds contributed by this Operation, whether blocked or active.
267
     * 
268
     * @return 
269
     */
270
    public Iterator<Fold>   foldIterator() {
271
        return new BI(new DFSI(execution.getRootFold()));
272
    }
273
    
239
    private void checkFoldOperation(Fold fold) {
274
    private void checkFoldOperation(Fold fold) {
240
        FoldOperationImpl foldOperation = getAccessor().foldGetOperation(fold);
275
        FoldOperationImpl foldOperation = getAccessor().foldGetOperation(fold);
241
        if (foldOperation != this) {
276
        if (foldOperation != this) {
Lines 250-254 Link Here
250
    private static ApiPackageAccessor getAccessor() {
285
    private static ApiPackageAccessor getAccessor() {
251
        return ApiPackageAccessor.get();
286
        return ApiPackageAccessor.get();
252
    }
287
    }
288
    
289
    /**
290
     * Compares two folds A, B. Fold "A" precedes "B", if and only if:
291
     * <ul>
292
     * <li>A fully encloses B, or
293
     * <li>A starts before B, or
294
     * <li>A and B occupy the same range, and A's priority is lower
295
     * </ul>
296
     * 
297
     * @param a fold to compare
298
     * @param b fold to compare
299
     * @return -1, 1, 0 as appropriate for a Comparator
300
     */
301
    private static final Comparator<Fold> FOLD_COMPARATOR = new Comparator<Fold>() {
302
        @Override
303
        public int compare(Fold a, Fold b) {
304
            int diff = a.getStartOffset() - b.getStartOffset();
305
            if (diff < 0) {
306
                return -1;
307
            }
308
            int diff2 = b.getEndOffset() - a.getEndOffset();
309
            if (diff2 != 0 || diff != 0) {
310
                return 1;
311
            }
312
            ApiPackageAccessor accessor = getAccessor();
313
            return accessor.foldGetOperation(a).getPriority() - accessor.foldGetOperation(b).getPriority();
314
        }
315
    };
253
316
317
    /**
318
     * Level of depth-first traversal
319
     */
320
    static class PS {
321
        private Fold    parent;
322
        private int     childIndex = -1;
323
        private PS      next;
324
        
325
        PS(Fold parent, PS next) {
326
            this.parent = parent;
327
            this.next = next;
328
        }
329
    }
330
    
331
    /**
332
     * Implmentation of depth-first pre-order traversal through Fold hierarchy.
333
     * Each level is iterated in the fold order = start offset order.
334
     */
335
    private class DFSI implements Iterator<Fold> {
336
        PS  level;
337
        
338
        private DFSI(Fold root) {
339
            level = new PS(root, null);
340
        }
341
        
342
        @Override
343
        public Fold next() {
344
            if (!hasNext()) {
345
                throw new NoSuchElementException();
346
            }
347
            if (level.childIndex == -1) {
348
                level.childIndex++;
349
                return level.parent;
350
            }
351
            // note that hasNext also pops levels, as necessary, so there's a
352
            // level, which is not yet exhausted.
353
            Fold f = level.parent.getFold(level.childIndex++);
354
            if (f.getFoldCount() > 0) {
355
                level = new PS(f, level);
356
                level.childIndex++;
357
                return level.parent;
358
            }
359
            return f;
360
        }
361
        
362
        @Override
363
        public boolean hasNext() {
364
            while (level != null) {
365
                if (level.childIndex == -1) {
366
                    return true;
367
                } else if (level.childIndex >= level.parent.getFoldCount()) {
368
                    level = level.next;
369
                } else {
370
                    return true;
371
                }
372
            }
373
            return false;
374
        }
375
        
376
        public void remove() {
377
            throw new IllegalArgumentException();
378
        }
379
    }
380
    
381
    /**
382
     * Iterator, which processes all blocked folds along with their blocker.
383
     * The blocker + blockers folds are ordered using the fold order. Results
384
     * are filtered to contain just Folds produced by this Operation. Folds 
385
     * owned by other operations/executions are skipped.
386
     * <p/>
387
     * Note that blocked folds do not form a hierarchy; they were removed from
388
     * the fold hierarchy when it was decided to block those folds. So prior to
389
     * iterating further in FoldHierarchy, all (recursively) blocked folds must
390
     * be processed.
391
     */
392
    private class BI implements Iterator<Fold> {
393
        private Iterator<Fold>  dfsi;
394
        private Iterator<Fold>  blockedFolds;
395
        private Fold ret;
396
        private Stack<Object[]> blockStack = new Stack<Object[]>();
397
        private Fold blocker;
398
399
        public BI(Iterator<Fold> dfsi) {
400
            this.dfsi = dfsi;
401
        }
402
        
403
        /**
404
         * If fold 'f' blocks some other folds, those blocked folds will b processed
405
         * instead of 'f'. f will be mixed among and ordered with its blocked folds, so the
406
         * entire chain will be processed in the document order.
407
         * 
408
         * @param f
409
         * @return true, if blocked folds should be processed.
410
         */
411
        private boolean processBlocked(Fold f) {
412
            if (f == blocker) {
413
                return false;
414
            }
415
            Collection<Fold> blocked = execution.getBlockedFolds(f);
416
            if (blocked != null && !blocked.isEmpty()) {
417
                List<Fold> blockedSorted = new ArrayList<Fold>(blocked.size() + 1);
418
                blockedSorted.addAll(blocked);
419
                // enumerate together with blocked ones
420
                blockedSorted.add(f);
421
                Collections.sort(blockedSorted, FOLD_COMPARATOR);
422
                blockStack.push(new Object[] { blockedFolds, blocker});
423
                blockedFolds = blockedSorted.iterator();
424
                blocker = f;
425
                return true;
426
            } else {
427
                return false;
428
            }
429
        }
430
        
431
        @Override
432
        public void remove() {
433
            throw new UnsupportedOperationException();
434
        }
435
        
436
        @Override
437
        public Fold next() {
438
            if (!hasNext()) {
439
                throw new NoSuchElementException();
440
            }
441
            Fold f = ret;
442
            ret = null;
443
            return f;
444
        }
445
        
446
        @Override
447
        public boolean hasNext() {
448
            if (ret != null) {
449
                return true;
450
            }
451
            if (blockedFolds != null) {
452
                while (blockedFolds.hasNext()) {
453
                    Fold f = blockedFolds.next();
454
                    if (processBlocked(f)) {
455
                        // continue with a different level of blocking
456
                        continue;
457
                    }
458
                    if (operation.owns(f)) {
459
                        ret = f;
460
                        return true;
461
                    }
462
                }
463
                blockedFolds = null;
464
            }
465
            if (!blockStack.isEmpty()) {
466
                Object[] o = blockStack.pop();
467
                blocker = (Fold)o[1];
468
                blockedFolds = (Iterator<Fold>)o[0];
469
                return hasNext();
470
            }
471
            
472
            while (dfsi.hasNext()) {
473
                Fold f = dfsi.next();
474
                if (processBlocked(f)) {
475
                    return hasNext();
476
                }
477
                if (operation.owns(f)) {
478
                    ret = f;
479
                    return true;
480
                }
481
            }
482
            return false;
483
        }
484
    }
485
    
486
    public Map<FoldInfo, Fold> update(Collection<FoldInfo> fi, Collection<Fold> removed, Collection<FoldInfo> created) throws BadLocationException {
487
        Refresher r = new Refresher(fi);
488
        if (!isReleased()) {
489
            if (!execution.isLockedByCaller()) {
490
                throw new IllegalStateException("Update must run under FoldHierarchy lock");
491
            }
492
            r.run();
493
        } else {
494
            return null;
495
        }
496
        if (removed != null) {
497
            removed.addAll(r.toRemove);
498
        }
499
        if (created != null) {
500
            created.addAll(r.toAdd);
501
        }
502
        return r.currentFolds;
503
    }
504
    
505
    public boolean getInitialState(FoldType ft) {
506
        return execution.getInitialFoldState(ft);
507
    }
508
509
    private class Refresher implements Comparator<FoldInfo> {
510
        private Collection<FoldInfo>    foldInfos;
511
        private Collection<Fold>        toRemove = new ArrayList<Fold>();
512
        private Collection<FoldInfo>    toAdd = new ArrayList<FoldInfo>();
513
        private Map<FoldInfo, Fold>     currentFolds = new HashMap<FoldInfo, Fold>();
514
515
        /**
516
         * Transaction which covers the update
517
         */
518
        private FoldHierarchyTransactionImpl tran;
519
520
        public Refresher(Collection<FoldInfo> foldInfos) {
521
            this.foldInfos = foldInfos;
522
        }
523
        
524
        @Override
525
        public int compare(FoldInfo a, FoldInfo b) {
526
            int diff = a.getStart() - b.getStart();
527
            if (diff < 0) {
528
                return -1;
529
            }
530
            int diff2 = b.getEnd() - a.getEnd();
531
            if (diff2 != 0 || diff != 0) {
532
                return 1;
533
            }
534
            return 0;
535
        }
536
        
537
            
538
        private int compare(FoldInfo info, Fold f) {
539
            if (info == null) {
540
                if (f == null) {
541
                    return 0;
542
                } else {
543
                    return 1;
544
                }
545
            } else if (f == null) {
546
                return -1;
547
            }
548
549
            int diff = info.getStart() - f.getStartOffset();
550
            if (diff != 0) {
551
                return diff;
552
            }
553
            diff = f.getEndOffset() - info.getEnd();
554
            if (diff != 0) {
555
                return diff;
556
            }
557
            if (info.getType() == f.getType()) {
558
                return 0;
559
            }
560
            return info.getType().code().compareToIgnoreCase(f.getType().code());
561
        }
562
        
563
        private Iterator<Fold>  foldIt;
564
        private Iterator<FoldInfo>  infoIt;
565
        private FoldInfo nextInfo;
566
        
567
        private FoldInfo ni() {
568
            if (nextInfo != null) {
569
                FoldInfo f = nextInfo;
570
                nextInfo = null;
571
                return f;
572
            }
573
            return infoIt.hasNext() ? infoIt.next() : null;
574
        }
575
        
576
        private FoldInfo peek() {
577
            FoldInfo f = ni();
578
            nextInfo = f;
579
            return f;
580
        }
581
        
582
        private boolean containsOneAnother(FoldInfo i, Fold f) {
583
            int s1 = i.getStart();
584
            int s2 = f.getStartOffset();
585
            int e1 = i.getEnd();
586
            int e2 = f.getEndOffset();
587
            
588
            return ((s1 >= s2 && e2 >= e1)  ||
589
                (s2 >= s1 && e1 >= e2));
590
        }
591
        
592
        private boolean nextSameRange(FoldInfo i, Fold f) {
593
            if (i == null || f == null) {
594
                return false;
595
            }
596
            if (i.getType() != f.getType() || !containsOneAnother(i, f)) {
597
                return false;
598
            }
599
            FoldInfo next = peek();
600
            if (next == null) {
601
                return true;
602
            }
603
            return next.getStart() != f.getStartOffset() || next.getEnd() != f.getEndOffset();
604
        }
605
        
606
        private boolean containsSame(FoldInfo i, Fold f) {
607
            if (i == null || f == null || i.getType() != f.getType()) {
608
                return false;
609
            }
610
            return containsOneAnother(i, f);
611
        }
612
        
613
        public void run() throws BadLocationException {
614
            // first order the supplied folds:
615
            List ll = new ArrayList<FoldInfo>(foldInfos);
616
            Collections.sort(ll, this);
617
            
618
            foldIt = foldIterator();
619
            infoIt = ll.iterator();
620
            
621
            Fold f = foldIt.hasNext() ? foldIt.next() : null;
622
            FoldInfo i = infoIt.hasNext() ? infoIt.next() : null;
623
            
624
            tran = openTransaction();
625
626
            
627
            try {
628
                while (f != null || i != null) {
629
                    if (LOG.isLoggable(Level.FINEST)) {
630
                        LOG.finest("Fold = " + f + ", FoldInfo = " + i);
631
                    }
632
                    int action = compare(i, f);
633
                    if (action < 0 && !nextSameRange(i, f)) {
634
                        // create a new fold from the FoldInfo
635
                        toAdd.add(i);
636
                        i = ni();
637
                        if (LOG.isLoggable(Level.FINEST)) {
638
                            LOG.finest("Advanced info, next = " + i);
639
                        }
640
                        continue;
641
                    } else if (action > 0 && !containsSame(i, f)) {
642
                        toRemove.add(f);
643
                        f = foldIt.hasNext() ? foldIt.next() : null;
644
                        if (LOG.isLoggable(Level.FINEST)) {
645
                            LOG.finest("Advanced fold, next = " + f);
646
                        }
647
                        continue;
648
                    }
649
650
                    update(f, i);
651
                    currentFolds.put(i, f);
652
                    i = ni();
653
                    f = foldIt.hasNext() ? foldIt.next() : null;
654
                    if (LOG.isLoggable(Level.FINEST)) {
655
                        LOG.finest("Advanced both info & fold");
656
                    }
657
                }
658
                for (Fold fold : toRemove) {
659
                    if (LOG.isLoggable(Level.FINEST)) {
660
                        LOG.finest("Removing: " + f);
661
                    }
662
                    if (fold.getParent() != null) {
663
                        removeFromHierarchy(fold, tran);
664
                    }
665
                }
666
                for (FoldInfo info : toAdd) {
667
                    try {
668
                        currentFolds.put(info, getOperation().addToHierarchy(
669
                                info.getType(), 
670
                                info.getStart(), info.getEnd(),
671
                                info.getCollapsed(), 
672
                                info.getTemplate(),
673
                                info.getDescriptionOverride(),
674
                                info.getExtraInfo(),
675
                                tran.getTransaction()));
676
                        if (LOG.isLoggable(Level.FINEST)) {
677
                            LOG.finest("Adding: " + i);
678
                        }
679
                    } catch (BadLocationException ex) {
680
                        Exceptions.printStackTrace(ex);
681
                    }
682
                }
683
            } finally {
684
                tran.commit();
685
            }
686
        }
687
        
688
        public Fold update(Fold f, FoldInfo info) throws BadLocationException {
689
            this.fsch = null;
690
            int offs = f.getStartOffset();
691
            ApiPackageAccessor acc = getAccessor();
692
            if (info.getStart() != offs) {
693
                acc.foldSetStartOffset(f, getDocument(), info.getStart());
694
                acc.foldStateChangeStartOffsetChanged(getFSCH(f), offs);
695
            }
696
            offs = f.getEndOffset();
697
            if (info.getEnd() != offs) {
698
                acc.foldSetEndOffset(f, getDocument(), info.getEnd());
699
                acc.foldStateChangeEndOffsetChanged(getFSCH(f), offs);
700
            }
701
            String desc = info.getDescriptionOverride();
702
            if (desc == null) {
703
                desc = info.getTemplate().getDescription();
704
            }
705
            if (!f.getDescription().equals(desc)) {
706
                acc.foldSetDescription(f, desc);
707
                acc.foldStateChangeDescriptionChanged(getFSCH(f));
708
            }
709
            if (info.getCollapsed() != null && f.isCollapsed() != info.getCollapsed()) {
710
                getAccessor().foldSetCollapsed(f, info.getCollapsed());
711
                getAccessor().foldStateChangeCollapsedChanged(getFSCH(f));
712
            }
713
            return f;
714
        }
715
716
        /**
717
         * FoldStateChange for the current fold being updated;
718
         * just an optimization.
719
         */
720
        private FoldStateChange fsch;
721
722
        private FoldStateChange getFSCH(Fold f) {
723
            if (fsch != null) {
724
                return fsch;
725
            }
726
            return fsch = tran.getFoldStateChange(f);
727
        }
728
729
    }
254
}
730
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldRegistry.java (+343 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 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 2011 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.fold;
43
44
import java.lang.ref.Reference;
45
import java.lang.ref.SoftReference;
46
import java.lang.ref.WeakReference;
47
import java.util.ArrayList;
48
import java.util.Collection;
49
import java.util.Collections;
50
import java.util.HashMap;
51
import java.util.Iterator;
52
import java.util.LinkedHashMap;
53
import java.util.LinkedHashSet;
54
import java.util.Map;
55
import java.util.Set;
56
import java.util.logging.Level;
57
import java.util.logging.Logger;
58
import javax.swing.event.ChangeEvent;
59
import javax.swing.event.ChangeListener;
60
import org.netbeans.api.editor.fold.FoldType;
61
import org.netbeans.api.editor.mimelookup.MimeLookup;
62
import org.netbeans.api.editor.mimelookup.MimePath;
63
import org.netbeans.spi.editor.fold.FoldTypeProvider;
64
import org.openide.util.Lookup;
65
import org.openide.util.Lookup.Result;
66
import org.openide.util.LookupEvent;
67
import org.openide.util.LookupListener;
68
import org.openide.util.WeakListeners;
69
70
/**
71
 * MIMEtype specific registry, which collects values for a single enum type (enumType). 
72
 * It looks up
73
 * {@link ExtEnum.MimeEnumProvider} in the {@link MimeLookup}. It supports efficient
74
 * valueOf lookup and changes to the set of the values.
75
 * <p/>
76
 * Clients <b>must not cache</b> the set of values and treat it as
77
 * an universe. As modules are enabled / disabled,
78
 * the set of available values may change and further queries to {@link #valueSet}
79
 * may return different results.
80
 * 
81
 * <p/>
82
 * Note: this class was trimmed down from generic enum registry. When other extensible
83
 * enums are introduced, fold registry will just wrap the real enum. Some constructions may
84
 * be unnecessary general ;)
85
 *
86
 * @author sdedic
87
 */
88
public final class FoldRegistry  {
89
    private static final Logger LOG = Logger.getLogger(FoldRegistry.class.getName());
90
91
    /**
92
     * The enum type being registered
93
     */
94
    private Class  enumType;
95
96
    /**
97
     * For a MimePath, holds set of applicable enums and value-to-enum map
98
     */
99
    private final Map<MimePath, R>  enums = new HashMap<MimePath, R>();
100
    
101
    private FoldRegistry(Class enumType) {
102
        this.enumType = enumType;
103
    }
104
    
105
    private static volatile Reference<FoldRegistry>     INSTANCE = new WeakReference(null);
106
    
107
    public static FoldRegistry get() {
108
        FoldRegistry fr = INSTANCE.get();
109
        if (fr == null) {
110
            synchronized (FoldRegistry.class) {
111
                fr = INSTANCE.get();
112
                if (fr == null) {
113
                    fr = new FoldRegistry(FoldType.class);
114
                    INSTANCE = new SoftReference(fr);
115
                }
116
            }
117
        }
118
        return fr;
119
    }
120
    
121
    public Collection<FoldType> values(MimePath mime) {
122
        return get(mime).enums;
123
    }
124
    
125
    public FoldType valueOf(MimePath mime, String val) {
126
        return get(mime).valueOf(val);
127
    }
128
    
129
    public FoldType.Domain getDomain(MimePath mime) {
130
        return get(mime);
131
    }
132
    
133
    private R get(MimePath mime) {
134
        synchronized (enums) {
135
            R r = enums.get(mime);
136
            if (r != null) {
137
                return r;
138
            }
139
        }
140
        
141
        return refreshMime(mime, null);
142
    }
143
    
144
    /**
145
     * Representation of the domain for a specific MIMEtype. It listens on a Lookup.Result
146
     * and refreshes the contents iff lookup changes - that is if a module is (un)loaded,
147
     * or the value provider set changes.
148
     * <p/>
149
     * Updates to enums must occur under synchro
150
     * 
151
     */
152
    private static class R implements LookupListener, FoldType.Domain {
153
        final FoldRegistry      dom;
154
        final MimePath          mime;
155
        /**
156
         * Listens on mime lookup 
157
         */
158
        final Lookup.Result     result;
159
        /**
160
         * Listens on parent's mime lookup, possibly null
161
         */
162
        final Lookup.Result     result2;
163
        Collection<ChangeListener> listeners;
164
        
165
        /**
166
         * Set of all enum values
167
         */
168
        // @GuardedBy(this)
169
        private Set<FoldType>         enums;
170
        
171
        /**
172
         * Allows to faster map an enum to a value. Produced lazily by map(), cleared by reset().
173
         */
174
        volatile Map<String, FoldType>      valueMap;
175
        
176
        public R(FoldRegistry dom, MimePath mime, Result result, Result result2) {
177
            this.dom = dom;
178
            this.mime = mime;
179
            this.result = result;
180
            this.result2 = result2;
181
            result.addLookupListener(WeakListeners.create(LookupListener.class, 
182
                    this, result));
183
            if (result2 != null) {
184
                result2.addLookupListener(WeakListeners.create(LookupListener.class, 
185
                        this, result2));
186
            }
187
        }
188
189
        @Override
190
        public void resultChanged(LookupEvent ev) {
191
            dom.refreshMime(mime, this);
192
        }
193
194
        @Override
195
        public Collection<FoldType> values() {
196
            return Collections.unmodifiableCollection(map().values());
197
        }
198
        
199
        private Map<String, FoldType> map() {
200
            Map<String, FoldType> m;
201
            
202
            m = valueMap;
203
            if (m == null) {
204
                synchronized (this) {
205
                    if (valueMap != null) {
206
                        return valueMap;
207
                    }
208
                    Set<FoldType> vals = enums;
209
                    m = new LinkedHashMap<String, FoldType>();
210
                    for (FoldType e : vals) {
211
                        FoldType old = m.put(e.code(), e);
212
                        if (old != null) {
213
                            throw new IllegalArgumentException("Two fold types share the same code: " + old + " and " + e);
214
                        }
215
                    }
216
                    this.valueMap = m;
217
                }
218
            }
219
            return m;
220
        }
221
        
222
        @Override
223
        public FoldType  valueOf(String val) {
224
            return map().get(val);
225
        }
226
227
        @Override
228
        public void addChangeListener(ChangeListener l) {
229
            synchronized (this) {
230
                if (listeners == null) {
231
                    listeners = new ArrayList<ChangeListener>();
232
                }
233
                listeners.add(l);
234
            }
235
        }
236
237
        @Override
238
        public void removeChangeListener(ChangeListener l) {
239
            synchronized (this) {
240
                if (listeners != null) {
241
                    listeners.remove(l);
242
                }
243
            }
244
        }
245
        
246
        void reset(Set<FoldType> allValues) {
247
            ChangeListener[] ll;
248
            synchronized (this) {
249
                enums = allValues;
250
                valueMap = null;
251
                if (listeners == null) {
252
                    return;
253
                }
254
                ll = listeners.toArray(new ChangeListener[listeners.size()]);
255
            }
256
            ChangeEvent e = new ChangeEvent(this);
257
            for (int i = 0; i < ll.length; i++) {
258
                ll[i].stateChanged(e);
259
            }
260
        }
261
    }
262
    
263
    
264
    private R refreshMime(MimePath mime, R holder) {
265
        Lookup.Result<FoldTypeProvider> r;
266
        Lookup.Result<FoldTypeProvider> pr = null;
267
268
        if (holder == null) {
269
            r = MimeLookup.getLookup(mime).lookup(
270
                    new Lookup.Template(
271
                        FoldTypeProvider.class
272
                    )
273
            );
274
            // get the inherited MimePaths:
275
            String parentMime = mime.getInheritedType();
276
            if (LOG.isLoggable(Level.FINER)) {
277
                LOG.finer("Get providers for " + mime + ", parent " + parentMime);
278
            }
279
            if (parentMime != null) {
280
                pr = MimeLookup.getLookup(parentMime).lookup(
281
                        new Lookup.Template(
282
                            FoldTypeProvider.class
283
                        )
284
                );
285
            }
286
        } else {
287
            r = holder.result;
288
            pr = holder.result2;
289
        }
290
        
291
        
292
        Collection<? extends FoldTypeProvider> providers = r.allInstances();
293
        Collection<? extends FoldTypeProvider> parentProvs = pr == null ? Collections.<FoldTypeProvider>emptySet() : pr.allInstances();
294
        
295
        for (Iterator<? extends FoldTypeProvider> it = parentProvs.iterator(); it.hasNext(); ) {
296
            FoldTypeProvider p = it.next();
297
            if (p.inheritable()) {
298
                if (LOG.isLoggable(Level.FINER)) {
299
                    LOG.log(Level.FINER, "Inheritable: " + p);
300
                }
301
                // leave only providers, which are !inheritable, so they'll be ignored.
302
                it.remove();
303
            }
304
        }
305
        
306
        Set<FoldType> allValues = new LinkedHashSet<FoldType>();
307
        
308
        for (FoldTypeProvider p : providers) {
309
            if (parentProvs.contains(p)) {
310
                if (LOG.isLoggable(Level.FINER)) {
311
                    LOG.log(Level.FINER, "Removing not inheritable: " + p);
312
                }
313
                // provider registered for parent, but inheritance is disallowed.
314
                continue;
315
            }
316
            
317
            Collection<FoldType> vals = p.getValues(enumType);
318
            if (vals != null) {
319
                // check that none enum overrides another one
320
                allValues.addAll(vals);
321
            }
322
        }
323
        
324
        boolean register = holder == null;
325
        if (holder == null) {
326
            holder = new R(this, mime, r, pr);
327
        }
328
        // sync
329
        holder.reset(allValues);
330
331
        if (register) {
332
            synchronized(enums) {
333
                R oldHolder = enums.put(mime, holder);
334
                if (oldHolder != null) {
335
                    // unlikely, but can happen.
336
                    enums.put(mime, oldHolder);
337
                    holder = oldHolder;
338
                }
339
            }
340
        }
341
        return holder;
342
    }
343
}
(-)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/FoldUtilitiesImpl.java (+49 lines)
Lines 48-60 Link Here
48
import java.util.Collection;
48
import java.util.Collection;
49
import java.util.Iterator;
49
import java.util.Iterator;
50
import java.util.List;
50
import java.util.List;
51
import java.util.prefs.Preferences;
51
import javax.swing.text.AbstractDocument;
52
import javax.swing.text.AbstractDocument;
52
import javax.swing.text.Document;
53
import javax.swing.text.Document;
53
import org.netbeans.api.editor.fold.Fold;
54
import org.netbeans.api.editor.fold.Fold;
54
import org.netbeans.api.editor.fold.FoldHierarchy;
55
import org.netbeans.api.editor.fold.FoldHierarchy;
55
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
56
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
56
import org.netbeans.api.editor.fold.FoldStateChange;
57
import org.netbeans.api.editor.fold.FoldStateChange;
58
import org.netbeans.api.editor.fold.FoldType;
57
import org.netbeans.api.editor.fold.FoldUtilities;
59
import org.netbeans.api.editor.fold.FoldUtilities;
60
import org.netbeans.api.editor.mimelookup.MimeLookup;
58
61
59
/**
62
/**
60
 * Implementations of methods from {@link org.netbeans.api.editor.fold.FoldUtilities}.
63
 * Implementations of methods from {@link org.netbeans.api.editor.fold.FoldUtilities}.
Lines 64-74 Link Here
64
 */
67
 */
65
68
66
public final class FoldUtilitiesImpl {
69
public final class FoldUtilitiesImpl {
70
    /**
71
     * Prefix used for initial-collapse folding preferences.
72
     */
73
    public static final String PREF_COLLAPSE_PREFIX = "code-folding-collapse-";
74
    
75
    /**
76
     * Preference key name for "use defaults" (default: true)
77
     */
78
    public static final String PREF_OVERRIDE_DEFAULTS = "code-folding-use-defaults"; // NOI18N
79
    
80
    /**
81
     * Preference key name for enable code folding (default: true)
82
     */
83
    public static final String PREF_CODE_FOLDING_ENABLED = "code-folding-enable"; // NOI18N
84
    
85
    /**
86
     * Preference key for "Content preview" display option (default: true).
87
     */
88
    public static final String PREF_CONTENT_PREVIEW = "code-folding-content.preview"; // NOI18N
89
90
    /**
91
     * Preference key for "Show summary" display option (default: true).
92
     */
93
    public static final String PREF_CONTENT_SUMMARY = "code-folding-content.summary"; // NOI18N
67
    
94
    
68
    private FoldUtilitiesImpl() {
95
    private FoldUtilitiesImpl() {
69
        // No instances
96
        // No instances
70
    }
97
    }
71
    
98
    
99
    public static boolean isFoldingEnabled(String mime) {
100
        Preferences prefs = MimeLookup.getLookup(mime).lookup(Preferences.class);
101
        return prefs == null ? false : prefs.getBoolean(PREF_CODE_FOLDING_ENABLED, false);
102
    }
103
    
104
    public static boolean isFoldingEnabled(FoldHierarchy h) {
105
        Preferences p = ApiPackageAccessor.get().foldGetExecution(h).getFoldPreferences();
106
        return p.getBoolean(PREF_CODE_FOLDING_ENABLED, false);
107
    }
108
109
    public static boolean isAutoCollapsed(FoldType ft, FoldHierarchy h) {
110
        Preferences p = ApiPackageAccessor.get().foldGetExecution(h).getFoldPreferences();
111
        FoldType parent = ft.parent();
112
        return p.getBoolean(
113
            PREF_COLLAPSE_PREFIX + ft.code(),
114
            parent == null ? 
115
                false : 
116
                // search for the parent, if parent is defined.
117
                p.getBoolean(PREF_COLLAPSE_PREFIX + parent.code(), false)
118
        );
119
    }
120
    
72
    public static void collapseOrExpand(FoldHierarchy hierarchy, Collection foldTypes,
121
    public static void collapseOrExpand(FoldHierarchy hierarchy, Collection foldTypes,
73
    boolean collapse) {
122
    boolean collapse) {
74
123
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldView.java (-358 lines)
Lines 1-358 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.Color;
48
import java.awt.Container;
49
import java.awt.Font;
50
import java.awt.Graphics2D;
51
import java.awt.Rectangle;
52
import java.awt.Shape;
53
import java.awt.font.FontRenderContext;
54
import java.awt.font.TextHitInfo;
55
import java.awt.font.TextLayout;
56
import java.awt.geom.Rectangle2D;
57
import java.util.logging.Logger;
58
import javax.swing.JComponent;
59
import javax.swing.JEditorPane;
60
import javax.swing.text.AttributeSet;
61
import javax.swing.text.BadLocationException;
62
import javax.swing.text.Document;
63
import javax.swing.text.EditorKit;
64
import javax.swing.text.Element;
65
import javax.swing.text.JTextComponent;
66
import javax.swing.text.Position;
67
import javax.swing.text.Position.Bias;
68
import javax.swing.text.StyleConstants;
69
import javax.swing.text.View;
70
import org.netbeans.api.editor.fold.Fold;
71
import org.netbeans.api.editor.settings.FontColorNames;
72
import org.netbeans.api.editor.settings.FontColorSettings;
73
import org.netbeans.modules.editor.lib2.view.EditorView;
74
import org.netbeans.modules.editor.lib2.view.ViewRenderContext;
75
import org.netbeans.modules.editor.lib2.view.ViewUtils;
76
77
/**
78
 * View with highlights. This is the most used view.
79
 *
80
 * @author Miloslav Metelka
81
 */
82
83
public class FoldView extends EditorView {
84
85
    // -J-Dorg.netbeans.modules.editor.lib2.view.HighlightsView.level=FINE
86
    private static final Logger LOG = Logger.getLogger(FoldView.class.getName());
87
88
    /**
89
     * Extra space added to each side of description text of a fold view.
90
     */
91
    private static final float EXTRA_MARGIN_WIDTH = 3;
92
93
    /** Raw end offset of this view. */
94
    private int rawEndOffset; // 24-super + 4 = 28 bytes
95
96
    /** Length of text occupied by this view. */
97
    private int length; // 28 + 4 = 32 bytes
98
99
    private final JTextComponent textComponent; // 32 + 4 = 36 bytes
100
101
    private final Fold fold; // 36 + 4 = 40 bytes
102
    
103
    private TextLayout collapsedTextLayout; // 40 + 4 = 44 bytes
104
    
105
    private AttributeSet    foldingColors;
106
107
    public FoldView(JTextComponent textComponent, Fold fold, FontColorSettings colorSettings) {
108
        super(null);
109
        int offset = fold.getStartOffset();
110
        int len = fold.getEndOffset() - offset;
111
        assert (len > 0) : "length=" + len + " <= 0"; // NOI18N
112
        this.length = len;
113
        this.textComponent = textComponent;
114
        this.fold = fold;
115
        this.foldingColors = colorSettings.getFontColors(FontColorNames.CODE_FOLDING_COLORING);
116
    }
117
118
    @Override
119
    public float getPreferredSpan(int axis) {
120
        TextLayout textLayout = getTextLayout();
121
        if (textLayout == null) {
122
            return 0f;
123
        }
124
        String desc = fold.getDescription(); // For empty desc a single-space text layout is returned
125
        if (axis == View.X_AXIS) {
126
            return ((desc.length() > 0) ? textLayout.getAdvance() : 0) 
127
                + (2 * EXTRA_MARGIN_WIDTH);
128
        } else {
129
            EditorView.Parent parent = (EditorView.Parent) getParent();
130
            return (parent != null) ? parent.getViewRenderContext().getDefaultRowHeight() : 0f;
131
        }
132
    }
133
134
    @Override
135
    public int getRawEndOffset() {
136
        return rawEndOffset;
137
    }
138
139
    @Override
140
    public void setRawEndOffset(int rawOffset) {
141
        this.rawEndOffset = rawOffset;
142
    }
143
144
    @Override
145
    public int getLength() {
146
        return length;
147
    }
148
149
    @Override
150
    public int getStartOffset() {
151
        return getEndOffset() - getLength();
152
    }
153
154
    @Override
155
    public int getEndOffset() {
156
        EditorView.Parent parent = (EditorView.Parent) getParent();
157
        return (parent != null) ? parent.getViewEndOffset(rawEndOffset) : rawEndOffset;
158
    }
159
160
    @Override
161
    public Document getDocument() {
162
        View parent = getParent();
163
        return (parent != null) ? parent.getDocument() : null;
164
    }
165
166
    @Override
167
    public AttributeSet getAttributes() {
168
        return null;
169
    }
170
171
    private TextLayout getTextLayout() {
172
        if (collapsedTextLayout == null) {
173
            EditorView.Parent parent = (EditorView.Parent) getParent();
174
            ViewRenderContext context = parent.getViewRenderContext();
175
            FontRenderContext frc = context.getFontRenderContext();
176
            assert (frc != null) : "Null FontRenderContext"; // NOI18N
177
            Font font = context.getRenderFont(textComponent.getFont());
178
            String text = fold.getDescription();
179
            if (text.length() == 0) {
180
                text = " "; // Use single space (mainly for height measurement etc.
181
            }
182
            collapsedTextLayout = new TextLayout(text, font, frc);
183
        }
184
        return collapsedTextLayout;
185
    }
186
187
    @Override
188
    public Shape modelToViewChecked(int offset, Shape alloc, Position.Bias bias) {
189
//        TextLayout textLayout = getTextLayout();
190
//        if (textLayout == null) {
191
//            return alloc; // Leave given bounds
192
//        }
193
//        Rectangle2D.Double bounds = ViewUtils.shape2Bounds(alloc);
194
//        return bounds;
195
        return alloc;
196
    }
197
198
    @Override
199
    public int viewToModelChecked(double x, double y, Shape alloc, Position.Bias[] biasReturn) {
200
        int startOffset = getStartOffset();
201
        return startOffset;
202
    }
203
204
    static TextHitInfo x2RelOffset(TextLayout textLayout, float x) {
205
        TextHitInfo hit;
206
        x -= EXTRA_MARGIN_WIDTH;
207
        if (x >= textLayout.getAdvance()) {
208
            hit = TextHitInfo.trailing(textLayout.getCharacterCount());
209
        } else {
210
            hit = textLayout.hitTestChar(x, 0); // What about backward bias -> with higher offsets it may go back visually
211
        }
212
        return hit;
213
214
    }
215
216
    @Override
217
    public int getNextVisualPositionFromChecked(int offset, Bias bias, Shape alloc, int direction, Bias[] biasRet) {
218
        int startOffset = getStartOffset();
219
        int retOffset = -1;
220
        switch (direction) {
221
            case WEST:
222
                if (offset == -1) {
223
                    retOffset = startOffset;
224
                } else {
225
                    retOffset = -1;
226
                }
227
                break;
228
229
            case EAST:
230
                if (offset == -1) {
231
                    retOffset = startOffset;
232
                } else {
233
                    retOffset = -1;
234
                }
235
                break;
236
237
            case NORTH:
238
            case SOUTH:
239
                break;
240
            default:
241
                throw new IllegalArgumentException("Bad direction: " + direction);
242
        }
243
        return retOffset;
244
    }
245
246
    @Override
247
    public JComponent getToolTip(double x, double y, Shape allocation) {
248
        Container container = getContainer();
249
        if (container instanceof JEditorPane) {
250
            JEditorPane editorPane = (JEditorPane) getContainer();
251
            JEditorPane tooltipPane = new JEditorPane();
252
            EditorKit kit = editorPane.getEditorKit();
253
            Document doc = getDocument();
254
            if (kit != null && doc != null) {
255
                Element lineRootElement = doc.getDefaultRootElement();
256
                tooltipPane.putClientProperty(FoldViewFactory.VIEW_FOLDS_EXPANDED_PROPERTY, true);
257
                try {
258
                    // Start-offset of the fold => line start => position
259
                    int lineIndex = lineRootElement.getElementIndex(fold.getStartOffset());
260
                    Position pos = doc.createPosition(
261
                            lineRootElement.getElement(lineIndex).getStartOffset());
262
                    // DocumentView.START_POSITION_PROPERTY
263
                    tooltipPane.putClientProperty("document-view-start-position", pos);
264
                    // End-offset of the fold => line end => position
265
                    lineIndex = lineRootElement.getElementIndex(fold.getEndOffset());
266
                    pos = doc.createPosition(lineRootElement.getElement(lineIndex).getEndOffset());
267
                    // DocumentView.END_POSITION_PROPERTY
268
                    tooltipPane.putClientProperty("document-view-end-position", pos);
269
                    tooltipPane.putClientProperty("document-view-accurate-span", true);
270
                    // Set the same kit and document
271
                    tooltipPane.setEditorKit(kit);
272
                    tooltipPane.setDocument(doc);
273
                    tooltipPane.setEditable(false);
274
                    return new FoldToolTip(editorPane, tooltipPane, getForegroundColor());
275
                } catch (BadLocationException e) {
276
                    // => return null
277
                }
278
            }
279
        }
280
        return null;
281
    }
282
    
283
    private Color getForegroundColor() {
284
        if (foldingColors == null) {
285
            return textComponent.getForeground();
286
        }
287
        Object bgColorObj = foldingColors.getAttribute(StyleConstants.Foreground);
288
        if (bgColorObj instanceof Color) {
289
            return (Color)bgColorObj;
290
        } else {
291
            return textComponent.getForeground();
292
        }
293
    }
294
295
    private Color getBackgroundColor() {
296
        if (foldingColors == null) {
297
            return textComponent.getBackground();
298
        }
299
        Object bgColorObj = foldingColors.getAttribute(StyleConstants.Background);
300
        if (bgColorObj instanceof Color) {
301
            return (Color)bgColorObj;
302
        } else {
303
            return textComponent.getBackground();
304
        }
305
    }
306
307
    @Override
308
    public void paint(Graphics2D g, Shape alloc, Rectangle clipBounds) {
309
        Rectangle2D.Double allocBounds = ViewUtils.shape2Bounds(alloc);
310
        if (allocBounds.intersects(clipBounds)) {
311
            Font origFont = g.getFont();
312
            Color origColor = g.getColor();
313
            Color origBkColor = g.getBackground();
314
            Shape origClip = g.getClip();
315
            try {
316
                // Leave component font
317
                g.setColor(getForegroundColor());
318
                g.setBackground(getBackgroundColor());
319
320
                int xInt = (int) allocBounds.getX();
321
                int yInt = (int) allocBounds.getY();
322
                int endXInt = (int) (allocBounds.getX() + allocBounds.getWidth() - 1);
323
                int endYInt = (int) (allocBounds.getY() + allocBounds.getHeight() - 1);
324
                g.drawRect(xInt, yInt, endXInt - xInt, endYInt - yInt);
325
                g.clearRect(xInt + 1, yInt + 1, endXInt - xInt - 1, endYInt - yInt - 1);
326
                g.clip(alloc);
327
                TextLayout textLayout = getTextLayout();
328
                if (textLayout != null) {
329
                    EditorView.Parent parent = (EditorView.Parent) getParent();
330
                    float ascent = parent.getViewRenderContext().getDefaultAscent();
331
                    String desc = fold.getDescription(); // For empty desc a single-space text layout is returned
332
                    float x = (float) (allocBounds.getX() + EXTRA_MARGIN_WIDTH);
333
                    float y = (float) allocBounds.getY();
334
                    if (desc.length() > 0) {
335
                        
336
                        textLayout.draw(g, x, y + ascent);
337
                    }
338
                }
339
            } finally {
340
                g.setClip(origClip);
341
                g.setBackground(origBkColor);
342
                g.setColor(origColor);
343
                g.setFont(origFont);
344
            }
345
        }
346
    }
347
348
    @Override
349
    protected String getDumpName() {
350
        return "FV";
351
    }
352
353
    @Override
354
    public String toString() {
355
        return appendViewInfo(new StringBuilder(200), 0, "", -1).toString();
356
    }
357
358
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldViewFactory.java (-238 lines)
Lines 1-238 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.util.Iterator;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import javax.swing.text.View;
51
import org.netbeans.api.editor.fold.Fold;
52
import org.netbeans.api.editor.fold.FoldHierarchy;
53
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
54
import org.netbeans.api.editor.fold.FoldHierarchyListener;
55
import org.netbeans.api.editor.fold.FoldUtilities;
56
import org.netbeans.api.editor.mimelookup.MimeLookup;
57
import org.netbeans.api.editor.settings.FontColorSettings;
58
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
59
import org.netbeans.modules.editor.lib2.view.EditorView;
60
import org.netbeans.modules.editor.lib2.view.EditorViewFactory;
61
import org.netbeans.modules.editor.lib2.view.EditorViewFactoryChange;
62
import org.netbeans.modules.editor.lib2.view.ViewUtils;
63
import org.openide.util.Lookup;
64
import org.openide.util.LookupEvent;
65
import org.openide.util.LookupListener;
66
import org.openide.util.WeakListeners;
67
68
/**
69
 * View factory creating views for collapsed folds.
70
 *
71
 * @author Miloslav Metelka
72
 */
73
74
@SuppressWarnings("ClassWithMultipleLoggers")
75
public final class FoldViewFactory extends EditorViewFactory implements FoldHierarchyListener, LookupListener {
76
77
    /**
78
     * Component's client property which can be set to view folds expanded for tooltip fold preview.
79
     */
80
    static final String VIEW_FOLDS_EXPANDED_PROPERTY = "view-folds-expanded"; // NOI18N
81
82
    // -J-Dorg.netbeans.editor.view.change.level=FINE
83
    static final Logger CHANGE_LOG = Logger.getLogger("org.netbeans.editor.view.change");
84
85
    // -J-Dorg.netbeans.modules.editor.fold.FoldViewFactory.level=FINE
86
    private static final Logger LOG = Logger.getLogger(FoldViewFactory.class.getName());
87
88
    static void register() {
89
        EditorViewFactory.registerFactory(new FoldFactory());
90
    }
91
92
    private FoldHierarchy foldHierarchy;
93
94
    private boolean foldHierarchyLocked;
95
96
    private Fold fold;
97
98
    private int foldStartOffset;
99
100
    private Iterator<Fold> collapsedFoldIterator;
101
102
    private boolean viewFoldsExpanded;
103
    
104
    /**
105
     * Composite Color settings from MIME lookup
106
     */
107
    private FontColorSettings   colorSettings;
108
109
    /**
110
     * Lookup results for color settings, being listened for changes.
111
     */
112
    private Lookup.Result       colorSource;
113
114
    public FoldViewFactory(View documentView) {
115
        super(documentView);
116
        foldHierarchy = FoldHierarchy.get(textComponent());
117
        foldHierarchy.addFoldHierarchyListener(this);
118
        viewFoldsExpanded = Boolean.TRUE.equals(textComponent().getClientProperty(VIEW_FOLDS_EXPANDED_PROPERTY));
119
        
120
        String mime = DocumentUtilities.getMimeType(document());
121
        
122
        Lookup lkp = MimeLookup.getLookup(mime);
123
        colorSource = lkp.lookupResult(FontColorSettings.class);
124
        colorSource.addLookupListener(WeakListeners.create(LookupListener.class, this, colorSource));
125
        colorSettings = (FontColorSettings)colorSource.allInstances().iterator().next();
126
    }
127
128
    @Override
129
    public void resultChanged(LookupEvent ev) {
130
        refreshColors();
131
    }
132
133
    private void refreshColors() {
134
        colorSettings = (FontColorSettings)colorSource.allInstances().iterator().next();
135
        document().render(new Runnable() {
136
            @Override
137
            public void run() {
138
                int end = document().getLength();
139
                fireEvent(EditorViewFactoryChange.createList(0, end, EditorViewFactoryChange.Type.CHARACTER_CHANGE));
140
            }
141
        });
142
    }
143
144
    @Override
145
    public void restart(int startOffset, int endOffset, boolean createViews) {
146
        foldHierarchy.lock(); // this.finish() always called in try-finally
147
        foldHierarchyLocked = true;
148
        @SuppressWarnings("unchecked")
149
        Iterator<Fold> it = FoldUtilities.collapsedFoldIterator(foldHierarchy, startOffset, Integer.MAX_VALUE);
150
        collapsedFoldIterator = it;
151
        foldStartOffset = -1; // Make a next call to updateFold() to fetch a fold
152
    }
153
154
    private void updateFold(int offset) {
155
        if (foldStartOffset < offset) {
156
            while (collapsedFoldIterator.hasNext()) {
157
                fold = collapsedFoldIterator.next();
158
                foldStartOffset = fold.getStartOffset();
159
                if (foldStartOffset >= offset) {
160
                    return;
161
                }
162
            }
163
            fold = null;
164
            foldStartOffset = Integer.MAX_VALUE;
165
        }
166
    }
167
168
    @Override
169
    public int nextViewStartOffset(int offset) {
170
        if (!viewFoldsExpanded) {
171
            updateFold(offset);
172
            return foldStartOffset;
173
        }
174
        return Integer.MAX_VALUE;
175
    }
176
177
    @Override
178
    public EditorView createView(int startOffset, int limitOffset, boolean forcedLimit,
179
    EditorView origView, int nextOrigViewOffset) {
180
        assert (startOffset == foldStartOffset) : "startOffset=" + startOffset + " != foldStartOffset=" + foldStartOffset; // NOI18N
181
        if (fold.getEndOffset() <= limitOffset || !forcedLimit) {
182
            return new FoldView(textComponent(), fold, colorSettings);
183
        } else {
184
            return null;
185
        }
186
    }
187
    
188
    @Override
189
    public int viewEndOffset(int startOffset, int limitOffset, boolean forcedLimit) {
190
        int foldEndOffset = fold.getEndOffset();
191
        if (foldEndOffset <= limitOffset) {
192
            return foldEndOffset;
193
        } else {
194
            return -1;
195
        }
196
    }
197
198
    @Override
199
    public void continueCreation(int startOffset, int endOffset) {
200
    }
201
202
    @Override
203
    public void finishCreation() {
204
        fold = null;
205
        collapsedFoldIterator = null;
206
        if (foldHierarchyLocked) {
207
            foldHierarchy.unlock();
208
        }
209
    }
210
211
    @Override
212
    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
213
        // For fold state changes use a higher priority
214
        int startOffset = evt.getAffectedStartOffset();
215
        int endOffset = evt.getAffectedEndOffset();
216
        if (CHANGE_LOG.isLoggable(Level.FINE)) {
217
            ViewUtils.log(CHANGE_LOG, "CHANGE in FoldViewFactory: <" + // NOI18N
218
                    startOffset + "," + endOffset + ">\n"); // NOI18N
219
        }
220
        fireEvent(EditorViewFactoryChange.createList(startOffset, endOffset,
221
                EditorViewFactoryChange.Type.PARAGRAPH_CHANGE));
222
    }
223
224
    public static final class FoldFactory implements EditorViewFactory.Factory {
225
226
        @Override
227
        public EditorViewFactory createEditorViewFactory(View documentView) {
228
            return new FoldViewFactory(documentView);
229
        }
230
231
        @Override
232
        public int weight() {
233
            return 100;
234
        }
235
236
    }
237
238
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/FoldingEditorSupport.java (+170 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. Since 
88
     * @return 
89
     */
90
    public Boolean call() {
91
        final Document doc = component.getDocument();
92
        final Boolean[] res = new Boolean[] { false };
93
        doc.render(new Runnable() {
94
            public void run() {
95
                foldHierarchy.lock();
96
                try {
97
                    int offset = component.getCaret().getDot();
98
                    Fold f = FoldUtilities.findCollapsedFold(foldHierarchy, offset, offset);
99
                    if (f != null) {
100
                        foldHierarchy.expand(f);
101
                        res[0] = true;
102
                    }
103
                } finally {
104
                    foldHierarchy.unlock();
105
                }
106
            }
107
        });
108
        return res[0];
109
    }
110
111
    public @Override void foldHierarchyChanged(FoldHierarchyEvent evt) {
112
        if (!(component.getCaret() instanceof BaseCaret)) {
113
            return;
114
        }
115
        int caretOffset = component.getCaret().getDot();
116
        final int addedFoldCnt = evt.getAddedFoldCount();
117
        final boolean scrollToView;
118
        LOG.finest("Received fold hierarchy change");
119
        if (addedFoldCnt > 0) {
120
            FoldHierarchy hierarchy = (FoldHierarchy) evt.getSource();
121
            Fold collapsed = null;
122
            boolean wasExpanded = false;
123
            while ((collapsed = FoldUtilities.findCollapsedFold(hierarchy, caretOffset, caretOffset)) != null && collapsed.getStartOffset() < caretOffset &&
124
                    collapsed.getEndOffset() > caretOffset) {
125
                        hierarchy.expand(collapsed);
126
                        wasExpanded = true;
127
            }
128
            // prevent unneeded scrolling; the user may have scrolled out using mouse already
129
            // so scroll only if the added fold may affect Y axis. Actually it's unclear why
130
            // we should reveal the current position on fold events except when caret is positioned in now-collapsed fold
131
            scrollToView = wasExpanded;
132
        } else {
133
            int startOffset = Integer.MAX_VALUE;
134
            // Set the caret's offset to the end of just collapsed fold if necessary
135
            if (evt.getAffectedStartOffset() <= caretOffset && evt.getAffectedEndOffset() >= caretOffset) {
136
                for (int i = 0; i < evt.getFoldStateChangeCount(); i++) {
137
                    FoldStateChange change = evt.getFoldStateChange(i);
138
                    if (change.isCollapsedChanged()) {
139
                        Fold fold = change.getFold();
140
                        if (fold.isCollapsed() && fold.getStartOffset() <= caretOffset && fold.getEndOffset() >= caretOffset) {
141
                            if (fold.getStartOffset() < startOffset) {
142
                                startOffset = fold.getStartOffset();
143
                            }
144
                        }
145
                    }
146
                }
147
                if (startOffset != Integer.MAX_VALUE) {
148
                    ((BaseCaret)component.getCaret()).setDot(startOffset, false);
149
                }
150
            }
151
            scrollToView = false;
152
        }
153
        // Update caret's visual position
154
        // Post the caret update asynchronously since the fold hierarchy is updated before
155
        // the view hierarchy and the views so the dispatchUpdate() could be picking obsolete
156
        // view information.
157
        if (addedFoldCnt > 1 || scrollToView) {
158
            SwingUtilities.invokeLater(new Runnable() {
159
                public @Override void run() {
160
                    LOG.finest("Updating after fold hierarchy change");
161
                    if (component == null) {
162
                        return;
163
                    }
164
                    ((BaseCaret)component.getCaret()).refresh(addedFoldCnt > 1 && !scrollToView);
165
                }
166
            });
167
        }
168
    }
169
    
170
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/JavadocReader.java (+148 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.util.regex.Matcher;
45
import java.util.regex.Pattern;
46
import javax.swing.text.BadLocationException;
47
import javax.swing.text.Document;
48
import org.netbeans.api.editor.fold.Fold;
49
import org.netbeans.api.editor.fold.FoldHierarchy;
50
import org.netbeans.api.editor.fold.FoldTemplate;
51
import org.netbeans.editor.BaseDocument;
52
import org.netbeans.editor.Utilities;
53
import org.netbeans.lib.editor.util.CharSequenceUtilities;
54
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
55
import org.netbeans.spi.editor.fold.ContentReader;
56
57
    
58
/**
59
 * The class will read the contents of the document starting after the fold's guarded start.
60
 * It will produce text found on the first non-blank line fully contained within the fold. During
61
 * the search, the Readed ignores leading whitespaces on a line followed by 'lineStartMarker' string.
62
 * <p/>
63
 * For example, if lineStartMarker is set to '*', it will ignore stars at the beginning of the Javadoc, 
64
 * so it will produce the overwiew sentence, although it typically starts at the 2nd javadoc line. If
65
 * the 'lineStartMarker' is set to "#", it will produce 1st line of a consecutive line comment block.
66
 * <p/>
67
 * The Reader will stop reading the content on line starting with a 'stopPattern'. For javadoc, lines 
68
 * that start with @tag will stop the reading.
69
 * <p/>
70
 * So, for Javadocs, the configuration will look like:
71
 * <code>
72
 * <pre>
73
 * ContentReader rd = new JavadocReader("*", "\.", "@");
74
 * </pre>
75
 * </code>
76
 * 
77
 * 
78
 */
79
public final class JavadocReader implements ContentReader {
80
    /**
81
     * The marker, which may (should!) be present at the start of the line
82
     */
83
    private final String lineStartMarker; 
84
85
    /**
86
     * Ignore contents after this pattern
87
     */
88
    private final Pattern stopPattern;
89
    
90
    private final Pattern termPattern;
91
    
92
    private final String prefix;
93
    
94
    public JavadocReader(String lineStartMarker, String terminator, String stopPattern, String prefix) {
95
        this.lineStartMarker = lineStartMarker;
96
        this.termPattern = terminator != null ? Pattern.compile(terminator) : null;
97
        this.stopPattern = stopPattern != null ? Pattern.compile(stopPattern, Pattern.CASE_INSENSITIVE) : null;
98
        this.prefix = prefix == null ? " " : prefix; // NOI18N
99
    }
100
101
    @Override
102
    public CharSequence read(Document d, Fold f, FoldTemplate ft) throws BadLocationException {
103
        int contentStart = f.getGuardedStart();
104
        int contentEnd = f.getGuardedEnd();
105
        BaseDocument bd = (BaseDocument)d;
106
        
107
        int rowStart = contentStart;
108
109
        while (rowStart < contentEnd) {
110
            int nextRow = Utilities.getRowStart(bd, rowStart, 1);
111
            if (nextRow == -1 || nextRow > contentEnd) {
112
                nextRow = contentEnd;
113
            }
114
            int nonWhite = Utilities.getFirstNonWhiteFwd(bd, rowStart, nextRow);
115
            // check if the nonwhite content matches the lineStartMarker
116
            if (nonWhite != -1 && 
117
                lineStartMarker != null &&
118
                CharSequenceUtilities.textEquals(DocumentUtilities.getText(d, nonWhite, lineStartMarker.length()), lineStartMarker)) {
119
                nonWhite = Utilities.getFirstNonWhiteFwd(bd, nonWhite + lineStartMarker.length(), nextRow);
120
            }
121
            if (nonWhite != -1) {
122
                // found a non-whitespace
123
                int endIndex = Utilities.getFirstNonWhiteBwd(bd, nextRow, nonWhite);
124
                if (endIndex < nextRow) {
125
                    endIndex++;
126
                }
127
                CharSequence ret = DocumentUtilities.getText(d, nonWhite, endIndex - nonWhite);
128
                if (stopPattern != null && stopPattern.matcher(ret).lookingAt()) {
129
                    return null;
130
                }
131
                int idx = -1;
132
                if (termPattern != null) {
133
                    Matcher m = termPattern.matcher(ret);
134
                    if (m.find()) {
135
                        idx = m.start();
136
                    }
137
                }
138
                if (idx > -1) {
139
                    return prefix + DocumentUtilities.getText(d, nonWhite, idx);
140
                } else {
141
                    return prefix + ret;
142
                }
143
            }
144
            rowStart = nextRow;
145
        }
146
        return null;
147
    }
148
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/LegacySettingsSync.java (+161 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.lang.ref.Reference;
45
import java.lang.ref.WeakReference;
46
import java.util.Collection;
47
import java.util.HashMap;
48
import java.util.Map;
49
import java.util.prefs.PreferenceChangeEvent;
50
import java.util.prefs.PreferenceChangeListener;
51
import java.util.prefs.Preferences;
52
import org.netbeans.api.editor.fold.FoldType;
53
import org.netbeans.api.editor.fold.FoldUtilities;
54
import org.netbeans.api.editor.mimelookup.MimeLookup;
55
import org.netbeans.api.editor.mimelookup.MimePath;
56
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
57
58
/**
59
 * The class is responsible for syncing legacy setting keys in the
60
 * default ("") mimetype. It is also responsible for erasing 
61
 * folds from a MIME, if the setting for 'override' is not set. This will
62
 * gradually clean up migrated settings.
63
 * 
64
 * @author sdedic
65
 */
66
class LegacySettingsSync implements PreferenceChangeListener {
67
    private static LegacySettingsSync INSTANCE;
68
    
69
    private Reference<Preferences> defaultMimePrefs;
70
    
71
    synchronized static LegacySettingsSync get() {
72
        if (INSTANCE == null) {
73
            INSTANCE = new LegacySettingsSync();
74
        }
75
        return INSTANCE;
76
    }
77
    
78
    synchronized Preferences processMime(String mime) {
79
        Preferences prefs = MimeLookup.getLookup(mime).lookup(Preferences.class);
80
        if ("".equals(mime)) { // NOI18N
81
            Preferences p = defaultMimePrefs == null ? null : defaultMimePrefs.get();
82
            if (p == prefs) {
83
                return prefs;
84
            } else if (p != null) {
85
                p.removePreferenceChangeListener(this);
86
            }
87
            // sync the default values for legacy code
88
            syncKey(FoldType.MEMBER.code(), prefs);
89
            syncKey(FoldType.NESTED.code(), prefs);
90
            syncKey(FoldType.DOCUMENTATION.code(), prefs);
91
            defaultMimePrefs = new WeakReference(prefs);
92
            // no weak listener, this instance lives forever, but the defaultMimePrefs
93
            // reference allows the pref to expire.
94
            prefs.addPreferenceChangeListener(this);
95
        } else {
96
            if (!((prefs instanceof OverridePreferences) && ((OverridePreferences)prefs).isOverriden(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS))) {
97
                return prefs;
98
            } 
99
            boolean state = prefs.getBoolean(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS, false);
100
            if (!state) {
101
                cleanupPreferences(mime, prefs);
102
            } else {
103
                clonePreferences(mime, prefs);
104
            }
105
        }
106
        return prefs;
107
    }
108
109
    @Override
110
    public void preferenceChange(PreferenceChangeEvent evt) {
111
        if (evt.getKey() == null || !evt.getKey().startsWith(FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX)) {
112
            return;
113
        }
114
        String k = evt.getKey().substring(FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX.length());
115
        syncKey(k, evt.getNode());
116
    }
117
    
118
    private void syncKey(String k, Preferences pref) {
119
        String l;
120
        if (FoldType.MEMBER.code().equals(k)) {
121
            l = "method"; // NOI18N
122
        } else if (FoldType.NESTED.code().equals(k)) {
123
            l = "innerclass"; // NOI18N
124
        } else if (FoldType.DOCUMENTATION.code().equals(k)) {
125
            l = "javadoc"; // NOI18N
126
        } else {
127
            return;
128
        }
129
        String syncKey = FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX + l;
130
        pref.putBoolean(syncKey, pref.getBoolean(
131
                FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX + k, false));
132
    }
133
    
134
    private void clonePreferences(String mime, Preferences pref) {
135
        Collection<? extends FoldType> types = FoldUtilities.getFoldTypes(mime).values();
136
        for (FoldType ft : types) {
137
            String key = FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX + ft.code();
138
            if (!isDefinedLocally(pref, key)) {
139
                boolean val = pref.getBoolean(key, 
140
                    ft.parent() == null ? false :
141
                    pref.getBoolean(FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX + ft.parent().code(), false));
142
                pref.putBoolean(key, val);
143
            }
144
        }
145
    }
146
    
147
    private boolean isDefinedLocally(Preferences pref, String key) {
148
        return pref instanceof OverridePreferences && 
149
                ((OverridePreferences)pref).isOverriden(key);
150
    }
151
    
152
    private void cleanupPreferences(String mime, Preferences pref) {
153
        Collection<? extends FoldType> types = FoldUtilities.getFoldTypes(mime).values();
154
        for (FoldType ft : types) {
155
            String key = FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX + ft.code();
156
            if (isDefinedLocally(pref, key)) {
157
                pref.remove(key);
158
            }
159
        }
160
    }
161
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/ActionFactory.java (+261 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.ui;
43
44
import java.awt.event.ActionEvent;
45
import javax.swing.text.BadLocationException;
46
import javax.swing.text.Document;
47
import javax.swing.text.JTextComponent;
48
import org.netbeans.api.editor.EditorActionRegistration;
49
import org.netbeans.api.editor.fold.Fold;
50
import org.netbeans.api.editor.fold.FoldHierarchy;
51
import org.netbeans.api.editor.fold.FoldUtilities;
52
import org.netbeans.editor.BaseAction;
53
import org.netbeans.editor.BaseKit;
54
55
/**
56
 * Factory for editor code folding actions.
57
 * Migrated from editor.lib module
58
 *
59
 * @author sdedic
60
 */
61
final class ActionFactory {
62
    
63
    /** Returns the fold that should be collapsed/expanded in the caret row
64
     *  @param hierarchy hierarchy under which all folds should be collapsed/expanded.
65
     *  @param dot caret position offset
66
     *  @param lineStart offset of the start of line
67
     *  @param lineEnd offset of the end of line
68
     *  @return the fold that meet common criteria in accordance with the caret position
69
     */
70
    private static Fold getLineFold(FoldHierarchy hierarchy, int dot, int lineStart, int lineEnd){
71
        Fold caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy, dot);
72
73
        // beginning searching from the lineStart
74
        Fold fold = FoldUtilities.findNearestFold(hierarchy, lineStart);  
75
        
76
        while (fold!=null && 
77
                  (fold.getEndOffset()<=dot || // find next available fold if the 'fold' is one-line
78
                      // or it has children and the caret is in the fold body
79
                      // i.e. class A{ |public void method foo(){}}
80
                      (!fold.isCollapsed() && fold.getFoldCount() > 0  && fold.getStartOffset()+1<dot) 
81
                   )
82
               ){
83
84
                   // look for next fold in forward direction
85
                   Fold nextFold = FoldUtilities.findNearestFold(hierarchy,
86
                       (fold.getFoldCount()>0) ? fold.getStartOffset()+1 : fold.getEndOffset());
87
                   if (nextFold!=null && nextFold.getStartOffset()<lineEnd){
88
                       if (nextFold == fold) return fold;
89
                       fold = nextFold;
90
                   }else{
91
                       break;
92
                   }
93
        }
94
95
        
96
        // a fold on the next line was found, returning fold at offset (in most cases inner class)
97
        if (fold == null || fold.getStartOffset()>lineEnd) {
98
99
            // in the case:
100
            // class A{
101
            // }     |
102
            // try to find an offset fold on the offset of the line beginning
103
            if (caretOffsetFold == null){
104
                caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy, lineStart);
105
            }
106
            
107
            return caretOffsetFold;
108
        }
109
        
110
        // no fold at offset found, in this case return the fold
111
        if (caretOffsetFold == null) return fold;
112
        
113
        // skip possible inner class members validating if the innerclass fold is collapsed
114
        if (caretOffsetFold.isCollapsed()) return caretOffsetFold;
115
        
116
        // in the case:
117
        // class A{
118
        // public vo|id foo(){} }
119
        // 'fold' (in this case fold of the method foo) will be returned
120
        if ( caretOffsetFold.getEndOffset()>fold.getEndOffset() && 
121
             fold.getEndOffset()>dot){
122
            return fold;
123
        }
124
        
125
        // class A{
126
        // |} public void method foo(){}
127
        // inner class fold will be returned
128
        if (fold.getStartOffset()>caretOffsetFold.getEndOffset()) return caretOffsetFold;
129
        
130
        // class A{
131
        // public void foo(){} |}
132
        // returning innerclass fold
133
        if (fold.getEndOffset()<dot) return caretOffsetFold;
134
        
135
        return fold;
136
    }
137
    
138
    /** Collapse a fold. Depends on the current caret position. */
139
    @EditorActionRegistration(name = BaseKit.collapseFoldAction,
140
            menuText = "#" + BaseKit.collapseFoldAction + "_menu_text")
141
    public static class CollapseFold extends LocalBaseAction {
142
        public CollapseFold(){
143
        }
144
        
145
        private boolean dotInFoldArea(JTextComponent target, Fold fold, int dot) throws BadLocationException{
146
            int foldStart = fold.getStartOffset();
147
            int foldEnd = fold.getEndOffset();
148
            int foldRowStart = javax.swing.text.Utilities.getRowStart(target, foldStart);
149
            int foldRowEnd = javax.swing.text.Utilities.getRowEnd(target, foldEnd);
150
            if (foldRowStart > dot || foldRowEnd < dot) return false; // it's not fold encapsulating dot
151
            return true;
152
            }
153
154
        
155
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
156
            Document doc = target.getDocument();
157
            doc.render(new Runnable() {
158
                @Override
159
                public void run() {
160
                    FoldHierarchy hierarchy = FoldHierarchy.get(target);
161
                    int dot = target.getCaret().getDot();
162
                    hierarchy.lock();
163
                    try{
164
                        try{
165
                            int rowStart = javax.swing.text.Utilities.getRowStart(target, dot);
166
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(target, dot);
167
                            Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
168
                            fold = getLineFold(hierarchy, dot, rowStart, rowEnd);
169
                            if (fold==null){
170
                                return; // no success
171
                            }
172
                            // ensure we' got the right fold
173
                            if (dotInFoldArea(target, fold, dot)){
174
                                hierarchy.collapse(fold);
175
                            }
176
                        }catch(BadLocationException ble){
177
                            ble.printStackTrace();
178
                        }
179
                    }finally {
180
                        hierarchy.unlock();
181
                    }
182
                }
183
            });
184
        }
185
    }
186
    
187
    /** Expand a fold. Depends on the current caret position. */
188
    @EditorActionRegistration(name = BaseKit.expandFoldAction,
189
            menuText = "#" + BaseKit.expandFoldAction + "_menu_text")
190
    public static class ExpandFold extends LocalBaseAction {
191
        public ExpandFold(){
192
        }
193
        
194
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
195
            Document doc = target.getDocument();
196
            doc.render(new Runnable() {
197
                @Override
198
                public void run() {
199
                    FoldHierarchy hierarchy = FoldHierarchy.get(target);
200
                    int dot = target.getCaret().getDot();
201
                    hierarchy.lock();
202
                    try {
203
                        try {
204
                            int rowStart = javax.swing.text.Utilities.getRowStart(target, dot);
205
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(target, dot);
206
                            Fold fold = getLineFold(hierarchy, dot, rowStart, rowEnd);
207
                            if (fold != null) {
208
                                hierarchy.expand(fold);
209
                            }
210
                        } catch (BadLocationException ble) {
211
                            ble.printStackTrace();
212
                        }
213
                    } finally {
214
                        hierarchy.unlock();
215
                    }
216
                }
217
            });
218
        }
219
    }
220
    
221
    /** Collapse all existing folds in the document. */
222
    @EditorActionRegistration(name = BaseKit.collapseAllFoldsAction,
223
            menuText = "#" + BaseKit.collapseAllFoldsAction + "_menu_text")
224
    public static class CollapseAllFolds extends LocalBaseAction {
225
        public CollapseAllFolds(){
226
        }
227
        
228
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
229
            FoldHierarchy hierarchy = FoldHierarchy.get(target);
230
            // Hierarchy locking done in the utility method
231
            FoldUtilities.collapseAll(hierarchy);
232
        }
233
    }
234
235
    /** Expand all existing folds in the document. */
236
    @EditorActionRegistration(name = BaseKit.expandAllFoldsAction,
237
            menuText = "#" + BaseKit.expandAllFoldsAction + "_menu_text")
238
    public static class ExpandAllFolds extends LocalBaseAction {
239
        public ExpandAllFolds(){
240
        }
241
        
242
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
243
            FoldHierarchy hierarchy = FoldHierarchy.get(target);
244
            // Hierarchy locking done in the utility method
245
            FoldUtilities.expandAll(hierarchy);
246
        }
247
    }
248
249
250
    private static abstract class LocalBaseAction extends BaseAction {
251
        public LocalBaseAction() {
252
            super();
253
        }
254
255
        @Override
256
        protected Class getShortDescriptionBundleClass() {
257
            return BaseKit.class;
258
        }
259
260
    }
261
}
(-)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 (+1519 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 boolean hasProviders() {
320
        final FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
321
        FoldHierarchyExecution exec = ApiPackageAccessor.get().foldGetExecution(foldHierarchy);
322
        exec.lock();
323
        try {
324
            return exec.hasProviders();
325
        } finally {
326
            exec.unlock();
327
        }
328
        
329
    }
330
    
331
    private void updatePreferredSize() {
332
        // do not show at all, if there are no providers registered.
333
        if (enabled && hasProviders() && !alreadyPresent) {
334
            setPreferredSize(new Dimension(getColoring().getFont().getSize(), component.getHeight()));
335
            setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
336
        }else{
337
            setPreferredSize(new Dimension(0,0));
338
            setMaximumSize(new Dimension(0,0));
339
        }
340
        revalidate();
341
    }
342
343
    private void updateColors() {
344
        Coloring c = getColoring();
345
        this.backColor = c.getBackColor();
346
        this.foreColor = c.getForeColor();
347
        this.font = c.getFont();
348
    }
349
350
    /**
351
     * This method should be treated as final. Subclasses are forbidden to override it.
352
     * @return The background color used for painting this component.
353
     * @deprecated Without any replacement.
354
     */
355
    protected Color getBackColor() {
356
        if (backColor == null) {
357
            updateColors();
358
        }
359
        return backColor;
360
    }
361
    
362
    /**
363
     * This method should be treated as final. Subclasses are forbidden to override it.
364
     * @return The foreground color used for painting this component.
365
     * @deprecated Without any replacement.
366
     */
367
    protected Color getForeColor() {
368
        if (foreColor == null) {
369
            updateColors();
370
        }
371
        return foreColor;
372
    }
373
    
374
    /**
375
     * This method should be treated as final. Subclasses are forbidden to override it.
376
     * @return The font used for painting this component.
377
     * @deprecated Without any replacement.
378
     */
379
    protected Font getColoringFont() {
380
        if (font == null) {
381
            updateColors();
382
        }
383
        return font;
384
    }
385
    
386
    // overriding due to issue #60304
387
    public @Override void update(Graphics g) {
388
    }
389
    
390
    protected void collectPaintInfos(
391
        View rootView, Fold fold, Map<Integer, PaintInfo> map, int level, int startIndex, int endIndex
392
    ) throws BadLocationException {
393
        //never called
394
    }
395
396
    /**
397
     * Adjust lowest/topmost boundaries from the Fold range y1-y2.
398
     * @param y1
399
     * @param y2
400
     * @param level 
401
     */
402
    private void setMouseBoundaries(int y1, int y2, int level) {
403
        if (!hasMousePoint() || mousePointConsumed) {
404
            return;
405
        }
406
        int y = mousePoint;
407
        if (y2 < y && lowestAboveMouse < y2) {
408
            LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
409
            lowestAboveMouse = y2;
410
        }
411
        if (y1 > y && topmostBelowMouse > y1) {
412
            LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
413
            topmostBelowMouse = y1;
414
        }
415
    }
416
    
417
    /*
418
     * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
419
     * then visually span multiple lines && be marked as collapsed.
420
     */
421
422
    protected List<? extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
423
        javax.swing.plaf.TextUI textUI = component.getUI();
424
        if (!(textUI instanceof BaseTextUI)) {
425
            return Collections.<PaintInfo>emptyList();
426
        }
427
        BaseTextUI baseTextUI = (BaseTextUI)textUI;
428
        BaseDocument bdoc = Utilities.getDocument(component);
429
        if (bdoc == null) {
430
            return Collections.<PaintInfo>emptyList();
431
        }
432
        mousePointConsumed = false;
433
        mouseBoundary = null;
434
        topmostBelowMouse = Integer.MAX_VALUE;
435
        lowestAboveMouse = -1;
436
        bdoc.readLock();
437
        try {
438
            int startPos = baseTextUI.getPosFromY(clip.y);
439
            int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
440
            
441
            if (startPos < 0 || endPos < 0) {
442
                // editor window is not properly sized yet; return no infos
443
                return Collections.<PaintInfo>emptyList();
444
            }
445
            
446
            // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
447
            // the document.
448
            int docLen = bdoc.getLength();
449
            if (startPos >= docLen || endPos > docLen) {
450
                return Collections.<PaintInfo>emptyList();
451
            }
452
            
453
            startPos = Utilities.getRowStart(bdoc, startPos);
454
            endPos = Utilities.getRowEnd(bdoc, endPos);
455
            
456
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
457
            hierarchy.lock();
458
            try {
459
                View rootView = Utilities.getDocumentView(component);
460
                if (rootView != null) {
461
                    Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
462
                    @SuppressWarnings("unchecked")
463
                    List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
464
                    int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
465
466
                    /*
467
                     * Note:
468
                     * 
469
                     * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
470
                     * This is because several folds may occupy the same line, while only one + sign is displayed,
471
                     * and affect the last fold in the row.
472
                     */
473
                    NavigableMap<Integer, PaintInfo> map = new TreeMap<Integer, PaintInfo>();
474
                    // search backwards
475
                    for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
476
                        Fold fold = foldList.get(i);
477
                        if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
478
                            break;
479
                        }
480
                    }
481
482
                    // search forward
483
                    for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
484
                        Fold fold = foldList.get(i);
485
                        if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
486
                            break;
487
                        }
488
                    }
489
                    
490
                    if (map.isEmpty() && foldList.size() > 0) {
491
                        assert foldList.size() == 1;
492
                        PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
493
                        mouseBoundary = new Rectangle(0, 0, 0, clip.height);
494
                        LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
495
                        if (hasMousePoint()) {
496
                            pi.markActive(true, true, true);
497
                        }
498
                        return Collections.singletonList(pi);
499
                    } else {
500
                        if (mouseBoundary == null) {
501
                            mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
502
                            LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
503
                        }
504
                        return new ArrayList<PaintInfo>(map.values());
505
                    }
506
                } else {
507
                    return Collections.<PaintInfo>emptyList();
508
                }
509
            } finally {
510
                hierarchy.unlock();
511
            }
512
        } finally {
513
            bdoc.readUnlock();
514
        }
515
    }
516
    
517
    /**
518
     * Adds a paint info to the map. If a paintinfo already exists, it merges
519
     * the structures, so the painting process can just follow the instructions.
520
     * 
521
     * @param infos
522
     * @param yOffset
523
     * @param nextInfo 
524
     */
525
    private void addPaintInfo(Map<Integer, PaintInfo> infos, int yOffset, PaintInfo nextInfo) {
526
        PaintInfo prevInfo = infos.get(yOffset);
527
        nextInfo.mergeWith(prevInfo);
528
        infos.put(yOffset, nextInfo);
529
    }
530
531
    private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level,  NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
532
//        System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
533
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
534
//                + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
535
//                + ", level=" + level);
536
        
537
        if (f.getStartOffset() > upperBoundary) {
538
            return false;
539
        }
540
541
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
542
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
543
        int y1 = btui.getYFromPos(lineStartOffset1);
544
        int h = btui.getEditorUI().getLineHeight();
545
        int y2 = btui.getYFromPos(lineStartOffset2);
546
         
547
        // the 'active' flags can be set only after children are processed; highlights
548
        // correspond to the innermost expanded child.
549
        boolean activeMark = false;
550
        boolean activeIn = false;
551
        boolean activeOut = false;
552
        PaintInfo spi;
553
        boolean activated;
554
        
555
        if (y1 == y2) {
556
            // whole fold is on a single line
557
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
558
            if (activated = isActivated(y1, y1 + h)) {
559
                activeMark = true;
560
            }
561
            addPaintInfo(infos, y1, spi);
562
        } else {
563
            // fold spans multiple lines
564
            spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
565
            if (activated = isActivated(y1, y2 + h / 2)) {
566
                activeMark = true;
567
                activeOut = true;
568
            }
569
            addPaintInfo(infos, y1, spi);
570
        }
571
572
        setMouseBoundaries(y1, y2 + h / 2, level);
573
574
        // Handle end mark after possible inner folds were processed because
575
        // otherwise if there would be two nested folds both ending at the same line
576
        // then the end mark for outer one would be replaced by an end mark for inner one
577
        // (same key in infos map) and the painting code would continue to paint line marking a fold
578
        // until next fold is reached (or end of doc).
579
        PaintInfo epi = null;
580
        if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
581
            epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
582
            addPaintInfo(infos, y2, epi);
583
        }
584
585
        // save the topmost/lowest information, reset for child processing
586
        int topmost = topmostBelowMouse;
587
        int lowest = lowestAboveMouse;
588
        topmostBelowMouse = y2 + h / 2;
589
        lowestAboveMouse = y1;
590
591
        try {
592
            if (!f.isCollapsed()) {
593
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
594
                @SuppressWarnings("unchecked")
595
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
596
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
597
598
                // search backwards
599
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
600
                    Fold fold = foldList.get(i);
601
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
602
                        break;
603
                    }
604
                }
605
606
                // search forward
607
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
608
                    Fold fold = foldList.get(i);
609
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
610
                        return false;
611
                    }
612
                }
613
            }
614
            if (!mousePointConsumed && activated) {
615
                mousePointConsumed = true;
616
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
617
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
618
                spi.markActive(activeMark, activeIn, activeOut);
619
                if (epi != null) {
620
                    epi.markActive(true, true, false);
621
                }
622
                markDeepChildrenActive(infos, y1, y2, level);
623
            }
624
        } finally {
625
            topmostBelowMouse = topmost;
626
            lowestAboveMouse = lowest;
627
        }
628
        return true;
629
    }
630
     
631
    /**
632
     * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
633
     * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
634
     * as active.
635
     * The method returns Y start coordinate of the 1st child found.
636
     * 
637
     * @param infos fold infos collected so far
638
     * @param yFrom upper Y-coordinate of the parent fold
639
     * @param yTo lower Y-coordinate of the parent fold
640
     * @param level level of the parent fold
641
     * @return Y-coordinate of the 1st child.
642
     */
643
    private int markDeepChildrenActive(NavigableMap<Integer, PaintInfo> infos, int yFrom, int yTo, int level) {
644
        int result = Integer.MAX_VALUE;
645
        Map<Integer, PaintInfo> m = infos.subMap(yFrom, yTo);
646
        for (Map.Entry<Integer, PaintInfo> me : m.entrySet()) {
647
            PaintInfo pi = me.getValue();
648
            int y = pi.getPaintY();
649
            if (y > yFrom && y < yTo) {
650
                if (LOG.isLoggable(Level.FINEST)) {
651
                    LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
652
                }
653
                pi.markActive(false, true, true);
654
                if (y < result) {
655
                    y = result;
656
                }
657
            }
658
        }
659
        return result;
660
    }
661
    
662
    /**
663
     * Returns stroke appropriate for painting (in)active outlines
664
     * @param s the default stroke
665
     * @param active true for active outlines
666
     * @return value of 's' or a Stroke which should be used to paint the outline.
667
     */
668
    private static Stroke getStroke(Stroke s, boolean active) {
669
        if (active) {
670
            return LINE_BOLD;
671
        } else {
672
            return s;
673
        }
674
    }
675
    
676
    private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
677
//        System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
678
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
679
//                + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
680
//                + ", level=" + level);
681
682
        if (f.getEndOffset() < lowerBoundary) {
683
            return false;
684
        }
685
686
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
687
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
688
        int h = btui.getEditorUI().getLineHeight();
689
690
        boolean activeMark = false;
691
        boolean activeIn = false;
692
        boolean activeOut = false;
693
        PaintInfo spi = null;
694
        PaintInfo epi = null;
695
        boolean activated = false;
696
        int y1 = 0;
697
        int y2 = 0;
698
        
699
        if (lineStartOffset1 == lineStartOffset2) {
700
            // whole fold is on a single line
701
            y2 = y1 = btui.getYFromPos(lineStartOffset1);
702
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
703
            if (activated = isActivated(y1, y1 + h)) {
704
                activeMark = true;
705
            }
706
            addPaintInfo(infos, y1, spi);
707
        } else {
708
            y2 = btui.getYFromPos(lineStartOffset2);
709
            // fold spans multiple lines
710
            y1 = btui.getYFromPos(lineStartOffset1);
711
            activated = isActivated(y1, y2 + h / 2);
712
            if (f.getStartOffset() >= upperBoundary) {
713
                spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
714
                if (activated) {
715
                    activeMark = true;
716
                    activeOut = true;
717
                }
718
                addPaintInfo(infos, y1, spi);
719
            }
720
721
            if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
722
                activated |= isActivated(y1, y2 + h / 2);
723
                epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
724
                addPaintInfo(infos, y2, epi);
725
            }
726
        }
727
        
728
        setMouseBoundaries(y1, y2 + h / 2, level);
729
730
        // save the topmost/lowest information, reset for child processing
731
        int topmost = topmostBelowMouse;
732
        int lowest = lowestAboveMouse;
733
        topmostBelowMouse = y2 + h /2;
734
        lowestAboveMouse = y1;
735
736
        try {
737
            if (!f.isCollapsed()) {
738
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
739
                @SuppressWarnings("unchecked")
740
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
741
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
742
743
                // search backwards
744
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
745
                    Fold fold = foldList.get(i);
746
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
747
                        return false;
748
                    }
749
                }
750
751
                // search forward
752
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
753
                    Fold fold = foldList.get(i);
754
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
755
                        break;
756
                    }
757
                }
758
            }
759
            if (!mousePointConsumed && activated) {
760
                mousePointConsumed = true;
761
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
762
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
763
                if (spi != null) {
764
                    spi.markActive(activeMark, activeIn, activeOut);
765
                }
766
                if (epi != null) {
767
                    epi.markActive(true, true, false);
768
                }
769
                int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
770
                if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
771
                    // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
772
                    // 1st child marker.
773
                    epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
774
                    epi.markActive(true, true, false);
775
                    addPaintInfo(infos, y1, epi);
776
                }
777
            }
778
        } finally {
779
            topmostBelowMouse = topmost;
780
            lowestAboveMouse = lowest;
781
        }
782
        return true;
783
    }
784
    
785
    private Rectangle makeMouseBoundary(int y1, int y2) {
786
        if (!hasMousePoint()) {
787
            return null;
788
        }
789
        if (topmostBelowMouse < Integer.MAX_VALUE) {
790
            y2 = topmostBelowMouse;
791
        }
792
        if (lowestAboveMouse  > -1) {
793
            y1 = lowestAboveMouse;
794
        }
795
        return new Rectangle(0, y1, 0, y2 - y1);
796
    }
797
    
798
    protected EditorUI getEditorUI(){
799
        return Utilities.getEditorUI(component);
800
    }
801
    
802
    protected Document getDocument(){
803
        return component.getDocument();
804
    }
805
806
807
    private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
808
        Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
809
        Fold prevFold = fold;
810
        while (fold != null && fold.getStartOffset()<rowEnd){
811
            Fold nextFold = FoldUtilities.findNearestFold(hierarchy, (fold.isCollapsed()) ? fold.getEndOffset() : fold.getStartOffset()+1);
812
            if (nextFold == fold) return fold;
813
            if (nextFold!=null && nextFold.getStartOffset() < rowEnd){
814
                prevFold = shift ? fold : nextFold;
815
                fold = nextFold;
816
            }else{
817
                return prevFold;
818
            }
819
        }
820
        return prevFold;
821
    }
822
    
823
    protected void performAction(Mark mark) {
824
        performAction(mark, false);
825
    }
826
    
827
    private void performActionAt(Mark mark, int mouseY) throws BadLocationException {
828
        if (mark != null) {
829
            return;
830
        }
831
        BaseDocument bdoc = Utilities.getDocument(component);
832
        BaseTextUI textUI = (BaseTextUI)component.getUI();
833
834
        View rootView = Utilities.getDocumentView(component);
835
        if (rootView == null) return;
836
        
837
        bdoc.readLock();
838
        try {
839
            int yOffset = textUI.getPosFromY(mouseY);
840
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
841
            hierarchy.lock();
842
            try {
843
                Fold f = FoldUtilities.findOffsetFold(hierarchy, yOffset);
844
                if (f == null) {
845
                    return;
846
                }
847
                if (f.isCollapsed()) {
848
                    LOG.log(Level.WARNING, "Clicked on a collapsed fold {0} at {1}", new Object[] { f, mouseY });
849
                    return;
850
                }
851
                int startOffset = f.getStartOffset();
852
                int endOffset = f.getEndOffset();
853
                
854
                int startY = textUI.getYFromPos(startOffset);
855
                int nextLineOffset = Utilities.getRowStart(bdoc, startOffset, 1);
856
                int nextY = textUI.getYFromPos(nextLineOffset);
857
858
                if (mouseY >= startY && mouseY <= nextY) {
859
                    LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
860
                            new Object[] { mouseY, startY, nextY });
861
                    return;
862
                }
863
864
                startY = textUI.getYFromPos(endOffset);
865
                nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
866
                nextY = textUI.getYFromPos(nextLineOffset);
867
868
                if (mouseY >= startY && mouseY <= nextY) {
869
                    // the mouse can be positioned above the marker (the fold found above), or
870
                    // below it; in that case, the immediate enclosing fold should be used - should be the fold
871
                    // that corresponds to the nextLineOffset, if any
872
                    int h2 = (startY + nextY) / 2;
873
                    if (mouseY >= h2) {
874
                        Fold f2 = f;
875
                        
876
                        f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
877
                        if (f == null) {
878
                            // fold does not exist for the position below end-of-fold indicator
879
                            return;
880
                        }
881
                    }
882
                    
883
                }
884
                
885
                LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
886
                hierarchy.collapse(f);
887
            } finally {
888
                hierarchy.unlock();
889
            }
890
        } finally {
891
            bdoc.readUnlock();
892
        }        
893
    }
894
    
895
    private void performAction(final Mark mark, final boolean shiftFold) {
896
        Document doc = component.getDocument();
897
        doc.render(new Runnable() {
898
            @Override
899
            public void run() {
900
                ViewHierarchy vh = ViewHierarchy.get(component);
901
                LockedViewHierarchy lockedVH = vh.lock();
902
                try {
903
                    int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
904
                    if (pViewIndex >= 0) {
905
                        ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
906
                        int pViewStartOffset = pViewDesc.getStartOffset();
907
                        int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
908
                        // Find corresponding fold
909
                        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
910
                        foldHierarchy.lock();
911
                        try {
912
                            int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
913
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
914
                            Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
915
                            if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
916
                                foldHierarchy.toggle(clickedFold);
917
                            }
918
                        } catch (BadLocationException ble) {
919
                            LOG.log(Level.WARNING, null, ble);
920
                        } finally {
921
                            foldHierarchy.unlock();
922
                        }
923
                    }
924
                } finally {
925
                    lockedVH.unlock();
926
                }
927
            }
928
        });
929
    }
930
    
931
    protected int getMarkSize(Graphics g){
932
        if (g != null){
933
            FontMetrics fm = g.getFontMetrics(getColoring().getFont());
934
            if (fm != null){
935
                int ret = fm.getAscent() - fm.getDescent();
936
                return ret - ret%2;
937
            }
938
        }
939
        return -1;
940
    }
941
    
942
    private boolean hasMousePoint() {
943
        return mousePoint >= 0;
944
    }
945
    
946
    private boolean isActivated(int y1, int y2) {
947
        return hasMousePoint() && 
948
               (mousePoint >= y1 && mousePoint < y2);
949
    }
950
    
951
    private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
952
        Stroke origStroke = g2d.getStroke();
953
        g2d.setStroke(getStroke(origStroke, active));
954
        g2d.drawLine(x1, y1, x2, y2);
955
        g2d.setStroke(origStroke);
956
    }
957
    
958
    protected @Override void paintComponent(Graphics g) {
959
        if (!enabled) {
960
            return;
961
        }
962
        
963
        Rectangle clip = getVisibleRect();//g.getClipBounds();
964
        visibleMarks.clear();
965
        
966
        Coloring coloring = getColoring();
967
        g.setColor(coloring.getBackColor());
968
        g.fillRect(clip.x, clip.y, clip.width, clip.height);
969
        g.setColor(coloring.getForeColor());
970
971
        AbstractDocument adoc = (AbstractDocument)component.getDocument();
972
        adoc.readLock();
973
        try {
974
            List<? extends PaintInfo> ps = getPaintInfo(clip);
975
            Font defFont = coloring.getFont();
976
            int markSize = getMarkSize(g);
977
            int halfMarkSize = markSize / 2;
978
            int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
979
            int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
980
            int lineX = markX + halfMarkSize; // x position of the centre of mark
981
982
            LOG.fine("CFSBar: PAINT START ------\n");
983
            int descent = g.getFontMetrics(defFont).getDescent();
984
            PaintInfo previousInfo = null;
985
            Graphics2D g2d = (Graphics2D)g;
986
            LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
987
988
            for(PaintInfo paintInfo : ps) {
989
                boolean isFolded = paintInfo.isCollapsed();
990
                int y = paintInfo.getPaintY();
991
                int height = paintInfo.getPaintHeight();
992
                int markY = y + descent; // y position of mark rectangle
993
                int paintOperation = paintInfo.getPaintOperation();
994
995
                if (previousInfo == null) {
996
                    if (paintInfo.hasLineIn()) {
997
                        if (LOG.isLoggable(Level.FINE)) {
998
                            LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
999
                        }
1000
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
1001
                    }
1002
                } else {
1003
                    if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
1004
                        // Draw middle vertical line
1005
                        int prevY = previousInfo.getPaintY();
1006
                        if (LOG.isLoggable(Level.FINE)) {
1007
                            LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
1008
                        }
1009
                        drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
1010
                    }
1011
                }
1012
1013
                if (paintInfo.hasSign()) {
1014
                    g.drawRect(markX, markY, markSize, markSize);
1015
                    g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
1016
                    String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
1017
                    if (isFolded) {
1018
                        if (LOG.isLoggable(Level.FINE)) {
1019
                            LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1020
                        }
1021
                        g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
1022
                    }
1023
                    if (paintOperation != SINGLE_PAINT_MARK) {
1024
                        if (LOG.isLoggable(Level.FINE)) {
1025
                            LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1026
                        }
1027
                    }
1028
                    if (paintInfo.hasLineIn()) { //[PENDING]
1029
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
1030
                    }
1031
                    if (paintInfo.hasLineOut()) {
1032
                        // This is an error in case there's a next paint info at the same y which is an end mark
1033
                        // for this mark (it must be cleared explicitly).
1034
                        drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
1035
                    }
1036
                    visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
1037
1038
                } else if (paintOperation == PAINT_LINE) {
1039
                    if (LOG.isLoggable(Level.FINE)) {
1040
                        LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1041
                    }
1042
                    // FIXME !!
1043
                    drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
1044
                } else if (paintOperation == PAINT_END_MARK) {
1045
                    if (LOG.isLoggable(Level.FINE)) {
1046
                        LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1047
                    }
1048
                    if (previousInfo == null || y != previousInfo.getPaintY()) {
1049
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
1050
                        drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
1051
                        if (paintInfo.getInnerLevel() > 0) {//[PENDING]
1052
                            if (LOG.isLoggable(Level.FINE)) {
1053
                                LOG.fine("  PAINT middle-line\n"); // NOI18N
1054
                            }
1055
                            drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
1056
                        }
1057
                    }
1058
                }
1059
1060
                previousInfo = paintInfo;
1061
            }
1062
1063
            if (previousInfo != null &&
1064
                (previousInfo.getInnerLevel() > 0 ||
1065
                 (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
1066
            ) {
1067
                drawFoldLine(g2d, previousInfo.lineOutActive, 
1068
                        lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
1069
            }
1070
1071
        } catch (BadLocationException ble) {
1072
            LOG.log(Level.WARNING, null, ble);
1073
        } finally {
1074
            LOG.fine("CFSBar: PAINT END ------\n\n");
1075
            adoc.readUnlock();
1076
        }
1077
    }
1078
    
1079
    private static Object [] getFoldList(Fold parentFold, int start, int end) {
1080
        List<Fold> ret = new ArrayList<Fold>();
1081
1082
        int index = FoldUtilities.findFoldEndIndex(parentFold, start);
1083
        int foldCount = parentFold.getFoldCount();
1084
        int idxOfFirstFoldStartingInside = -1;
1085
        while (index < foldCount) {
1086
            Fold f = parentFold.getFold(index);
1087
            if (f.getStartOffset() <= end) {
1088
                ret.add(f);
1089
            } else {
1090
                break; // no more relevant folds
1091
            }
1092
            if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
1093
                idxOfFirstFoldStartingInside = ret.size() - 1;
1094
            }
1095
            index++;
1096
        }
1097
1098
        return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
1099
    }
1100
1101
    /**
1102
     * This class should be never used by other code; will be made private
1103
     */
1104
    public class PaintInfo {
1105
        
1106
        int paintOperation;
1107
        /**
1108
         * level of the 1st marker on the line
1109
         */
1110
        int innerLevel;
1111
        
1112
        /**
1113
         * Y-coordinate of the cell
1114
         */
1115
        int paintY;
1116
        
1117
        /**
1118
         * Height of the paint cell
1119
         */
1120
        int paintHeight;
1121
        
1122
        /**
1123
         * State of the marker (+/-)
1124
         */
1125
        boolean isCollapsed;
1126
        
1127
        /**
1128
         * all markers on the line are collapsed
1129
         */
1130
        boolean allCollapsed;
1131
        int startOffset;
1132
        int endOffset;
1133
        /**
1134
         * nesting level of the last marker on the line
1135
         */
1136
        int outgoingLevel;
1137
        
1138
        /**
1139
         * Force incoming line (from above) to be present
1140
         */
1141
        boolean lineIn;
1142
        
1143
        /**
1144
         * Force outgoing line (down from marker) to be present
1145
         */
1146
        boolean lineOut;
1147
        
1148
        /**
1149
         * The 'incoming' (upper) line should be painted as active
1150
         */
1151
        boolean lineInActive;
1152
        
1153
        /**
1154
         * The 'outgoing' (down) line should be painted as active
1155
         */
1156
        boolean lineOutActive;
1157
        
1158
        /**
1159
         * The sign/marker itself should be painted as active
1160
         */
1161
        boolean signActive;
1162
        
1163
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
1164
            this.paintOperation = paintOperation;
1165
            this.innerLevel = this.outgoingLevel = innerLevel;
1166
            this.paintY = paintY;
1167
            this.paintHeight = paintHeight;
1168
            this.isCollapsed = this.allCollapsed = isCollapsed;
1169
            this.startOffset = startOffset;
1170
            this.endOffset = endOffset;
1171
1172
            switch (paintOperation) {
1173
                case PAINT_MARK:
1174
                    lineIn = false;
1175
                    lineOut = true;
1176
                    outgoingLevel++;
1177
                    break;
1178
                case SINGLE_PAINT_MARK:
1179
                    lineIn = false;
1180
                    lineOut = false;
1181
                    break;
1182
                case PAINT_END_MARK:
1183
                    lineIn = true;
1184
                    lineOut = false;
1185
                    isCollapsed = true;
1186
                    allCollapsed = true;
1187
                    break;
1188
                case PAINT_LINE:
1189
                    lineIn = lineOut = true;
1190
                    break;
1191
            }
1192
        }
1193
        
1194
        /**
1195
         * Sets active flags on inidivual parts of the mark
1196
         * @param mark
1197
         * @param lineIn
1198
         * @param lineOut S
1199
         */
1200
        void markActive(boolean mark, boolean lineIn, boolean lineOut) {
1201
            this.signActive |= mark;
1202
            this.lineInActive |= lineIn;
1203
            this.lineOutActive |= lineOut;
1204
        }
1205
        
1206
        boolean hasLineIn() {
1207
            return lineIn || innerLevel > 0;
1208
        }
1209
        
1210
        boolean hasLineOut() {
1211
            return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
1212
        }
1213
1214
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
1215
            this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
1216
        }
1217
        
1218
        public int getPaintOperation(){
1219
            return paintOperation;
1220
        }
1221
        
1222
        public int getInnerLevel(){
1223
            return innerLevel;
1224
        }
1225
        
1226
        public int getPaintY(){
1227
            return paintY;
1228
        }
1229
        
1230
        public int getPaintHeight(){
1231
            return paintHeight;
1232
        }
1233
        
1234
        public boolean isCollapsed(){
1235
            return isCollapsed;
1236
        }
1237
        
1238
         boolean isAllCollapsed() {
1239
            return allCollapsed;
1240
        }
1241
        
1242
        public void setPaintOperation(int paintOperation){
1243
            this.paintOperation = paintOperation;
1244
        }
1245
        
1246
        public void setInnerLevel(int innerLevel){
1247
            this.innerLevel = innerLevel;
1248
        }
1249
        
1250
        public @Override String toString(){
1251
            StringBuffer sb = new StringBuffer("");
1252
            if (paintOperation == PAINT_MARK){
1253
                sb.append("PAINT_MARK"); // NOI18N
1254
            }else if (paintOperation == PAINT_LINE){
1255
                sb.append("PAINT_LINE"); // NOI18N
1256
            }else if (paintOperation == PAINT_END_MARK) {
1257
                sb.append("PAINT_END_MARK"); // NOI18N
1258
            }else if (paintOperation == SINGLE_PAINT_MARK) {
1259
                sb.append("SINGLE_PAINT_MARK");
1260
            }
1261
            sb.append(",L:").append(innerLevel).append("/").append(outgoingLevel); // NOI18N
1262
            sb.append(',').append(isCollapsed ? "C" : "E"); // NOI18N
1263
            sb.append(", start=").append(startOffset).append(", end=").append(endOffset);
1264
            sb.append(", lineIn=").append(lineIn).append(", lineOut=").append(lineOut);
1265
            return sb.toString();
1266
        }
1267
        
1268
        boolean hasSign() {
1269
            return paintOperation == PAINT_MARK || paintOperation == SINGLE_PAINT_MARK;
1270
        }
1271
        
1272
        
1273
        void mergeWith(PaintInfo prevInfo) {
1274
            if (prevInfo == null) {
1275
                return;
1276
            }
1277
1278
            int operation = this.paintOperation;
1279
            boolean lineIn = prevInfo.lineIn;
1280
            boolean lineOut = prevInfo.lineOut;
1281
            
1282
            LOG.log(Level.FINE, "Merging {0} with {1}: ", new Object[] { this, prevInfo });
1283
            if (prevInfo.getPaintOperation() == PAINT_END_MARK) {
1284
                // merge with start|single -> start mark + line-in
1285
                lineIn = true;
1286
            } else {
1287
                operation = PAINT_MARK;
1288
            }
1289
1290
            int level1 = Math.min(prevInfo.innerLevel, innerLevel);
1291
            int level2 = prevInfo.outgoingLevel;
1292
1293
            if (getPaintOperation() == PAINT_END_MARK 
1294
                && innerLevel == prevInfo.outgoingLevel) {
1295
                // if merging end marker at the last level, update to the new outgoing level
1296
                level2 = outgoingLevel;
1297
            } else if (!isCollapsed) {
1298
                level2 = Math.max(prevInfo.outgoingLevel, outgoingLevel);
1299
            }
1300
1301
            if (prevInfo.getInnerLevel() < getInnerLevel()) {
1302
                int paintFrom = Math.min(prevInfo.paintY, paintY);
1303
                int paintTo = Math.max(prevInfo.paintY + prevInfo.paintHeight, paintY + paintHeight);
1304
                // at least one collapsed -> paint plus sign
1305
                boolean collapsed = prevInfo.isCollapsed() || isCollapsed();
1306
                int offsetFrom = Math.min(prevInfo.startOffset, startOffset);
1307
                int offsetTo = Math.max(prevInfo.endOffset, endOffset);
1308
                
1309
                this.paintY = paintFrom;
1310
                this.paintHeight = paintTo - paintFrom;
1311
                this.isCollapsed = collapsed;
1312
                this.startOffset = offsetFrom;
1313
                this.endOffset = offsetTo;
1314
            }
1315
            this.paintOperation = operation;
1316
            this.allCollapsed = prevInfo.allCollapsed && allCollapsed;
1317
            this.innerLevel = level1;
1318
            this.outgoingLevel = level2;
1319
            this.lineIn |= lineIn;
1320
            this.lineOut |= lineOut;
1321
            
1322
            this.signActive |= prevInfo.signActive;
1323
            this.lineInActive |= prevInfo.lineInActive;
1324
            this.lineOutActive |= prevInfo.lineOutActive;
1325
            
1326
            LOG.log(Level.FINE, "Merged result: {0}", this);
1327
        }
1328
    }
1329
    
1330
    /** Keeps info of visible folding mark */
1331
    public class Mark{
1332
        public int x;
1333
        public int y;
1334
        public int size;
1335
        public boolean isFolded;
1336
        
1337
        public Mark(int x, int y, int size, boolean isFolded){
1338
            this.x = x;
1339
            this.y = y;
1340
            this.size = size;
1341
            this.isFolded = isFolded;
1342
        }
1343
    }
1344
    
1345
    private final class Listener extends MouseAdapter implements FoldHierarchyListener, DocumentListener, Runnable {
1346
    
1347
        public Listener(){
1348
        }
1349
1350
        // --------------------------------------------------------------------
1351
        // FoldHierarchyListener implementation
1352
        // --------------------------------------------------------------------
1353
1354
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
1355
            refresh();
1356
        }
1357
1358
        // --------------------------------------------------------------------
1359
        // DocumentListener implementation
1360
        // --------------------------------------------------------------------
1361
1362
        public void insertUpdate(DocumentEvent evt) {
1363
            if (!(evt instanceof BaseDocumentEvent)) return;
1364
1365
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1366
            if (bevt.getLFCount() > 0) { // one or more lines inserted
1367
                refresh();
1368
            }
1369
        }
1370
1371
        public void removeUpdate(DocumentEvent evt) {
1372
            if (!(evt instanceof BaseDocumentEvent)) return;
1373
1374
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1375
            if (bevt.getLFCount() > 0) { // one or more lines removed
1376
                refresh();
1377
            }
1378
        }
1379
1380
        public void changedUpdate(DocumentEvent evt) {
1381
        }
1382
1383
        // --------------------------------------------------------------------
1384
        // MouseListener implementation
1385
        // --------------------------------------------------------------------
1386
1387
        @Override
1388
        public void mousePressed (MouseEvent e) {
1389
            Mark mark = getClickedMark(e);
1390
            if (mark!=null){
1391
                e.consume();
1392
                performAction(mark, (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
1393
            }
1394
        }
1395
1396
        @Override
1397
        public void mouseClicked(MouseEvent e) {
1398
            // #102288 - missing event consuming caused quick doubleclicks to break
1399
            // fold expanding/collapsing and move caret to the particular line
1400
            if (e.getClickCount() > 1) {
1401
                LOG.log(Level.FINEST, "Mouse {0}click at {1}", new Object[] { e.getClickCount(), e.getY()});
1402
                Mark mark = getClickedMark(e);
1403
                try {
1404
                    performActionAt(mark, e.getY());
1405
                } catch (BadLocationException ex) {
1406
                    LOG.log(Level.WARNING, "Error during fold expansion using sideline", ex);
1407
                }
1408
            } else {
1409
                e.consume();
1410
            }
1411
        }
1412
1413
        private void refreshIfMouseOutside(Point pt) {
1414
            mousePoint = (int)pt.getY();
1415
            if (LOG.isLoggable(Level.FINEST)) {
1416
                if (mouseBoundary == null) {
1417
                    LOG.log(Level.FINEST, "Mouse boundary not set, refreshing: {0}", mousePoint);
1418
                } else {
1419
                    LOG.log(Level.FINEST, "Mouse {0} inside known mouse boundary: {1}-{2}", 
1420
                            new Object[] { mousePoint, mouseBoundary.y, mouseBoundary.getMaxY() });
1421
                }
1422
            }
1423
            if (mouseBoundary == null || mousePoint < mouseBoundary.y || mousePoint > mouseBoundary.getMaxY()) {
1424
                refresh();
1425
            }
1426
        }
1427
        
1428
        @Override
1429
        public void mouseMoved(MouseEvent e) {
1430
            refreshIfMouseOutside(e.getPoint());
1431
        }
1432
        
1433
        public void mouseEntered(MouseEvent e) {
1434
            refreshIfMouseOutside(e.getPoint());
1435
        }
1436
        
1437
        public void mouseExited(MouseEvent e) {
1438
            mousePoint = NO_MOUSE_POINT;
1439
            refresh();
1440
        }
1441
        
1442
1443
        // --------------------------------------------------------------------
1444
        // private implementation
1445
        // --------------------------------------------------------------------
1446
1447
        private Mark getClickedMark(MouseEvent e){
1448
            if (e == null || !SwingUtilities.isLeftMouseButton(e)) {
1449
                return null;
1450
            }
1451
            
1452
            int x = e.getX();
1453
            int y = e.getY();
1454
            for (Mark mark : visibleMarks) {
1455
                if (x >= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)) {
1456
                    return mark;
1457
                }
1458
            }
1459
            return null;
1460
        }
1461
1462
        private void refresh() {
1463
            SwingUtilities.invokeLater(this);
1464
        }
1465
1466
        @Override
1467
        public void run() {
1468
            if (getPreferredSize().width == 0 && enabled) {
1469
                updatePreferredSize();
1470
            }
1471
            repaint();
1472
        }
1473
    } // End of Listener class
1474
    
1475
    @Override
1476
    public AccessibleContext getAccessibleContext() {
1477
        if (accessibleContext == null) {
1478
            accessibleContext = new AccessibleJComponent() {
1479
                public @Override AccessibleRole getAccessibleRole() {
1480
                    return AccessibleRole.PANEL;
1481
                }
1482
            };
1483
            accessibleContext.setAccessibleName(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSN_CodeFoldingSideBar")); //NOI18N
1484
        accessibleContext.setAccessibleDescription(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSD_CodeFoldingSideBar")); //NOI18N
1485
        }
1486
        return accessibleContext;
1487
    }
1488
1489
    private Coloring getColoring() {
1490
        if (attribs == null) {
1491
            if (fcsLookupResult == null) {
1492
                fcsLookupResult = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component))
1493
                        .lookupResult(FontColorSettings.class);
1494
                fcsLookupResult.addLookupListener(WeakListeners.create(LookupListener.class, fcsTracker, fcsLookupResult));
1495
            }
1496
            
1497
            FontColorSettings fcs = fcsLookupResult.allInstances().iterator().next();
1498
            AttributeSet attr = fcs.getFontColors(FontColorNames.CODE_FOLDING_BAR_COLORING);
1499
            if (attr == null) {
1500
                attr = fcs.getFontColors(FontColorNames.DEFAULT_COLORING);
1501
            } else {
1502
                attr = AttributesUtilities.createComposite(attr, fcs.getFontColors(FontColorNames.DEFAULT_COLORING));
1503
            }
1504
            attribs = attr;
1505
        }        
1506
        return Coloring.fromAttributeSet(attribs);
1507
    }
1508
1509
    /**
1510
     * Factory for the sidebars. Use {@link FoldUtilities#getFoldingSidebarFactory()} to
1511
     * obtain an instance.
1512
     */
1513
    public static class Factory implements SideBarFactory {
1514
        @Override
1515
        public JComponent createSideBar(JTextComponent target) {
1516
            return new CodeFoldingSideBar(target);
1517
        }
1518
    }
1519
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/CustomizerWithDefaults.java (+62 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.ui;
43
44
import java.util.prefs.Preferences;
45
46
/**
47
 * Mixin interface, that allows to configure a Customizer to be configured with parent preferences.
48
 * The parent preferences may be either defaults stored or predefined, or preferences for the parent Mime type.
49
 * The customizer <b>must not</b> write to the default preferences
50
 * 
51
 * @author sdedic
52
 */
53
public interface CustomizerWithDefaults {
54
    /**
55
     * Provides default preferences to the customizer. All language preferences,
56
     * or text/xml preferences are passed this way to the language customizer,
57
     * to serve as a basis. The default preferences 
58
     * 
59
     * @param pref default preferences
60
     */
61
    public void setDefaultPreferences(Preferences pref);
62
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/DefaultFoldingOptions.form (+59 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8" ?>
2
3
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
4
  <AuxValues>
5
    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
6
    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
7
    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
8
    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
9
    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
10
    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
11
    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
12
    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
13
    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
14
  </AuxValues>
15
16
  <Layout>
17
    <DimensionLayout dim="0">
18
      <Group type="103" groupAlignment="0" attributes="0">
19
          <Component id="collapseContainer" pref="180" max="32767" attributes="0"/>
20
      </Group>
21
    </DimensionLayout>
22
    <DimensionLayout dim="1">
23
      <Group type="103" groupAlignment="0" attributes="0">
24
          <Component id="collapseContainer" alignment="0" pref="151" max="32767" attributes="0"/>
25
      </Group>
26
    </DimensionLayout>
27
  </Layout>
28
  <SubComponents>
29
    <Container class="javax.swing.JPanel" name="collapseContainer">
30
      <Properties>
31
        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
32
          <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
33
            <TitledBorder title="Collapse by default">
34
              <ResourceString PropertyName="titleX" bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="DefaultFoldingOptions.collapseContainer.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
35
            </TitledBorder>
36
          </Border>
37
        </Property>
38
      </Properties>
39
      <AuxValues>
40
        <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-105,0,0,0,-84"/>
41
      </AuxValues>
42
43
      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
44
      <SubComponents>
45
        <Container class="javax.swing.JPanel" name="localSwitchboard">
46
          <Constraints>
47
            <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
48
              <BorderConstraints direction="Center"/>
49
            </Constraint>
50
          </Constraints>
51
52
          <Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
53
            <Property name="useNullLayout" type="boolean" value="true"/>
54
          </Layout>
55
        </Container>
56
      </SubComponents>
57
    </Container>
58
  </SubComponents>
59
</Form>
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/DefaultFoldingOptions.java (+463 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.ui;
43
44
import java.awt.GridLayout;
45
import java.awt.event.ItemEvent;
46
import java.awt.event.ItemListener;
47
import java.util.ArrayList;
48
import java.util.Collection;
49
import java.util.Collections;
50
import java.util.HashSet;
51
import java.util.Iterator;
52
import java.util.List;
53
import java.util.Set;
54
import java.util.logging.Level;
55
import java.util.logging.Logger;
56
import java.util.prefs.PreferenceChangeEvent;
57
import java.util.prefs.PreferenceChangeListener;
58
import java.util.prefs.Preferences;
59
import javax.swing.JCheckBox;
60
import javax.swing.JComponent;
61
import javax.swing.SwingUtilities;
62
import javax.swing.event.ChangeEvent;
63
import javax.swing.event.ChangeListener;
64
import org.netbeans.api.editor.fold.FoldType;
65
import org.netbeans.api.editor.fold.FoldUtilities;
66
import org.netbeans.api.editor.mimelookup.MimePath;
67
import org.netbeans.api.editor.settings.SimpleValueNames;
68
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
69
import org.netbeans.modules.editor.settings.storage.api.EditorSettings;
70
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
71
import org.openide.util.WeakListeners;
72
73
/**
74
 * Default implementation of folding options. The dialog will only display
75
 * checboxes, for each of the registered FoldTypes. FoldTypes are displayed
76
 * in same order, as they are enumerated by FoldUtilities.getFoldTypes().
77
 * <p/>
78
 * The dialog will read and set preference values {@code code-folding-collapse-X},
79
 * where X is obtained from {@link FoldType#code()}.
80
 *
81
 * @author sdedic
82
 */
83
public final class DefaultFoldingOptions extends javax.swing.JPanel 
84
implements PreferenceChangeListener, ChangeListener, CustomizerWithDefaults, ItemListener {
85
    
86
    private static final Logger LOG = Logger.getLogger(DefaultFoldingOptions.class.getName());
87
    
88
    public static final String COLLAPSE_PREFIX = FoldUtilitiesImpl.PREF_COLLAPSE_PREFIX;
89
    
90
    public static final String PREF_OVERRIDE_DEFAULTS = FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS;
91
    
92
    private static final Set<FoldType> LEGACY_FOLD_TYPES = new HashSet<FoldType>();
93
    
94
    static {
95
        LEGACY_FOLD_TYPES.add(FoldType.CODE_BLOCK);
96
        LEGACY_FOLD_TYPES.add(FoldType.INITIAL_COMMENT);
97
        LEGACY_FOLD_TYPES.add(FoldType.DOCUMENTATION);
98
        LEGACY_FOLD_TYPES.add(FoldType.TAG);
99
        LEGACY_FOLD_TYPES.add(FoldType.MEMBER);
100
        LEGACY_FOLD_TYPES.add(FoldType.NESTED);
101
        LEGACY_FOLD_TYPES.add(FoldType.IMPORT);
102
    }
103
    
104
    /**
105
     * Mimetype for which these options work
106
     */
107
    private String mimeType;
108
    
109
    /**
110
     * Transient preferences, contains current values + defaults.
111
     */
112
    private Preferences preferences;
113
114
    /**
115
     * Preferences for the super mimetype (or "")
116
     */
117
    private Preferences defaultPrefs;
118
    
119
    /**
120
     * Fold types registered for this MIME type. For "" type, the fold types
121
     * are filtered for those, which are actually used by any of the registered MIME types
122
     */
123
    private Collection<? extends FoldType> types;
124
    
125
    /**
126
     * Checkbox controls for collapse-* options
127
     */
128
    private Collection<JCheckBox> controls = new ArrayList<JCheckBox>();
129
130
    private PreferenceChangeListener weakL;
131
    
132
    private Collection<String>    parentFoldTypes;
133
    
134
    /**
135
     * Creates new form DefaultFoldingOptions
136
     */
137
    public DefaultFoldingOptions(String mime, Preferences preferences) {
138
        initComponents();
139
	 
140
        VerticalFlowLayout vfl = new VerticalFlowLayout();
141
        localSwitchboard.setLayout(vfl);
142
        
143
        vfl = new VerticalFlowLayout();
144
        localSwitchboard.setLayout(vfl);
145
146
        this.mimeType = mime;
147
        this.preferences = preferences;
148
        
149
        String parentMime = MimePath.parse(mime).getInheritedType();
150
        if (parentMime != null) {
151
            parentFoldTypes = new HashSet<String>(13);
152
            for (FoldType ft : FoldUtilities.getFoldTypes(parentMime).values()) {
153
                parentFoldTypes.add(ft.code());
154
            }
155
        } else {
156
            parentFoldTypes = Collections.emptyList();
157
        }
158
    }
159
160
    @Override
161
    public void setDefaultPreferences(Preferences pref) {
162
        if (this.defaultPrefs != null) {
163
            defaultPrefs.removePreferenceChangeListener(weakL);
164
        }
165
        // anomaly: if the local Preference contains 'removedKey' for a preference
166
        // which has been turned to default, it blocks all change propagations.
167
        this.defaultPrefs = pref;
168
        if (pref != null) {
169
            weakL = WeakListeners.create(PreferenceChangeListener.class, this, pref);
170
            pref.addPreferenceChangeListener(weakL);
171
        }
172
    }
173
    
174
    private static String k(FoldType ft) {
175
        return COLLAPSE_PREFIX + ft.code();
176
    }
177
    
178
    private JCheckBox createCheckBox(FoldType ft) {
179
        return new JCheckBox();
180
    }
181
    
182
    /**
183
     * Filters types to contain only mimetypes, which are actually used in 
184
     * one or more mimetypes. Used only for "" mimetype (defaults).
185
     */
186
    private void filterUsedMimeTypes() {
187
        Set<String> mimeTypes = EditorSettings.getDefault().getAllMimeTypes();
188
        Set<String> codes = new HashSet<String>();
189
        for (String mt : mimeTypes) {
190
            Collection<? extends FoldType> fts = FoldUtilities.getFoldTypes(mt).values();
191
            for (FoldType ft : fts) {
192
                codes.add(ft.code());
193
                if (ft.parent() != null) {
194
                    codes.add(ft.parent().code());
195
                }
196
            }
197
        }
198
        for (Iterator<? extends FoldType> it = types.iterator(); it.hasNext();) {
199
            FoldType ft = it.next();
200
            if (LEGACY_FOLD_TYPES.contains(ft)) {
201
                continue;
202
            }
203
            if (!codes.contains(ft.code())) {
204
                it.remove();
205
            }
206
        }
207
    }
208
    
209
    private void load() {
210
        types = new ArrayList<FoldType>(FoldUtilities.getFoldTypes(mimeType).values());
211
        if ("".equals(mimeType)) { // NOI18N
212
            filterUsedMimeTypes();
213
        }
214
215
        boolean override = isCollapseRedefined();
216
        boolean useDefaultsState = 
217
                !(preferences.getBoolean(PREF_OVERRIDE_DEFAULTS, false) ||
218
                isDefinedLocally(PREF_OVERRIDE_DEFAULTS));
219
        if (override != useDefaultsState) {
220
            updateOverrideChanged();
221
        }
222
        
223
        for (FoldType ft : types) {
224
            String name = ft.getLabel();
225
            
226
            JCheckBox cb = createCheckBox(ft);
227
            cb.setText(name);
228
            cb.putClientProperty("id", ft.code()); // NOI18N
229
            cb.putClientProperty("type", ft); // NOI18N
230
            localSwitchboard.add(cb);
231
            controls.add(cb);
232
            cb.addItemListener(this);
233
        }
234
        
235
        // watch out for preferences
236
        this.preferences.addPreferenceChangeListener(this);
237
        updateEnabledState();
238
    }
239
    
240
    /**
241
     * Checks whether some of the collapse- options is redefined for this MIME type.
242
     * If so, the checkbox will be selected even though the override option is not
243
     * present.
244
     * 
245
     * @return 
246
     */
247
    private boolean isCollapseRedefined() {
248
        for (FoldType ft : types) {
249
            String pref = k(ft);
250
            if (((OverridePreferences)preferences).isOverriden(pref)) {
251
               if (defaultPrefs == null || 
252
                    (parentFoldTypes.contains(ft.code()) || (ft.parent() != null && parentFoldTypes.contains(ft.parent().code())))) {
253
                    return true;
254
               }
255
            }
256
        }
257
        return false;
258
    }
259
    
260
    private boolean loaded;
261
262
    @Override
263
    public void addNotify() {
264
        super.addNotify();
265
        if (!loaded) {
266
            load();
267
            updateEnabledState();
268
            updateValueState();
269
            loaded = true;
270
        }
271
    }
272
273
    @Override
274
    public void preferenceChange(final PreferenceChangeEvent evt) {
275
        SwingUtilities.invokeLater(new Runnable() {
276
            public void run() {
277
                updateCheckers(evt);
278
            }
279
        });
280
    }
281
    
282
    private void updateValueState() {
283
        ignoreStateChange = true;
284
        for (JCheckBox cb : controls) {
285
            FoldType ft = (FoldType)cb.getClientProperty("type"); // NOI18N
286
            String k = COLLAPSE_PREFIX + ft.code();
287
            boolean val = isCollapseEnabled(ft);
288
            cb.setSelected(val);
289
        }
290
        ignoreStateChange = false;
291
    }
292
    
293
    private void updateEnabledState() {
294
        boolean foldEnable = preferences.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, true);
295
        boolean useDefaults = preferences.getBoolean(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS, true);
296
        
297
        for (JComponent c : controls) {
298
            FoldType ft = (FoldType)c.getClientProperty("type"); // NOI18N
299
            boolean enable = foldEnable;
300
            if (defaultPrefs != null && useDefaults) {
301
                if (!parentFoldTypes.contains(ft.code())) {
302
                    if (ft.parent() == null || !parentFoldTypes.contains(ft.parent().code())) {
303
                        continue;
304
                    }
305
                }
306
                enable &= !isDefinedDefault(ft);
307
            }
308
            c.setEnabled(enable);
309
        }
310
    }
311
    
312
    private void updateCheckers(PreferenceChangeEvent evt) {
313
        String pk = evt.getKey();
314
        if (pk.equals(SimpleValueNames.CODE_FOLDING_ENABLE)) {
315
            updateEnabledState();
316
            return;
317
        }
318
        if (pk.equals(PREF_OVERRIDE_DEFAULTS)) {
319
            updateOverrideChanged();
320
        } else if (!pk.startsWith(COLLAPSE_PREFIX)) {
321
            return;
322
        }
323
        String c = pk.substring(COLLAPSE_PREFIX.length());
324
        for (JCheckBox cb : controls) {
325
            FoldType ft = (FoldType)cb.getClientProperty("type"); // NOI18N
326
            FoldType ftp = ft.parent();
327
            if (ft.code().equals(c) || (ftp != null && ftp.code().equals(c))) {
328
                updateChecker(pk, cb, ft);
329
                return;
330
            }
331
        }
332
    }
333
    
334
    private boolean isCollapseEnabled(FoldType ft) {
335
        if (defaultPrefs == null) {
336
            return preferences.getBoolean(k(ft),
337
                    ft.parent() == null ? false
338
                    : preferences.getBoolean(k(ft.parent()), false));
339
        } else {
340
            String k = k(ft);
341
            return preferences.getBoolean(k,
342
                    defaultPrefs.getBoolean(k,
343
                    ft.parent() == null ? false
344
                    : preferences.getBoolean(k(ft.parent()), false)));
345
        }
346
    }
347
    
348
    private void updateOverrideChanged() {
349
        boolean en = preferences.getBoolean(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS, true);
350
        if (defaultPrefs == null) {
351
            return;
352
        }
353
        if (en) {
354
            // persist all foldtype settings:
355
            for (FoldType ft : types) {
356
                preferences.putBoolean(k(ft), 
357
			   defaultPrefs.getBoolean(k(ft),
358
	                     ft.parent() == null ? false :
359
	                 defaultPrefs.getBoolean(k(ft.parent()), false))
360
		  );
361
            }
362
        } else {
363
            for (FoldType ft : types) {
364
                if (isDefinedDefault(ft)) {
365
                    preferences.remove(k(ft));
366
                }
367
            }
368
        }
369
        updateEnabledState();
370
        updateValueState();
371
    }
372
    
373
    private boolean isDefinedDefault(FoldType ft) {
374
        return parentFoldTypes.contains(ft.code()) ||
375
            (ft.parent() != null && parentFoldTypes.contains(ft.parent().code()));
376
    }
377
    
378
    private boolean isDefinedLocally(String prefKey) {
379
        return !(preferences instanceof OverridePreferences) ||
380
                ((OverridePreferences)preferences).isOverriden(prefKey);
381
    }
382
    
383
    private void updateChecker(String prefKey, JCheckBox cb, FoldType ft) {
384
        if (lastChangedCB == cb) {
385
            // ignore
386
            lastChangedCB = null;
387
            return;
388
        }
389
        boolean val = isCollapseEnabled(ft);
390
        ignoreStateChange = true;
391
        LOG.log(Level.INFO, "Updating checker: " + prefKey + ", setSelected " + val);
392
        cb.setSelected(val);
393
        ignoreStateChange = false;
394
    }
395
    
396
    private boolean ignoreStateChange;
397
    
398
    private JCheckBox lastChangedCB;
399
400
    @Override
401
    public void itemStateChanged(final ItemEvent e) {
402
        if (ignoreStateChange) {
403
            return;
404
        }
405
        SwingUtilities.invokeLater(new Runnable() {
406
            public void run() {
407
                updatePref(e);
408
            }
409
        });
410
    }
411
    
412
    private void updatePref(ItemEvent e) {
413
        JCheckBox cb = (JCheckBox)e.getSource();
414
        FoldType ft = (FoldType)cb.getClientProperty("type"); // NOI18N
415
        
416
        String prefKey = COLLAPSE_PREFIX + ft.code();
417
        lastChangedCB = cb;
418
        LOG.log(Level.INFO, "Updating preference: " + prefKey + ", value = " + cb.isSelected());
419
        preferences.putBoolean(prefKey, cb.isSelected());
420
    }
421
    
422
    @Override
423
    public void stateChanged(ChangeEvent e) {
424
        if (ignoreStateChange) {
425
            return;
426
        }
427
    }
428
    
429
    /**
430
     * This method is called from within the constructor to initialize the form.
431
     * WARNING: Do NOT modify this code. The content of this method is always
432
     * regenerated by the Form Editor.
433
     */
434
    @SuppressWarnings("unchecked")
435
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
436
    private void initComponents() {
437
438
        collapseContainer = new javax.swing.JPanel();
439
        localSwitchboard = new javax.swing.JPanel();
440
441
        collapseContainer.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(DefaultFoldingOptions.class, "DefaultFoldingOptions.collapseContainer.border.title"))); // NOI18N
442
        collapseContainer.setLayout(new java.awt.BorderLayout());
443
444
        localSwitchboard.setLayout(null);
445
        collapseContainer.add(localSwitchboard, java.awt.BorderLayout.CENTER);
446
447
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
448
        this.setLayout(layout);
449
        layout.setHorizontalGroup(
450
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
451
            .addComponent(collapseContainer, javax.swing.GroupLayout.DEFAULT_SIZE, 180, Short.MAX_VALUE)
452
        );
453
        layout.setVerticalGroup(
454
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
455
            .addComponent(collapseContainer, javax.swing.GroupLayout.DEFAULT_SIZE, 151, Short.MAX_VALUE)
456
        );
457
    }// </editor-fold>//GEN-END:initComponents
458
459
    // Variables declaration - do not modify//GEN-BEGIN:variables
460
    private javax.swing.JPanel collapseContainer;
461
    private javax.swing.JPanel localSwitchboard;
462
    // End of variables declaration//GEN-END:variables
463
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/FoldOptionsController.java (+237 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.ui;
43
44
import java.beans.PropertyChangeListener;
45
import java.beans.PropertyChangeSupport;
46
import java.util.HashMap;
47
import java.util.Map;
48
import java.util.prefs.BackingStoreException;
49
import java.util.prefs.PreferenceChangeEvent;
50
import java.util.prefs.PreferenceChangeListener;
51
import java.util.prefs.Preferences;
52
import javax.swing.JComponent;
53
import org.netbeans.api.editor.mimelookup.MimeLookup;
54
import org.netbeans.api.editor.mimelookup.MimePath;
55
import org.netbeans.api.editor.settings.SimpleValueNames;
56
import org.netbeans.api.options.OptionsDisplayer;
57
import org.netbeans.modules.editor.settings.storage.api.EditorSettings;
58
import org.netbeans.modules.editor.settings.storage.api.MemoryPreferences;
59
import org.netbeans.spi.options.OptionsPanelController;
60
import org.openide.util.Exceptions;
61
import org.openide.util.HelpCtx;
62
import org.openide.util.Lookup;
63
import org.openide.util.WeakListeners;
64
65
/**
66
 * Controller for the Folding options tab.
67
 * The controller manages the MemoryPreferences storage, which is then passed to individual folding
68
 * customizer panels. It also tracks 'dirty' status of all the language panels: if one of them becomes dirty,
69
 * the whole page starts to report dirty = true.
70
 * <p/>
71
 * This class manages only the language switch + overall 'enable folding' setting. All settings
72
 * for a given language are handled by the language-specific panel.
73
 *
74
 * @author sdedic
75
 */
76
@OptionsPanelController.SubRegistration(
77
    displayName="org.netbeans.modules.editor.fold.ui.Bundle#CTL_OptionsDisplayName",
78
    keywords="org.netbeans.modules.editor.fold.ui.Bundle#KW_Options",
79
    keywordsCategory="Editor/Folding",
80
    id="Folding", // XXX used anywhere?
81
    location=OptionsDisplayer.EDITOR,
82
    position=110
83
//    toolTip="org.netbeans.modules.options.editor.Bundle#CTL_General_ToolTip"
84
)
85
public class FoldOptionsController extends OptionsPanelController implements PreferenceChangeListener {
86
    /**
87
     * The main panel.
88
     */
89
    private FoldOptionsPanel panel;
90
    
91
    /**
92
     * True, if some of the created panels became dirty
93
     */
94
    private boolean changed;
95
    
96
    /**
97
     * for firing PROP_DIRTY
98
     */
99
    private PropertyChangeSupport propSupport = new PropertyChangeSupport(this);
100
101
    /**
102
     * Preferences created for individual MIME types, as they are displaye by the user
103
     */
104
    private Map<String, MemoryPreferences>    preferences = new HashMap<String, MemoryPreferences>();
105
    
106
    @Override
107
    public void update() {
108
        clearContents();
109
        if (panel != null) {
110
            panel.update();
111
        }
112
    }
113
    
114
    @Override
115
    public void applyChanges() {
116
        for (MemoryPreferences p : preferences.values()) {
117
            try {
118
                p.getPreferences().flush();
119
            } catch (BackingStoreException ex) {
120
                Exceptions.printStackTrace(ex);
121
            }
122
        }
123
        changed = false;
124
        propSupport.firePropertyChange(PROP_CHANGED, true, false);
125
    }
126
127
    @Override
128
    public void preferenceChange(PreferenceChangeEvent evt) {
129
        boolean ch = detectIsChanged();
130
        if (ch != changed) {
131
            changed = ch;
132
            propSupport.firePropertyChange(PROP_CHANGED, !ch, ch);
133
        }
134
    }
135
    
136
    private PreferenceChangeListener weakChangeL = WeakListeners.create(PreferenceChangeListener.class, this, null);
137
    
138
    void globalEnableFolding(boolean enable) {
139
        prefs("").putBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, enable);
140
        for (String mime : EditorSettings.getDefault().getAllMimeTypes()) {
141
            prefs(mime).remove(SimpleValueNames.CODE_FOLDING_ENABLE);
142
        }
143
    }
144
    
145
    /* called from the FoldOptionsPanel */
146
    Preferences prefs(String mime) {
147
        MemoryPreferences cached = preferences.get(mime);
148
        if (cached != null) {
149
            return cached.getPreferences();
150
        }
151
        MimePath path = MimePath.parse(mime);
152
        Preferences result = MimeLookup.getLookup(mime).lookup(Preferences.class);
153
        
154
        if (!mime.equals("")) {
155
            String parentMime = path.getInheritedType();
156
            /*
157
            result = new InheritedPreferences(
158
                    prefs(parentMime), result);
159
                    */
160
            cached = MemoryPreferences.getWithInherited(this, 
161
                prefs(parentMime),
162
                result);
163
        } else {
164
            cached = MemoryPreferences.get(this, result);
165
        }
166
        cached.getPreferences().addPreferenceChangeListener(weakChangeL);
167
        preferences.put(mime, cached);
168
        return cached.getPreferences();
169
    }
170
    
171
    private void clearContents() {
172
        // clear the old preference values and recreate the panel
173
        for (MemoryPreferences m : preferences.values()) {
174
            m.getPreferences().removePreferenceChangeListener(weakChangeL);
175
            m.destroy();
176
        }
177
        preferences.clear();
178
        if (panel != null) {
179
            panel.clear();
180
        }
181
        changed = false;
182
    }
183
184
    @Override
185
    public void cancel() {
186
        clearContents();
187
    }
188
189
    @Override
190
    public boolean isValid() {
191
        return true;
192
        
193
    }
194
195
    @Override
196
    public boolean isChanged() {
197
        return changed;
198
        
199
    }
200
    
201
    private boolean detectIsChanged() {
202
        for (MemoryPreferences cached : preferences.values()) {
203
            if (!cached.isDirty(cached.getPreferences())) {
204
                return true;
205
            }
206
        }
207
        return false;
208
    }
209
210
    @Override
211
    public JComponent getComponent(Lookup masterLookup) {
212
        return getPanel();
213
    }
214
215
    @Override
216
    public HelpCtx getHelpCtx() {
217
        return new HelpCtx ("netbeans.optionsDialog.editor.folding");
218
    }
219
220
    @Override
221
    public void addPropertyChangeListener(PropertyChangeListener l) {
222
        propSupport.addPropertyChangeListener(l);
223
    }
224
225
    @Override
226
    public void removePropertyChangeListener(PropertyChangeListener l) {
227
        propSupport.removePropertyChangeListener(l);
228
    }
229
    
230
    private FoldOptionsPanel getPanel() {
231
        if (panel == null) {
232
            panel = new FoldOptionsPanel(this);
233
        }
234
        return panel;
235
    }
236
    
237
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/FoldOptionsPanel.form (+155 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8" ?>
2
3
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
4
  <AuxValues>
5
    <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
6
    <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
7
    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
8
    <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
9
    <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
10
    <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
11
    <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
12
    <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
13
    <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
14
  </AuxValues>
15
16
  <Layout>
17
    <DimensionLayout dim="0">
18
      <Group type="103" groupAlignment="0" attributes="0">
19
          <Group type="102" alignment="0" attributes="0">
20
              <EmptySpace max="-2" attributes="0"/>
21
              <Group type="103" groupAlignment="0" attributes="0">
22
                  <Component id="content" max="32767" attributes="0"/>
23
                  <Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
24
                  <Group type="102" attributes="0">
25
                      <Group type="103" groupAlignment="0" attributes="0">
26
                          <Component id="useDefaults" min="-2" max="-2" attributes="0"/>
27
                          <Group type="102" attributes="0">
28
                              <Component id="langLabel" min="-2" max="-2" attributes="0"/>
29
                              <EmptySpace max="-2" attributes="0"/>
30
                              <Component id="langSelect" min="-2" pref="186" max="-2" attributes="0"/>
31
                              <EmptySpace type="unrelated" max="-2" attributes="0"/>
32
                              <Component id="enableFolds" min="-2" max="-2" attributes="0"/>
33
                          </Group>
34
                      </Group>
35
                      <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
36
                  </Group>
37
              </Group>
38
              <EmptySpace max="-2" attributes="0"/>
39
          </Group>
40
      </Group>
41
    </DimensionLayout>
42
    <DimensionLayout dim="1">
43
      <Group type="103" groupAlignment="0" attributes="0">
44
          <Group type="102" alignment="0" attributes="0">
45
              <EmptySpace max="-2" attributes="0"/>
46
              <Group type="103" groupAlignment="3" attributes="0">
47
                  <Component id="langSelect" alignment="3" min="-2" max="-2" attributes="0"/>
48
                  <Component id="langLabel" alignment="3" min="-2" max="-2" attributes="0"/>
49
                  <Component id="enableFolds" alignment="3" min="-2" max="-2" attributes="0"/>
50
              </Group>
51
              <EmptySpace type="unrelated" max="-2" attributes="0"/>
52
              <Component id="useDefaults" min="-2" max="-2" attributes="0"/>
53
              <EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
54
              <Component id="content" pref="25" max="32767" attributes="0"/>
55
              <EmptySpace type="separate" max="-2" attributes="0"/>
56
              <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
57
              <EmptySpace min="-2" max="-2" attributes="0"/>
58
          </Group>
59
      </Group>
60
    </DimensionLayout>
61
  </Layout>
62
  <SubComponents>
63
    <Component class="javax.swing.JComboBox" name="langSelect">
64
      <Properties>
65
        <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
66
          <StringArray count="0"/>
67
        </Property>
68
      </Properties>
69
    </Component>
70
    <Component class="javax.swing.JLabel" name="langLabel">
71
      <Properties>
72
        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
73
          <ResourceString bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="FoldOptionsPanel.langLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
74
        </Property>
75
      </Properties>
76
    </Component>
77
    <Container class="javax.swing.JPanel" name="content">
78
79
      <Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
80
    </Container>
81
    <Component class="javax.swing.JCheckBox" name="enableFolds">
82
      <Properties>
83
        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
84
          <ResourceString bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="FoldOptionsPanel.enableFolds.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
85
        </Property>
86
      </Properties>
87
      <Events>
88
        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="enableFoldsActionPerformed"/>
89
      </Events>
90
    </Component>
91
    <Container class="javax.swing.JPanel" name="jPanel1">
92
      <Properties>
93
        <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
94
          <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
95
            <TitledBorder title="Display Options">
96
              <ResourceString PropertyName="titleX" bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="Title_FoldDisplayOptions" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
97
            </TitledBorder>
98
          </Border>
99
        </Property>
100
      </Properties>
101
102
      <Layout>
103
        <DimensionLayout dim="0">
104
          <Group type="103" groupAlignment="0" attributes="0">
105
              <Group type="102" attributes="0">
106
                  <EmptySpace max="-2" attributes="0"/>
107
                  <Group type="103" groupAlignment="0" attributes="0">
108
                      <Component id="contentPreview" alignment="0" min="-2" max="-2" attributes="0"/>
109
                      <Component id="foldedSummary" alignment="0" min="-2" max="-2" attributes="0"/>
110
                  </Group>
111
                  <EmptySpace max="32767" attributes="0"/>
112
              </Group>
113
          </Group>
114
        </DimensionLayout>
115
        <DimensionLayout dim="1">
116
          <Group type="103" groupAlignment="0" attributes="0">
117
              <Group type="102" alignment="0" attributes="0">
118
                  <EmptySpace max="-2" attributes="0"/>
119
                  <Component id="contentPreview" min="-2" max="-2" attributes="0"/>
120
                  <EmptySpace max="-2" attributes="0"/>
121
                  <Component id="foldedSummary" min="-2" max="-2" attributes="0"/>
122
                  <EmptySpace max="32767" attributes="0"/>
123
              </Group>
124
          </Group>
125
        </DimensionLayout>
126
      </Layout>
127
      <SubComponents>
128
        <Component class="javax.swing.JCheckBox" name="contentPreview">
129
          <Properties>
130
            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
131
              <ResourceString bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="FoldOptionsPanel.contentPreview.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
132
            </Property>
133
          </Properties>
134
        </Component>
135
        <Component class="javax.swing.JCheckBox" name="foldedSummary">
136
          <Properties>
137
            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
138
              <ResourceString bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="FoldOptionsPanel.foldedSummary.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
139
            </Property>
140
          </Properties>
141
        </Component>
142
      </SubComponents>
143
    </Container>
144
    <Component class="javax.swing.JCheckBox" name="useDefaults">
145
      <Properties>
146
        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
147
          <ResourceString bundle="org/netbeans/modules/editor/fold/ui/Bundle.properties" key="FoldOptionsPanel.useDefaults.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
148
        </Property>
149
      </Properties>
150
      <Events>
151
        <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="useDefaultsActionPerformed"/>
152
      </Events>
153
    </Component>
154
  </SubComponents>
155
</Form>
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/FoldOptionsPanel.java (+424 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.ui;
43
44
import java.awt.CardLayout;
45
import java.awt.Component;
46
import java.awt.event.ActionEvent;
47
import java.awt.event.ActionListener;
48
import java.util.ArrayList;
49
import java.util.Collections;
50
import java.util.Comparator;
51
import java.util.HashMap;
52
import java.util.List;
53
import java.util.Map;
54
import java.util.Set;
55
import java.util.prefs.PreferenceChangeEvent;
56
import java.util.prefs.PreferenceChangeListener;
57
import java.util.prefs.Preferences;
58
import javax.swing.DefaultComboBoxModel;
59
import javax.swing.DefaultListCellRenderer;
60
import javax.swing.JComponent;
61
import javax.swing.JList;
62
import org.netbeans.api.editor.fold.FoldUtilities;
63
import org.netbeans.api.editor.mimelookup.MimePath;
64
import org.netbeans.api.editor.settings.SimpleValueNames;
65
import org.netbeans.api.lexer.Language;
66
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
67
import org.netbeans.modules.editor.settings.storage.api.EditorSettings;
68
import org.openide.util.NbBundle;
69
import org.openide.util.WeakListeners;
70
71
import static org.netbeans.modules.editor.fold.ui.Bundle.*;
72
73
/**
74
 * UI for the folding enable + language switch.
75
 * The panel contains a placeholder for language-specific contents. When language is selected, it replaces the
76
 * interior with a language-specific panel.
77
 *
78
 * @author sdedic
79
 */
80
final class FoldOptionsPanel extends javax.swing.JPanel implements ActionListener, PreferenceChangeListener {
81
    /**
82
     * All mime types presented in the selector
83
     */
84
    private List<String[]>  languageMimeTypes;
85
    
86
    /**
87
     * Our controller
88
     */
89
    private FoldOptionsController ctrl;
90
    
91
    private Preferences parentPrefs;
92
    
93
    /**
94
     * Creates new form FoldOptionsPanel
95
     */
96
    public FoldOptionsPanel(FoldOptionsController ctrl) {
97
        this.ctrl = ctrl;
98
        initComponents();
99
        
100
        langSelect.setRenderer(new DefaultListCellRenderer() {
101
            @Override
102
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
103
                if (value instanceof String[]) {
104
                    value = ((String[])value)[1];
105
                }
106
                return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); //To change body of generated methods, choose Tools | Templates.
107
            }
108
        });
109
        langSelect.addActionListener(this);
110
        contentPreview.addActionListener(this);
111
        foldedSummary.addActionListener(this);
112
        
113
        // preferences should be set as a reaction to index selection
114
    }
115
    
116
    /**
117
     * The preferences object for the currently selected language
118
     */
119
    private Preferences currentPreferences;
120
    
121
    /**
122
     * Panels created for individual Mime types
123
     */
124
    private Map<String, JComponent> panels = new HashMap<String, JComponent>();
125
    
126
    private PreferenceChangeListener wPrefL = WeakListeners.create(PreferenceChangeListener.class, this, null);
127
    
128
    private void languageSelected() {
129
        String[] sel = (String[])langSelect.getSelectedItem();
130
        if (sel == null) {
131
            return;
132
        }
133
        String mime = sel[0];
134
        if (currentPreferences != null) {
135
            currentPreferences.removePreferenceChangeListener(wPrefL);
136
        }
137
        currentPreferences = ctrl.prefs(mime);
138
        JComponent panel = panels.get(mime);
139
        String parentMime = MimePath.parse(mime).getInheritedType();
140
        if (parentMime != null) {
141
            parentPrefs = ctrl.prefs(parentMime);
142
        } else {
143
            parentPrefs = null;
144
        }
145
        if (panel == null) {
146
            panel = new DefaultFoldingOptions(mime, currentPreferences);
147
            if (panel instanceof CustomizerWithDefaults) {
148
                    ((CustomizerWithDefaults)panel).setDefaultPreferences(parentPrefs);
149
            }
150
            panels.put(mime, panel);
151
            content.add(panel, mime);
152
        }
153
        ((CardLayout)content.getLayout()).show(content, mime);
154
        currentPreferences.addPreferenceChangeListener(wPrefL);
155
        useDefaults.setVisible(!"".equals(mime)); // NOI18N
156
        preferenceChange(null);
157
    }
158
    
159
    private void previewChanged() {
160
        currentPreferences.putBoolean(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW, contentPreview.isSelected());
161
    }
162
    
163
    private void summaryChanged() {
164
        currentPreferences.putBoolean(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY, foldedSummary.isSelected());
165
    }
166
    
167
    @Override
168
    public void actionPerformed(ActionEvent e) {
169
        Object o = e.getSource();
170
        if (ignoreEnableTrigger) {
171
            return;
172
        }
173
        if (o == langSelect) {
174
            languageSelected();
175
        } else if (o == contentPreview) {
176
            previewChanged();
177
        } else if (o == foldedSummary) {
178
            summaryChanged();
179
        }
180
    }
181
182
    @Override
183
    public void preferenceChange(PreferenceChangeEvent evt) {
184
        String k = evt == null ? null : evt.getKey();
185
        ignoreEnableTrigger = true;
186
        try {
187
            if (k == null || k.equals(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS)) {
188
                useDefaults.setSelected(currentPreferences.getBoolean(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS, true));
189
            }
190
            if (k == null || k.equals(SimpleValueNames.CODE_FOLDING_ENABLE)) {
191
                boolean enabled = currentPreferences.getBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, true);
192
                enableFolds.setSelected(enabled);
193
                contentPreview.setEnabled(enabled);
194
                foldedSummary.setEnabled(enabled);
195
                useDefaults.setEnabled(enabled);
196
            } 
197
            if (k == null || FoldUtilitiesImpl.PREF_CONTENT_PREVIEW.equals(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW)) {
198
                contentPreview.setSelected(currentPreferences.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW, true));
199
            }
200
            if (k == null || FoldUtilitiesImpl.PREF_CONTENT_SUMMARY.equals(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY)) {
201
                foldedSummary.setSelected(currentPreferences.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY, true));
202
            } 
203
            if (k == null || FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS.equals(k)) {
204
                boolean b = parentPrefs == null || !currentPreferences.getBoolean(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS, true);
205
                if (parentPrefs != null) {
206
                    if (b) {
207
                        currentPreferences.putBoolean(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW, 
208
                                parentPrefs.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW, true));
209
                        currentPreferences.putBoolean(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY, 
210
                                parentPrefs.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY, true));
211
                    } else {
212
                        currentPreferences.remove(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW);
213
                        currentPreferences.remove(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY);
214
                    }
215
                }
216
                contentPreview.setEnabled(b);
217
                foldedSummary.setEnabled(b);
218
                contentPreview.setSelected(currentPreferences.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW, true));
219
                foldedSummary.setSelected(currentPreferences.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY, true));
220
            }
221
        } finally {
222
            ignoreEnableTrigger = false;
223
        }
224
    }
225
    
226
    void update() {
227
        initialize();
228
    }
229
    
230
    @NbBundle.Messages({
231
        "ITEM_AllLanguages=All Languages"
232
    })
233
    private void initialize() {
234
        Set<String> mimeTypes = EditorSettings.getDefault().getAllMimeTypes();
235
        List<String[]> langMimes = new ArrayList<String[]>(mimeTypes.size());
236
        langMimes.add(new String[] { "", ITEM_AllLanguages() }); // NOI18N
237
        for (String s : mimeTypes) {
238
            Language l = Language.find(s);
239
            if (l == null) {
240
                continue;
241
            }
242
            // filter out languages, whose author didn't name it - probably
243
            // unimportant && should not be displayed.
244
            String name = EditorSettings.getDefault().getLanguageName(s);
245
            if (name.equals(s)) {
246
                continue;
247
            }
248
            // last, discard everything that does not have any FoldTypes:
249
            if (FoldUtilities.getFoldTypes(s).values().isEmpty()) {
250
                continue;
251
            }
252
            langMimes.add(new String[] {
253
                s, EditorSettings.getDefault().getLanguageName(s)
254
            });
255
        }
256
        Collections.sort(langMimes, LANG_COMPARATOR);
257
        languageMimeTypes = langMimes;
258
        
259
        langSelect.setModel(new DefaultComboBoxModel(languageMimeTypes.toArray(new Object[languageMimeTypes.size()])));
260
        langSelect.setSelectedIndex(0);
261
    }
262
    
263
    void clear() {
264
        panels.clear();
265
    }
266
267
    /**
268
     * Special comparator, which sorts "" mime type first, other Mimetypes are then sorted based on the language names,
269
     * alphabetically. It is expected  that the 1st member of the String[]is a Mimetype string, the 2nd member is a 
270
     * human-readable language name.
271
     */
272
    private static final Comparator<String[]> LANG_COMPARATOR = new Comparator<String[]>() {
273
        @Override
274
        public int compare(String[] o1, String[] o2) {
275
            if (o1 == null) {
276
                return -1;
277
            } else if (o2 == null) {
278
                return 1;
279
            }
280
            if (o1[0].equals(o2[0])) {
281
                return 0;
282
            }
283
            // sort 'all languages' first
284
            if (o1[0].length() == 0) {
285
                return -1;
286
            } else if (o2[0].length() == 0) {
287
                return 1;
288
            }
289
            return o1[1].compareToIgnoreCase(o2[1]);
290
        }
291
    };
292
    
293
    private boolean ignoreEnableTrigger;
294
295
    /**
296
     * This method is called from within the constructor to initialize the form.
297
     * WARNING: Do NOT modify this code. The content of this method is always
298
     * regenerated by the Form Editor.
299
     */
300
    @SuppressWarnings("unchecked")
301
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
302
    private void initComponents() {
303
304
        langSelect = new javax.swing.JComboBox();
305
        langLabel = new javax.swing.JLabel();
306
        content = new javax.swing.JPanel();
307
        enableFolds = new javax.swing.JCheckBox();
308
        jPanel1 = new javax.swing.JPanel();
309
        contentPreview = new javax.swing.JCheckBox();
310
        foldedSummary = new javax.swing.JCheckBox();
311
        useDefaults = new javax.swing.JCheckBox();
312
313
        org.openide.awt.Mnemonics.setLocalizedText(langLabel, org.openide.util.NbBundle.getMessage(FoldOptionsPanel.class, "FoldOptionsPanel.langLabel.text")); // NOI18N
314
315
        content.setLayout(new java.awt.CardLayout());
316
317
        org.openide.awt.Mnemonics.setLocalizedText(enableFolds, org.openide.util.NbBundle.getMessage(FoldOptionsPanel.class, "FoldOptionsPanel.enableFolds.text")); // NOI18N
318
        enableFolds.addActionListener(new java.awt.event.ActionListener() {
319
            public void actionPerformed(java.awt.event.ActionEvent evt) {
320
                enableFoldsActionPerformed(evt);
321
            }
322
        });
323
324
        jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(FoldOptionsPanel.class, "Title_FoldDisplayOptions"))); // NOI18N
325
326
        org.openide.awt.Mnemonics.setLocalizedText(contentPreview, org.openide.util.NbBundle.getMessage(FoldOptionsPanel.class, "FoldOptionsPanel.contentPreview.text")); // NOI18N
327
328
        org.openide.awt.Mnemonics.setLocalizedText(foldedSummary, org.openide.util.NbBundle.getMessage(FoldOptionsPanel.class, "FoldOptionsPanel.foldedSummary.text")); // NOI18N
329
330
        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
331
        jPanel1.setLayout(jPanel1Layout);
332
        jPanel1Layout.setHorizontalGroup(
333
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
334
            .addGroup(jPanel1Layout.createSequentialGroup()
335
                .addContainerGap()
336
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
337
                    .addComponent(contentPreview)
338
                    .addComponent(foldedSummary))
339
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
340
        );
341
        jPanel1Layout.setVerticalGroup(
342
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
343
            .addGroup(jPanel1Layout.createSequentialGroup()
344
                .addContainerGap()
345
                .addComponent(contentPreview)
346
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
347
                .addComponent(foldedSummary)
348
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
349
        );
350
351
        org.openide.awt.Mnemonics.setLocalizedText(useDefaults, org.openide.util.NbBundle.getMessage(FoldOptionsPanel.class, "FoldOptionsPanel.useDefaults.text")); // NOI18N
352
        useDefaults.addActionListener(new java.awt.event.ActionListener() {
353
            public void actionPerformed(java.awt.event.ActionEvent evt) {
354
                useDefaultsActionPerformed(evt);
355
            }
356
        });
357
358
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
359
        this.setLayout(layout);
360
        layout.setHorizontalGroup(
361
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
362
            .addGroup(layout.createSequentialGroup()
363
                .addContainerGap()
364
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
365
                    .addComponent(content, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
366
                    .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
367
                    .addGroup(layout.createSequentialGroup()
368
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
369
                            .addComponent(useDefaults)
370
                            .addGroup(layout.createSequentialGroup()
371
                                .addComponent(langLabel)
372
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
373
                                .addComponent(langSelect, javax.swing.GroupLayout.PREFERRED_SIZE, 186, javax.swing.GroupLayout.PREFERRED_SIZE)
374
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
375
                                .addComponent(enableFolds)))
376
                        .addGap(0, 0, Short.MAX_VALUE)))
377
                .addContainerGap())
378
        );
379
        layout.setVerticalGroup(
380
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
381
            .addGroup(layout.createSequentialGroup()
382
                .addContainerGap()
383
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
384
                    .addComponent(langSelect, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
385
                    .addComponent(langLabel)
386
                    .addComponent(enableFolds))
387
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
388
                .addComponent(useDefaults)
389
                .addGap(12, 12, 12)
390
                .addComponent(content, javax.swing.GroupLayout.DEFAULT_SIZE, 25, Short.MAX_VALUE)
391
                .addGap(18, 18, 18)
392
                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
393
                .addContainerGap())
394
        );
395
    }// </editor-fold>//GEN-END:initComponents
396
397
    private void enableFoldsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_enableFoldsActionPerformed
398
        if (ignoreEnableTrigger) {
399
            return;
400
        }
401
        boolean enable = enableFolds.isSelected();
402
        currentPreferences.putBoolean(SimpleValueNames.CODE_FOLDING_ENABLE, enable);
403
        // visual feedback handled by listener.
404
    }//GEN-LAST:event_enableFoldsActionPerformed
405
406
    private void useDefaultsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useDefaultsActionPerformed
407
        if (ignoreEnableTrigger) {
408
            return;
409
        }
410
        currentPreferences.putBoolean(FoldUtilitiesImpl.PREF_OVERRIDE_DEFAULTS, 
411
                useDefaults.isSelected());
412
    }//GEN-LAST:event_useDefaultsActionPerformed
413
414
    // Variables declaration - do not modify//GEN-BEGIN:variables
415
    private javax.swing.JPanel content;
416
    private javax.swing.JCheckBox contentPreview;
417
    private javax.swing.JCheckBox enableFolds;
418
    private javax.swing.JCheckBox foldedSummary;
419
    private javax.swing.JPanel jPanel1;
420
    private javax.swing.JLabel langLabel;
421
    private javax.swing.JComboBox langSelect;
422
    private javax.swing.JCheckBox useDefaults;
423
    // End of variables declaration//GEN-END:variables
424
}
(-)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/ui/FoldView.java (+430 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 org.netbeans.modules.editor.fold.ui.FoldViewFactory;
48
import java.awt.Color;
49
import java.awt.Container;
50
import java.awt.Font;
51
import java.awt.Graphics2D;
52
import java.awt.Rectangle;
53
import java.awt.Shape;
54
import java.awt.font.FontRenderContext;
55
import java.awt.font.TextHitInfo;
56
import java.awt.font.TextLayout;
57
import java.awt.geom.Rectangle2D;
58
import java.util.logging.Logger;
59
import javax.swing.JComponent;
60
import javax.swing.JEditorPane;
61
import javax.swing.text.AttributeSet;
62
import javax.swing.text.BadLocationException;
63
import javax.swing.text.Document;
64
import javax.swing.text.EditorKit;
65
import javax.swing.text.Element;
66
import javax.swing.text.JTextComponent;
67
import javax.swing.text.Position;
68
import javax.swing.text.Position.Bias;
69
import javax.swing.text.StyleConstants;
70
import javax.swing.text.View;
71
import org.netbeans.api.editor.fold.Fold;
72
import org.netbeans.api.editor.fold.FoldTemplate;
73
import org.netbeans.api.editor.settings.FontColorNames;
74
import org.netbeans.api.editor.settings.FontColorSettings;
75
import org.netbeans.editor.BaseDocument;
76
import org.netbeans.editor.Utilities;
77
import org.netbeans.modules.editor.fold.FoldContentReaders;
78
import org.netbeans.modules.editor.lib2.view.EditorView;
79
import org.netbeans.modules.editor.lib2.view.ViewRenderContext;
80
import org.netbeans.modules.editor.lib2.view.ViewUtils;
81
import org.openide.util.NbBundle;
82
83
import static org.netbeans.modules.editor.fold.ui.Bundle.*;
84
85
86
/**
87
 * View with highlights. This is the most used view.
88
 *
89
 * @author Miloslav Metelka
90
 */
91
92
final class FoldView extends EditorView {
93
94
    // -J-Dorg.netbeans.modules.editor.lib2.view.HighlightsView.level=FINE
95
    private static final Logger LOG = Logger.getLogger(FoldView.class.getName());
96
97
    /**
98
     * Extra space added to each side of description text of a fold view.
99
     */
100
    private static final float EXTRA_MARGIN_WIDTH = 3;
101
102
    /** Raw end offset of this view. */
103
    private int rawEndOffset; // 24-super + 4 = 28 bytes
104
105
    /** Length of text occupied by this view. */
106
    private int length; // 28 + 4 = 32 bytes
107
108
    private final JTextComponent textComponent; // 32 + 4 = 36 bytes
109
110
    private final Fold fold; // 36 + 4 = 40 bytes
111
    
112
    private TextLayout collapsedTextLayout; // 40 + 4 = 44 bytes
113
    
114
    private AttributeSet    foldingColors;
115
    
116
    private int options;
117
118
    public FoldView(JTextComponent textComponent, Fold fold, FontColorSettings colorSettings, int options) {
119
        super(null);
120
        int offset = fold.getStartOffset();
121
        int len = fold.getEndOffset() - offset;
122
        assert (len > 0) : "length=" + len + " <= 0"; // NOI18N
123
        this.length = len;
124
        this.textComponent = textComponent;
125
        this.fold = fold;
126
        this.foldingColors = colorSettings.getFontColors(FontColorNames.CODE_FOLDING_COLORING);
127
        this.options = options;
128
    }
129
130
    @Override
131
    public float getPreferredSpan(int axis) {
132
        if (axis == View.X_AXIS) {
133
            String desc = fold.getDescription(); // For empty desc a single-space text layout is returned
134
            float advance = 0;
135
            if (desc.length() > 0) {
136
                TextLayout textLayout = getTextLayout();
137
                if (textLayout == null) {
138
                    return 0f;
139
                }
140
                advance = textLayout.getAdvance();
141
            }
142
            return advance + (2 * EXTRA_MARGIN_WIDTH);
143
        } else {
144
            EditorView.Parent parent = (EditorView.Parent) getParent();
145
            return (parent != null) ? parent.getViewRenderContext().getDefaultRowHeight() : 0f;
146
        }
147
    }
148
149
    @Override
150
    public int getRawEndOffset() {
151
        return rawEndOffset;
152
    }
153
154
    @Override
155
    public void setRawEndOffset(int rawOffset) {
156
        this.rawEndOffset = rawOffset;
157
    }
158
159
    @Override
160
    public int getLength() {
161
        return length;
162
    }
163
164
    @Override
165
    public int getStartOffset() {
166
        return getEndOffset() - getLength();
167
    }
168
169
    @Override
170
    public int getEndOffset() {
171
        EditorView.Parent parent = (EditorView.Parent) getParent();
172
        return (parent != null) ? parent.getViewEndOffset(rawEndOffset) : rawEndOffset;
173
    }
174
175
    @Override
176
    public Document getDocument() {
177
        View parent = getParent();
178
        return (parent != null) ? parent.getDocument() : null;
179
    }
180
181
    @Override
182
    public AttributeSet getAttributes() {
183
        return null;
184
    }
185
    
186
    @NbBundle.Messages({
187
        "# {0} - number of lines",
188
        "FMT_contentSummary={0} line(s)"
189
    })
190
    private String resolvePlaceholder(String text, int at) {
191
        if ((options & 3) == 0) {
192
            return text;
193
        }
194
        Document d = getDocument();
195
        if (!(d instanceof BaseDocument)) {
196
            return null;
197
        }
198
        BaseDocument bd = (BaseDocument)d;
199
        CharSequence contentSeq = ""; // NOI18N
200
        String summary = ""; // NOI18N
201
        
202
        int mask = options;
203
        try {
204
            if ((options & 1) > 0) {
205
                    contentSeq = FoldContentReaders.get().readContent(
206
                           org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(textComponent),
207
                           d, 
208
                           fold, 
209
                           fold.getType().getTemplate());
210
                    if (contentSeq == null) {
211
                        mask &= ~1;
212
                    }
213
            }
214
            if ((options & 2) > 0) {
215
                int start = fold.getStartOffset();
216
                int end = fold.getEndOffset();
217
                int startLine = Utilities.getLineOffset(bd, start);
218
                int endLine = Utilities.getLineOffset(bd, end) + 1;
219
                
220
                if (endLine <= startLine + 1) {
221
                    mask &= ~2;
222
                } else {
223
                    summary = FMT_contentSummary((endLine - startLine));
224
                }
225
            }
226
        } catch (BadLocationException ex) {
227
        }
228
        if (mask == 0) {
229
            return text;
230
        }
231
        String replacement = NbBundle.getMessage(FoldView.class, "FMT_ContentPlaceholder_" + (mask & 3), contentSeq, summary);
232
        StringBuilder sb = new StringBuilder(text.length() + replacement.length());
233
        sb.append(text.subSequence(0, at));
234
        sb.append(replacement);
235
        sb.append(text.subSequence(at + FoldTemplate.CONTENT_PLACEHOLDER.length(), text.length()));
236
        return sb.toString();
237
    }
238
239
    private TextLayout getTextLayout() {
240
        if (collapsedTextLayout == null) {
241
            EditorView.Parent parent = (EditorView.Parent) getParent();
242
            ViewRenderContext context = parent.getViewRenderContext();
243
            FontRenderContext frc = context.getFontRenderContext();
244
            assert (frc != null) : "Null FontRenderContext"; // NOI18N
245
            Font font = context.getRenderFont(textComponent.getFont());
246
            String text = fold.getDescription();
247
            if (text.length() == 0) {
248
                text = " "; // Use single space (mainly for height measurement etc.
249
            }
250
            int placeIndex = text.indexOf(FoldTemplate.CONTENT_PLACEHOLDER);
251
            if (placeIndex > -1) {
252
                text = resolvePlaceholder(text, placeIndex);
253
            }
254
            collapsedTextLayout = new TextLayout(text, font, frc);
255
        }
256
        return collapsedTextLayout;
257
    }
258
259
    @Override
260
    public Shape modelToViewChecked(int offset, Shape alloc, Position.Bias bias) {
261
//        TextLayout textLayout = getTextLayout();
262
//        if (textLayout == null) {
263
//            return alloc; // Leave given bounds
264
//        }
265
//        Rectangle2D.Double bounds = ViewUtils.shape2Bounds(alloc);
266
//        return bounds;
267
        return alloc;
268
    }
269
270
    @Override
271
    public int viewToModelChecked(double x, double y, Shape alloc, Position.Bias[] biasReturn) {
272
        int startOffset = getStartOffset();
273
        return startOffset;
274
    }
275
276
    static TextHitInfo x2RelOffset(TextLayout textLayout, float x) {
277
        TextHitInfo hit;
278
        x -= EXTRA_MARGIN_WIDTH;
279
        if (x >= textLayout.getAdvance()) {
280
            hit = TextHitInfo.trailing(textLayout.getCharacterCount());
281
        } else {
282
            hit = textLayout.hitTestChar(x, 0); // What about backward bias -> with higher offsets it may go back visually
283
        }
284
        return hit;
285
286
    }
287
288
    @Override
289
    public int getNextVisualPositionFromChecked(int offset, Bias bias, Shape alloc, int direction, Bias[] biasRet) {
290
        int startOffset = getStartOffset();
291
        int retOffset = -1;
292
        switch (direction) {
293
            case WEST:
294
                if (offset == -1) {
295
                    retOffset = startOffset;
296
                } else {
297
                    retOffset = -1;
298
                }
299
                break;
300
301
            case EAST:
302
                if (offset == -1) {
303
                    retOffset = startOffset;
304
                } else {
305
                    retOffset = -1;
306
                }
307
                break;
308
309
            case NORTH:
310
            case SOUTH:
311
                break;
312
            default:
313
                throw new IllegalArgumentException("Bad direction: " + direction);
314
        }
315
        return retOffset;
316
    }
317
318
    @Override
319
    public JComponent getToolTip(double x, double y, Shape allocation) {
320
        Container container = getContainer();
321
        if (container instanceof JEditorPane) {
322
            JEditorPane editorPane = (JEditorPane) getContainer();
323
            JEditorPane tooltipPane = new JEditorPane();
324
            EditorKit kit = editorPane.getEditorKit();
325
            Document doc = getDocument();
326
            if (kit != null && doc != null) {
327
                Element lineRootElement = doc.getDefaultRootElement();
328
                tooltipPane.putClientProperty(FoldViewFactory.VIEW_FOLDS_EXPANDED_PROPERTY, true);
329
                try {
330
                    // Start-offset of the fold => line start => position
331
                    int lineIndex = lineRootElement.getElementIndex(fold.getStartOffset());
332
                    Position pos = doc.createPosition(
333
                            lineRootElement.getElement(lineIndex).getStartOffset());
334
                    // DocumentView.START_POSITION_PROPERTY
335
                    tooltipPane.putClientProperty("document-view-start-position", pos);
336
                    // End-offset of the fold => line end => position
337
                    lineIndex = lineRootElement.getElementIndex(fold.getEndOffset());
338
                    pos = doc.createPosition(lineRootElement.getElement(lineIndex).getEndOffset());
339
                    // DocumentView.END_POSITION_PROPERTY
340
                    tooltipPane.putClientProperty("document-view-end-position", pos);
341
                    tooltipPane.putClientProperty("document-view-accurate-span", true);
342
                    // Set the same kit and document
343
                    tooltipPane.setEditorKit(kit);
344
                    tooltipPane.setDocument(doc);
345
                    tooltipPane.setEditable(false);
346
                    return new FoldToolTip(editorPane, tooltipPane, getForegroundColor());
347
                } catch (BadLocationException e) {
348
                    // => return null
349
                }
350
            }
351
        }
352
        return null;
353
    }
354
    
355
    private Color getForegroundColor() {
356
        if (foldingColors == null) {
357
            return textComponent.getForeground();
358
        }
359
        Object bgColorObj = foldingColors.getAttribute(StyleConstants.Foreground);
360
        if (bgColorObj instanceof Color) {
361
            return (Color)bgColorObj;
362
        } else {
363
            return textComponent.getForeground();
364
        }
365
    }
366
367
    private Color getBackgroundColor() {
368
        if (foldingColors == null) {
369
            return textComponent.getBackground();
370
        }
371
        Object bgColorObj = foldingColors.getAttribute(StyleConstants.Background);
372
        if (bgColorObj instanceof Color) {
373
            return (Color)bgColorObj;
374
        } else {
375
            return textComponent.getBackground();
376
        }
377
    }
378
379
    @Override
380
    public void paint(Graphics2D g, Shape alloc, Rectangle clipBounds) {
381
        Rectangle2D.Double allocBounds = ViewUtils.shape2Bounds(alloc);
382
        if (allocBounds.intersects(clipBounds)) {
383
            Font origFont = g.getFont();
384
            Color origColor = g.getColor();
385
            Color origBkColor = g.getBackground();
386
            Shape origClip = g.getClip();
387
            try {
388
                // Leave component font
389
                g.setColor(getForegroundColor());
390
                g.setBackground(getBackgroundColor());
391
392
                int xInt = (int) allocBounds.getX();
393
                int yInt = (int) allocBounds.getY();
394
                int endXInt = (int) (allocBounds.getX() + allocBounds.getWidth() - 1);
395
                int endYInt = (int) (allocBounds.getY() + allocBounds.getHeight() - 1);
396
                g.drawRect(xInt, yInt, endXInt - xInt, endYInt - yInt);
397
                g.clearRect(xInt + 1, yInt + 1, endXInt - xInt - 1, endYInt - yInt - 1);
398
                g.clip(alloc);
399
                TextLayout textLayout = getTextLayout();
400
                if (textLayout != null) {
401
                    EditorView.Parent parent = (EditorView.Parent) getParent();
402
                    float ascent = parent.getViewRenderContext().getDefaultAscent();
403
                    String desc = fold.getDescription(); // For empty desc a single-space text layout is returned
404
                    float x = (float) (allocBounds.getX() + EXTRA_MARGIN_WIDTH);
405
                    float y = (float) allocBounds.getY();
406
                    if (desc.length() > 0) {
407
                        
408
                        textLayout.draw(g, x, y + ascent);
409
                    }
410
                }
411
            } finally {
412
                g.setClip(origClip);
413
                g.setBackground(origBkColor);
414
                g.setColor(origColor);
415
                g.setFont(origFont);
416
            }
417
        }
418
    }
419
420
    @Override
421
    protected String getDumpName() {
422
        return "FV";
423
    }
424
425
    @Override
426
    public String toString() {
427
        return appendViewInfo(new StringBuilder(200), 0, "", -1).toString();
428
    }
429
430
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/FoldViewFactory.java (+272 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.util.Iterator;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import java.util.prefs.PreferenceChangeEvent;
51
import java.util.prefs.PreferenceChangeListener;
52
import java.util.prefs.Preferences;
53
import javax.swing.text.View;
54
import org.netbeans.api.editor.fold.Fold;
55
import org.netbeans.api.editor.fold.FoldHierarchy;
56
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
57
import org.netbeans.api.editor.fold.FoldHierarchyListener;
58
import org.netbeans.api.editor.fold.FoldUtilities;
59
import org.netbeans.api.editor.mimelookup.MimeLookup;
60
import org.netbeans.api.editor.settings.FontColorSettings;
61
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
62
import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
63
import org.netbeans.modules.editor.lib2.view.EditorView;
64
import org.netbeans.modules.editor.lib2.view.EditorViewFactory;
65
import org.netbeans.modules.editor.lib2.view.EditorViewFactoryChange;
66
import org.netbeans.modules.editor.lib2.view.ViewUtils;
67
import org.openide.util.Lookup;
68
import org.openide.util.LookupEvent;
69
import org.openide.util.LookupListener;
70
import org.openide.util.WeakListeners;
71
72
/**
73
 * View factory creating views for collapsed folds.
74
 *
75
 * @author Miloslav Metelka
76
 */
77
78
@SuppressWarnings("ClassWithMultipleLoggers")
79
public final class FoldViewFactory extends EditorViewFactory implements FoldHierarchyListener, LookupListener, PreferenceChangeListener {
80
81
    /**
82
     * Component's client property which can be set to view folds expanded for tooltip fold preview.
83
     */
84
    static final String VIEW_FOLDS_EXPANDED_PROPERTY = "view-folds-expanded"; // NOI18N
85
86
    // -J-Dorg.netbeans.editor.view.change.level=FINE
87
    static final Logger CHANGE_LOG = Logger.getLogger("org.netbeans.editor.view.change");
88
89
    // -J-Dorg.netbeans.modules.editor.fold.FoldViewFactory.level=FINE
90
    private static final Logger LOG = Logger.getLogger(FoldViewFactory.class.getName());
91
92
    public static void register() {
93
        EditorViewFactory.registerFactory(new FoldFactory());
94
    }
95
96
    private FoldHierarchy foldHierarchy;
97
98
    private boolean foldHierarchyLocked;
99
100
    private Fold fold;
101
102
    private int foldStartOffset;
103
104
    private Iterator<Fold> collapsedFoldIterator;
105
106
    private boolean viewFoldsExpanded;
107
    
108
    /**
109
     * Composite Color settings from MIME lookup
110
     */
111
    private FontColorSettings   colorSettings;
112
113
    /**
114
     * Lookup results for color settings, being listened for changes.
115
     */
116
    private Lookup.Result       colorSource;
117
    
118
    private Preferences         prefs;
119
    
120
    private int viewFlags = 0;
121
122
    public FoldViewFactory(View documentView) {
123
        super(documentView);
124
        foldHierarchy = FoldHierarchy.get(textComponent());
125
        foldHierarchy.addFoldHierarchyListener(this);
126
        viewFoldsExpanded = Boolean.TRUE.equals(textComponent().getClientProperty(VIEW_FOLDS_EXPANDED_PROPERTY));
127
        
128
        String mime = DocumentUtilities.getMimeType(document());
129
        
130
        Lookup lkp = MimeLookup.getLookup(mime);
131
        colorSource = lkp.lookupResult(FontColorSettings.class);
132
        colorSource.addLookupListener(WeakListeners.create(LookupListener.class, this, colorSource));
133
        colorSettings = (FontColorSettings)colorSource.allInstances().iterator().next();
134
        prefs = lkp.lookup(Preferences.class);
135
        prefs.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, this, prefs));
136
        
137
        initViewFlags();
138
    }
139
    
140
    private void initViewFlags() {
141
        viewFlags =
142
                (prefs.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_PREVIEW, true) ? 1 : 0) |
143
                (prefs.getBoolean(FoldUtilitiesImpl.PREF_CONTENT_SUMMARY, true) ? 2 : 0);
144
    }
145
146
    @Override
147
    public void resultChanged(LookupEvent ev) {
148
        refreshColors();
149
    }
150
151
    @Override
152
    public void preferenceChange(PreferenceChangeEvent evt) {
153
        String k = evt.getKey();
154
        if (FoldUtilitiesImpl.PREF_CONTENT_PREVIEW.equals(k) ||
155
            FoldUtilitiesImpl.PREF_CONTENT_SUMMARY.equals(k)) {
156
            initViewFlags();
157
            document().render(new Runnable() {
158
                @Override
159
                public void run() {
160
                    int end = document().getLength();
161
                    fireEvent(EditorViewFactoryChange.createList(0, end, EditorViewFactoryChange.Type.CHARACTER_CHANGE));
162
                }
163
            });
164
        }
165
    }
166
167
    private void refreshColors() {
168
        colorSettings = (FontColorSettings)colorSource.allInstances().iterator().next();
169
        document().render(new Runnable() {
170
            @Override
171
            public void run() {
172
                int end = document().getLength();
173
                fireEvent(EditorViewFactoryChange.createList(0, end, EditorViewFactoryChange.Type.CHARACTER_CHANGE));
174
            }
175
        });
176
    }
177
178
    @Override
179
    public void restart(int startOffset, int endOffset, boolean createViews) {
180
        foldHierarchy.lock(); // this.finish() always called in try-finally
181
        foldHierarchyLocked = true;
182
        @SuppressWarnings("unchecked")
183
        Iterator<Fold> it = FoldUtilities.collapsedFoldIterator(foldHierarchy, startOffset, Integer.MAX_VALUE);
184
        collapsedFoldIterator = it;
185
        foldStartOffset = -1; // Make a next call to updateFold() to fetch a fold
186
    }
187
188
    private void updateFold(int offset) {
189
        if (foldStartOffset < offset) {
190
            while (collapsedFoldIterator.hasNext()) {
191
                fold = collapsedFoldIterator.next();
192
                foldStartOffset = fold.getStartOffset();
193
                if (foldStartOffset >= offset) {
194
                    return;
195
                }
196
            }
197
            fold = null;
198
            foldStartOffset = Integer.MAX_VALUE;
199
        }
200
    }
201
202
    @Override
203
    public int nextViewStartOffset(int offset) {
204
        if (!viewFoldsExpanded) {
205
            updateFold(offset);
206
            return foldStartOffset;
207
        }
208
        return Integer.MAX_VALUE;
209
    }
210
211
    @Override
212
    public EditorView createView(int startOffset, int limitOffset, boolean forcedLimit,
213
    EditorView origView, int nextOrigViewOffset) {
214
        assert (startOffset == foldStartOffset) : "startOffset=" + startOffset + " != foldStartOffset=" + foldStartOffset; // NOI18N
215
        if (fold.getEndOffset() <= limitOffset || !forcedLimit) {
216
            return new FoldView(textComponent(), fold, colorSettings, viewFlags);
217
        } else {
218
            return null;
219
        }
220
    }
221
    
222
    @Override
223
    public int viewEndOffset(int startOffset, int limitOffset, boolean forcedLimit) {
224
        int foldEndOffset = fold.getEndOffset();
225
        if (foldEndOffset <= limitOffset) {
226
            return foldEndOffset;
227
        } else {
228
            return -1;
229
        }
230
    }
231
232
    @Override
233
    public void continueCreation(int startOffset, int endOffset) {
234
    }
235
236
    @Override
237
    public void finishCreation() {
238
        fold = null;
239
        collapsedFoldIterator = null;
240
        if (foldHierarchyLocked) {
241
            foldHierarchy.unlock();
242
        }
243
    }
244
245
    @Override
246
    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
247
        // For fold state changes use a higher priority
248
        int startOffset = evt.getAffectedStartOffset();
249
        int endOffset = evt.getAffectedEndOffset();
250
        if (CHANGE_LOG.isLoggable(Level.FINE)) {
251
            ViewUtils.log(CHANGE_LOG, "CHANGE in FoldViewFactory: <" + // NOI18N
252
                    startOffset + "," + endOffset + ">\n"); // NOI18N
253
        }
254
        fireEvent(EditorViewFactoryChange.createList(startOffset, endOffset,
255
                EditorViewFactoryChange.Type.PARAGRAPH_CHANGE));
256
    }
257
258
    public static final class FoldFactory implements EditorViewFactory.Factory {
259
260
        @Override
261
        public EditorViewFactory createEditorViewFactory(View documentView) {
262
            return new FoldViewFactory(documentView);
263
        }
264
265
        @Override
266
        public int weight() {
267
            return 100;
268
        }
269
270
    }
271
272
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/InheritedPreferences.java (+252 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.ui;
43
44
import java.util.Arrays;
45
import java.util.Collection;
46
import java.util.HashSet;
47
import java.util.prefs.AbstractPreferences;
48
import java.util.prefs.BackingStoreException;
49
import java.util.prefs.PreferenceChangeEvent;
50
import java.util.prefs.PreferenceChangeListener;
51
import java.util.prefs.Preferences;
52
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
53
54
/**
55
 * Support for inheriting Preferences, while still working with stored ones.
56
 * 
57
 * This class solves the 'diamond inheritance', which is present during editing:
58
 * a MIME-type preferences derive from BOTH its persistent values (preferred) and
59
 * from the parent, whose actual values are potentially transient, and also persistent.
60
 * <p/>
61
 * Let us assume the following assignment:
62
 * <ul>
63
 * <li>TC (this current) = currently added/changed/removed values
64
 * <li>TP (this persistent) = persistent values, the getLocal() part of the Mime PreferencesImpl object
65
 * <li>PC (parent current) = currently added/changed/removed values of the parent
66
 * <li>PP (parent persistent) = persistent values, the getLocal() part of the parent MimePreferences
67
 * </ul>
68
 * The desired priority to find a value is: TC, TP, PC, PP. Because of {@link MemoryPreferences}, the
69
 * PC, PP (and potentially fallback to a grandparent) we already have, if we use the parent's {@link MemoryPreferences}
70
 * preferences as 'inherited'. The "TC" is handled by ProxyPreferences for this Mime node. In InheritedPreferences,
71
 * we must only inject the TP in between TC and the parent's preferences (PC, PP, ...)
72
 * <p/>
73
 * The object is intended to act as a ProxyPreferences delegate, all writes go directly to the stored
74
 * Mime preferences.
75
 * 
76
 * @author sdedic
77
 */
78
public final class InheritedPreferences extends AbstractPreferences implements PreferenceChangeListener, OverridePreferences  {
79
    /**
80
     * Preferences inherited, ie from a parent Mime type
81
     */
82
    private Preferences inherited;
83
    
84
    /**
85
     * Stored preferences, 
86
     */
87
    private Preferences stored;
88
    
89
    public InheritedPreferences(Preferences inherited, Preferences stored) {
90
        super(null, ""); // NOI18N
91
        this.inherited = inherited;
92
        if (!(stored instanceof OverridePreferences)) {
93
            throw new IllegalArgumentException();
94
        }
95
        this.stored = stored;
96
        
97
        inherited.addPreferenceChangeListener(this);
98
    }
99
    
100
    @Override
101
    protected void putSpi(String key, String value) {
102
        // do nothing, the AbstractPref then just fires an event
103
    }
104
105
    @Override
106
    public void put(String key, String value) {
107
        if (Boolean.TRUE != ignorePut.get()) {
108
            stored.put(key, value);
109
        }
110
        super.put(key, value);
111
    }
112
113
    @Override
114
    public void putInt(String key, int value) {
115
        if (Boolean.TRUE != ignorePut.get()) {
116
            stored.putInt(key, value);
117
        }
118
        super.putInt(key, value); 
119
    }
120
121
    @Override
122
    public void putLong(String key, long value) {
123
        if (Boolean.TRUE != ignorePut.get()) {
124
            stored.putLong(key, value);
125
        }
126
        super.putLong(key, value);
127
    }
128
129
    @Override
130
    public void putBoolean(String key, boolean value) {
131
        if (Boolean.TRUE != ignorePut.get()) {
132
            stored.putBoolean(key, value);
133
        }
134
        super.putBoolean(key, value);
135
    }
136
137
    @Override
138
    public void putFloat(String key, float value) {
139
        if (Boolean.TRUE != ignorePut.get()) {
140
            stored.putFloat(key, value);
141
        }
142
        super.putFloat(key, value); 
143
    }
144
145
    @Override
146
    public void putDouble(String key, double value) {
147
        if (Boolean.TRUE != ignorePut.get()) {
148
            stored.putDouble(key, value);
149
        }
150
        super.putDouble(key, value); 
151
    }
152
153
    @Override
154
    public void putByteArray(String key, byte[] value) {
155
        if (Boolean.TRUE != ignorePut.get()) {
156
            stored.putByteArray(key, value);
157
        }
158
        super.putByteArray(key, value);
159
    }
160
    
161
    private ThreadLocal<Boolean> ignorePut = new ThreadLocal<Boolean>();
162
163
    @Override
164
    public void preferenceChange(PreferenceChangeEvent evt) {
165
        if (!isOverriden(evt.getKey())) {
166
            // jusr refires an event
167
            ignorePut.set(true);
168
            try {
169
                put(evt.getKey(), evt.getNewValue());
170
            } finally {
171
                ignorePut.set(false);
172
            }
173
        }
174
    }
175
    
176
    /**
177
     * The value is defined locally, if the stored prefs define the value
178
     * locally. The parent definitions do not count. It is expected, that the
179
     * ProxyPreferences will report its local overrides as local in front of this
180
     * InheritedPreferences.
181
     * 
182
     * @param k
183
     * @return 
184
     */
185
    public @Override boolean isOverriden(String k) {
186
        if (stored instanceof OverridePreferences) {
187
            return ((OverridePreferences)stored).isOverriden(k);
188
        } else {
189
            return true;
190
        }
191
    }
192
    
193
    @Override
194
    protected String getSpi(String key) {
195
        // check the stored values
196
        OverridePreferences localStored = (OverridePreferences)stored;
197
        if (localStored.isOverriden(key)) {
198
            return stored.get(key, null);
199
        }
200
        // fall back to the inherited prefs, potentially its stored values etc.
201
        return inherited.get(key, null);
202
    }
203
204
    @Override
205
    protected void removeSpi(String key) {
206
        stored.remove(key);
207
    }
208
209
    @Override
210
    protected void removeNodeSpi() throws BackingStoreException {
211
        stored.removeNode();
212
    }
213
214
    @Override
215
    protected String[] keysSpi() throws BackingStoreException {
216
        Collection<String> names = new HashSet<String>();
217
        names.addAll(Arrays.asList(stored.keys()));
218
        names.addAll(Arrays.asList(inherited.keys()));
219
        return names.toArray(new String[names.size()]);
220
    }
221
222
    @Override
223
    protected String[] childrenNamesSpi() throws BackingStoreException {
224
        if (stored != null) {
225
            return stored.childrenNames();
226
        } else {
227
            return new String[0];
228
        }
229
    }
230
231
    @Override
232
    protected AbstractPreferences childSpi(String name) {
233
        Preferences storedNode = stored != null ? stored.node(name) : null;
234
        if (storedNode != null) {
235
            return new InheritedPreferences(null, storedNode);
236
        } else {
237
            return null;
238
        }
239
    }
240
241
    @Override
242
    protected void syncSpi() throws BackingStoreException {
243
        stored.sync();
244
    }
245
246
    @Override
247
    protected void flushSpi() throws BackingStoreException {
248
        stored.flush();
249
    }
250
    
251
    
252
}
(-)a/editor.fold/src/org/netbeans/modules/editor/fold/ui/VerticalFlowLayout.java (+175 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.ui;
43
44
import java.awt.Component;
45
import java.awt.Container;
46
import java.awt.Dimension;
47
import java.awt.Insets;
48
import java.awt.LayoutManager2;
49
import java.util.LinkedHashSet;
50
import java.util.Set;
51
52
/**
53
 * Simple layout, which fills the space vertically, then overflows to the next column.
54
 *
55
 * @author sdedic
56
 */
57
final class VerticalFlowLayout implements LayoutManager2 {
58
59
    final private Set<Component> components = new LinkedHashSet<Component>();
60
    private int hgap = 0;
61
    private int vgap = 0;
62
63
    public void setHGap(int hgap) {
64
        this.hgap = hgap;
65
    }
66
67
    public void setVGap(int vgap) {
68
        this.vgap = vgap;
69
    }
70
71
    @Override
72
    public void addLayoutComponent(Component comp, Object constraints) {
73
        this.components.add(comp);
74
    }
75
76
    @Override
77
    public float getLayoutAlignmentX(Container target) {
78
        return 0;
79
    }
80
81
    @Override
82
    public float getLayoutAlignmentY(Container target) {
83
        return 0;
84
    }
85
86
    @Override
87
    public void invalidateLayout(Container target) {
88
    }
89
90
    @Override
91
    public void addLayoutComponent(String name, Component comp) {
92
        this.components.add(comp);
93
    }
94
95
    private Dimension computeDimension(Container parent, int type) {
96
        Insets insets = parent.getInsets();
97
        int x = insets.left;
98
        int y = insets.top;
99
        int columnWidth = 0;
100
        // int limitHeight = parent.getHeight() - insets.bottom;
101
        int maxY = 0;
102
103
        for (Component c : this.components) {
104
            if (c.isVisible()) {
105
                Dimension d;
106
107
                switch (type) {
108
                    case 0:
109
                        d = c.getPreferredSize();
110
                        break;
111
                    case 1:
112
                        d = c.getMinimumSize();
113
                        break;
114
                    default:
115
                        d = c.getMaximumSize();
116
                        break;
117
                }
118
                columnWidth = Math.max(columnWidth, d.width);
119
                /*
120
                if (limitHeight != 0 && y + d.height >= limitHeight) {
121
                    x += columnWidth + this.hgap;
122
                    y = insets.top;
123
                    columnWidth = d.width;
124
                }
125
                */
126
                y += d.height;
127
                maxY = Math.max(y, maxY);
128
                y += this.vgap;
129
            }
130
        }
131
        x += columnWidth;
132
        return new Dimension(x, maxY);
133
    }
134
135
    @Override
136
    public void layoutContainer(Container parent) {
137
        Insets insets = parent.getInsets();
138
        int x = insets.left;
139
        int y = insets.top;
140
        int columnWidth = 0;
141
        int limitHeight = parent.getHeight() - insets.bottom;
142
        for (Component c : this.components) {
143
            if (c.isVisible()) {
144
                Dimension d = c.getPreferredSize();
145
                columnWidth = Math.max(columnWidth, d.width);
146
                if (y + d.height >= limitHeight) {
147
                    x += columnWidth + this.hgap;
148
                    y = insets.top;
149
                }
150
                c.setBounds(x, y, d.width, d.height);
151
                y += d.height + this.vgap;
152
            }
153
        }
154
    }
155
156
    @Override
157
    public Dimension minimumLayoutSize(Container parent) {
158
        return computeDimension(parent, 1);
159
    }
160
161
    @Override
162
    public Dimension preferredLayoutSize(Container parent) {
163
        return computeDimension(parent, 1);
164
    }
165
166
    @Override
167
    public Dimension maximumLayoutSize(Container target) {
168
        return computeDimension(target, 2);
169
    }
170
171
    @Override
172
    public void removeLayoutComponent(Component comp) {
173
        this.components.remove(comp);
174
    }
175
}
(-)a/editor.fold/src/org/netbeans/spi/editor/fold/ContentReader.java (+97 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.spi.editor.fold;
43
44
import javax.swing.text.BadLocationException;
45
import javax.swing.text.Document;
46
import org.netbeans.api.editor.fold.Fold;
47
import org.netbeans.api.editor.fold.FoldTemplate;
48
import org.netbeans.api.editor.fold.FoldType;
49
import org.netbeans.spi.editor.mimelookup.MimeLocation;
50
51
/**
52
 * Callback interface, which is called to extract description for a fold.
53
 * The Reader will be called to produce a preview text to be displayed in the
54
 * folded view. The reader will be called under document lock, although the fold
55
 * hierarchy will not be locked. You may query the fold properties, but may not
56
 * traverse the fold hierarchy.
57
 * <p/>
58
 * An instance of ContentReader is used repeatedly, i.e. if the same FoldTemplate
59
 * is assigned to multiple folds. It is advised that the implementation of ContentReader
60
 * is stateless.
61
 * 
62
 * @author sdedic
63
 */
64
public interface ContentReader {
65
    /**
66
     * Acquires text for fold content.
67
     * The method is executed under <i>read lock</i> on the Document. However, the Fold Hierarchy
68
     * is not locked. Accessing fold offsets should be safe, but relationships with other Folds
69
     * (parents, children, root) are not guarded.
70
     * <p/>
71
     * If the ContentReader cannot extract the contents (i.e. it does not want to handle the fold), 
72
     * {@code null} may be returned. If more ContentReaders are registered, some other instance might
73
     * handle the fold properly. If not, the placeholder will be retained and presented in the fold 
74
     * text.
75
     * 
76
     * @param d the document to read from
77
     * @param f fold, whose contents should be read
78
     * @param ft the original fold template, if available
79
     * @return content to be displayed in the folded view, or {@code null} if unwilling to retrieve the content.
80
     */
81
    public CharSequence read(Document d, Fold f, FoldTemplate ft) throws BadLocationException;
82
83
    /**
84
     * Factory, which produces ContentReader instance(s) appropriate for the fold type.
85
     * The returned instance may be used to read contents for all folds of the given type, in 
86
     * different documents (of the same mime type).
87
     */
88
    @MimeLocation(subfolderName = "FoldManager")
89
    public interface Factory {
90
        /**
91
         * @param ft the fold type
92
         * @return ContentReader instance or {@code null}, if content should not be presented in the fold preview.
93
         */
94
        public ContentReader    createReader(FoldType ft);
95
    }
96
}
97
(-)a/editor.fold/src/org/netbeans/spi/editor/fold/FoldInfo.java (+261 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.spi.editor.fold;
43
44
import org.netbeans.api.editor.fold.FoldTemplate;
45
import org.netbeans.api.editor.fold.FoldType;
46
import org.openide.util.Parameters;
47
48
/**
49
 * The FoldInfo encapsulates the information passed to the
50
 * {@link FoldOperation#addToHierarchy(org.netbeans.api.editor.fold.FoldType, int, int, org.netbeans.api.editor.fold.FoldTemplate, boolean, java.lang.Object, org.netbeans.spi.editor.fold.FoldHierarchyTransaction).
51
 * Set of FoldInfos can be then applied to the FoldHieararchy, creating new, and removing obsolete Folds, so that 
52
 * Folds which prevail remain in the hierarchy. The mandatory information is start and end of the fold, and the {@link FoldType}.
53
 * If necessary, a {@link FoldTemplate} attached to the FoldType can be overriden for the specific Fold instance. Note though, that
54
 * if the FoldTemplate instance changes with the next fold update, the Fold may fire change events.
55
 * <p/>
56
 * Ultimately, it is possible to hand-override the Fold's description from the FoldTemplate provided value.
57
 * <p/>
58
 * If the FoldInfo is used to update an existing Fold, the or the FoldTemplate's properties 
59
 * collapsed state are  updated to the existing Fold instance. Appropriate fold change event is fired. It is not possible
60
 * to change the type of the fold.
61
 * <p/>
62
 * Initial folding state can be specified, for the case the fold will be created (it does not exist). If unspecified,
63
 * the infrastructure can assign an appropriate state based on e.g. user preferences. Collapsed state is never changed
64
 * for existing folds, even though FoldInfo specifies a value.
65
 * 
66
 * Use {@link FoldUtilities#update} to perform the process.
67
 * 
68
 * @author sdedic
69
 */
70
public final class FoldInfo {
71
    /**
72
     * Start of the folded region
73
     */
74
    private int start;
75
    
76
    /**
77
     * End of the folded region
78
     */
79
    private int end;
80
    
81
    /**
82
     * Tooltip contents and guarded areas of the fold. Defaults to the FoldTemplate present in the FoldType.
83
     */
84
    private FoldTemplate template;
85
    
86
    /**
87
     * The fold type of the fold
88
     */
89
    private FoldType type;
90
    
91
    /**
92
     * Determines whether the fold should be initially collapsed. Value of null means
93
     * the collapsed state should be computed by the infrastructure. Non-null value will
94
     * force the fold to expand or collapse.
95
     */
96
    private Boolean collapsed;
97
    
98
    /**
99
     * Extra information attached to a Fold
100
     */
101
    private Object  extraInfo;
102
    
103
    /**
104
     * Custom description, overriding the default one in the template
105
     */
106
    private String  description;
107
    
108
    /**
109
     * Creates a FoldInfo for the specified range.
110
     * 
111
     * @param start start offset
112
     * @param end end offset
113
     * @param type type of the fold
114
     * @return FoldInfo instance
115
     */
116
    public static FoldInfo range(int start, int end, FoldType type) {
117
        return new FoldInfo(start, end, type);
118
    }
119
    
120
    private FoldInfo(int start, int end, FoldType ft) {
121
        Parameters.notNull("ft", ft);
122
        if (start < 0) {
123
            throw new IllegalArgumentException("Invalid start offet: " + start);
124
        }
125
        if (end < start) {
126
            throw new IllegalArgumentException("Invalid end offset: " + end + ", start is: " + start);
127
        }
128
        this.type = ft;
129
        this.start = start;
130
        this.end = end;
131
        this.template = ft.getTemplate();
132
    }
133
    
134
    /**
135
     * Attaches FoldTemplate to the FoldInfo.
136
     * The instance will be used to configure or update the Fold instance in preference to {@link FoldType#getTemplate}.
137
     * 
138
     * @param t fold template
139
     * @return this instance
140
     */
141
    public FoldInfo withTemplate(FoldTemplate t) {
142
        Parameters.notNull("t", t);
143
        this.template = t;
144
        return this;
145
    }
146
    
147
    /**
148
     * Use to provide a custom description for the fold.
149
     * The description overrides all other ones taken from FoldTemplates. The description can use
150
     * content placeholder (see {@link FoldTemplate} for explanation. When {@code null} is set, the
151
     * description Fold reverts back to the one provided by FoldTemplates (the override is cleared).
152
     * 
153
     * @param desc description text.
154
     * @return this instance
155
     */
156
    public FoldInfo withDescription(String desc) {
157
        this.description = desc;
158
        return this;
159
    }
160
    
161
    /**
162
     * Attaches custom extra info to the fold.
163
     * The extra info will be available from {@link org.netbeans.api.editor.fold.Fold#getExtraInfo.
164
     * 
165
     * @param extraInfo custom data
166
     * @return this instance
167
     */
168
    public FoldInfo attach(Object extraInfo) {
169
        this.extraInfo = extraInfo;
170
        return this;
171
    }
172
    
173
    /**
174
     * Returns description override.
175
     * When {@code null}, information from FoldTemplates should be used.
176
     * 
177
     * @return explicit description, or {@code null}.
178
     */
179
    public String getDescriptionOverride() {
180
        return description;
181
    }
182
    
183
    /**
184
     * Returns the extra information attached to a fold.
185
     * 
186
     * @return data, or {@code null} if no data is present
187
     */
188
    public Object getExtraInfo() {
189
        return extraInfo;
190
    }
191
    
192
    /**
193
     * Records the desired collapsed state.
194
     * 
195
     * @param state the desired collapsed state
196
     * @return this instance.
197
     */
198
    public FoldInfo collapsed(boolean state) {
199
        this.collapsed = state;
200
        return this;
201
    }
202
203
    /**
204
     * Provides start offset of the folded content
205
     * @return offset into the document
206
     */
207
    public int getStart() {
208
        return start;
209
    }
210
211
    /**
212
     * Provides end offset of the folded content
213
     * @return offset into the document
214
     */
215
    public int getEnd() {
216
        return end;
217
    }
218
219
    /**
220
     * Provides FoldTemplate to be used with the Fold.
221
     * The template will be used in preference to {@link org.netbeans.api.editor.fold.FoldType#getTemplate}. 
222
     * {@code Null} return value means that the FoldTemplate from the FoldType is in effect.
223
     
224
     * @return FoldTemplate instance, or {@code null}
225
     */
226
    public FoldTemplate getTemplate() {
227
        return template;
228
    }
229
230
    /**
231
     * Provides FoldType for the fold.
232
     * The FoldType will be assigned to the new fold. If type of a fold (occupying the same range) changes during
233
     * {@link FoldOperation#update}, the fold will be destroyed and re-created. It is not possible to change FoldType
234
     * of a Fold.
235
     * 
236
     * @return FoldType instance, never {@code null}. 
237
     */
238
    public FoldType getType() {
239
        return type;
240
    }
241
242
    /**
243
     * Provides the desired collapsed state or {@code null}, if no specific
244
     * state is required.
245
     * When {@code null} is reported, the infrastructure can assign an an appropriate initial state to the fold,
246
     * e.g. based on user preferences. States of existing fold is never changed during update.
247
     * 
248
     * @return desired collapsed state or {@code null}
249
     */
250
    public Boolean getCollapsed() {
251
        return collapsed;
252
    }
253
    
254
    public String toString() {
255
        StringBuilder sb = new StringBuilder();
256
        sb.append("FoldInfo[").append(start).append(" - ").append(end).
257
                append(", ").append(type).append(", desc = ").append(description == null ? template.getDescription() : description).
258
                append(" collapsed = ").append(collapsed).append("]");
259
        return sb.toString();
260
    }
261
}
(-)a/editor.fold/src/org/netbeans/spi/editor/fold/FoldOperation.java (+134 lines)
Lines 44-57 Link Here
44
44
45
package org.netbeans.spi.editor.fold;
45
package org.netbeans.spi.editor.fold;
46
46
47
import java.util.Collection;
48
import java.util.Iterator;
49
import java.util.Map;
47
import javax.swing.text.BadLocationException;
50
import javax.swing.text.BadLocationException;
48
import org.netbeans.api.editor.fold.Fold;
51
import org.netbeans.api.editor.fold.Fold;
49
import org.netbeans.api.editor.fold.FoldHierarchy;
52
import org.netbeans.api.editor.fold.FoldHierarchy;
53
import org.netbeans.api.editor.fold.FoldTemplate;
50
import org.netbeans.api.editor.fold.FoldType;
54
import org.netbeans.api.editor.fold.FoldType;
51
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
55
import org.netbeans.modules.editor.fold.ApiPackageAccessor;
52
import org.netbeans.modules.editor.fold.FoldHierarchyTransactionImpl;
56
import org.netbeans.modules.editor.fold.FoldHierarchyTransactionImpl;
53
import org.netbeans.modules.editor.fold.FoldOperationImpl;
57
import org.netbeans.modules.editor.fold.FoldOperationImpl;
54
import org.netbeans.modules.editor.fold.SpiPackageAccessor;
58
import org.netbeans.modules.editor.fold.SpiPackageAccessor;
59
import org.openide.util.Parameters;
55
60
56
61
57
/**
62
/**
Lines 128-133 Link Here
128
     *  {@link #getExtraInfo(org.netbeans.api.editor.fold.Fold)}.
133
     *  {@link #getExtraInfo(org.netbeans.api.editor.fold.Fold)}.
129
     *
134
     *
130
     * @return new fold instance that was added to the hierarchy.
135
     * @return new fold instance that was added to the hierarchy.
136
     * @deprecated please use {@link #addToHierarchy(org.netbeans.api.editor.fold.FoldType, int, int, java.lang.Boolean, org.netbeans.api.editor.fold.FoldTemplate, java.lang.String, java.lang.Object, org.netbeans.spi.editor.fold.FoldHierarchyTransaction)}.
137
     * This form of call does not support automatic state assignment and fold templates.
131
     */
138
     */
132
    public Fold addToHierarchy(FoldType type, String description, boolean collapsed,
139
    public Fold addToHierarchy(FoldType type, String description, boolean collapsed,
133
    int startOffset, int endOffset, int startGuardedLength, int endGuardedLength,
140
    int startOffset, int endOffset, int startGuardedLength, int endGuardedLength,
Lines 142-147 Link Here
142
    }
149
    }
143
    
150
    
144
    /**
151
    /**
152
     * Adds a fold to the hierarchy.
153
     * The description and the guarded start/end is taken from the 'template' FoldTemplate. As the fold template display
154
     * is the most common override, the override string can be passed in 'displayOverride' (and will be used instead
155
     * of template and instead of type's template).
156
     * <p/>
157
     * The collapsed state can be prescribed, but can {@code null} can be passed to indicate the infrastructure should
158
     * assign collapsed state based on e.g. user preferences. The exact assignment algorithm is left unspecified. Callers
159
     * are recommended not to assign collapsed/expanded state explicitly.
160
     * <p/>
161
     * Usually, it's OK to pass null for collapsed, template and possibly extraInfo.
162
     * <p/>
163
     * Events produced by this add- call will be fired when the 'transaction' is committed. However fold hierarch will
164
     * be changed immediately.
165
     * 
166
     * @param type type of the fold, cannot be {@code null}
167
     * @param startOffset starting offset
168
     * @param endOffset end offset
169
     * @param collapsed the initial collapsed state; if {@code null}, the state will be assigned automatically.
170
     * @param template the FoldTemplate to use instead of default template of the type. {@code null}, if the type's template should be used.
171
     * @param extraInfo arbitrary extra information specific for the fold being created.
172
     *  It's not touched or used by the folding infrastructure in any way.
173
     *  <code>null<code> can be passed if there is no extra information.
174
     *  <br>
175
     *  The extra info of the existing fold can be obtained by
176
     *  {@link #getExtraInfo(org.netbeans.api.editor.fold.Fold)}.
177
     * @param transaction the transaction that manages events, cannot be null.
178
     * @return the created Fold instance
179
     * @throws BadLocationException 
180
     * @since 1.34
181
     */
182
    public Fold addToHierarchy(
183
            FoldType type, 
184
            int startOffset, int endOffset,
185
            Boolean collapsed,
186
            FoldTemplate template, String displayOverride, 
187
            Object extraInfo, FoldHierarchyTransaction transaction) 
188
            throws BadLocationException {
189
        Parameters.notNull("type", type);
190
        Parameters.notNull("transaction", transaction);
191
192
        boolean c;
193
        if (collapsed == null) {
194
            c = impl.getInitialState(type);
195
        } else {
196
            c = collapsed;
197
        }
198
        if (template == null) {
199
            template = type.getTemplate();
200
        }
201
        if (displayOverride == null) {
202
            displayOverride = template.getDescription();
203
        }
204
        Fold fold = impl.createFold(type, displayOverride, 
205
                c, startOffset, endOffset, template.getGuardedStart(),
206
                template.getGuardedEnd(), extraInfo);
207
        impl.addToHierarchy(fold, transaction.getImpl());
208
        return fold;
209
    }
210
    
211
    /**
145
     * This static method can be used to check whether the bounds
212
     * This static method can be used to check whether the bounds
146
     * of the fold that is planned to be added are valid.
213
     * of the fold that is planned to be added are valid.
147
     * <br>
214
     * <br>
Lines 270-291 Link Here
270
        return impl.getHierarchy();
337
        return impl.getHierarchy();
271
    }
338
    }
272
339
340
    /**
341
     * Informs that the manager was released.
342
     * Use the method to check whether the {@link FoldManager} should be still operational.
343
     * Once released, the FoldManager (and its Operation) will not be used again by the infrastructure.
344
     * 
345
     * @return true, if release() was called on the manager
346
     * @since 1.34
347
     */
273
    public boolean isReleased() {
348
    public boolean isReleased() {
274
        return impl.isReleased();
349
        return impl.isReleased();
275
    }
350
    }
276
    
351
    
352
    /**
353
     * Enumerates all Folds defined by this Operation, in the document-range order.
354
     * Outer folds precede the inner ones. Folds, which overlap are enumerated strictly
355
     * in the order of their starting positions. 
356
     * <p/>
357
     * The method may be only called under {@link FoldHierarchy#lock}. The Iterator may
358
     * be only used until that lock is released. After releasing the lock, the Iterator
359
     * may fail.
360
     * 
361
     * @return readonly iterator for all folds defined through this FoldOperation
362
     * @since 1.34
363
     */
364
    public Iterator<Fold>  foldIterator() {
365
        return impl.foldIterator();
366
    }
367
    
368
    /**
369
     * Performs refresh of folding information. The method will:
370
     * <ul>
371
     * <li>remove Folds, which do not appear among the supplied FoldInfos
372
     * <li>add Folds, which do not exist, but are described by some FoldInfo
373
     * <li>attempt to update Folds, which match the FoldInfos
374
     * </ul>
375
     * For each of the supplied FoldInfos, there should be at most 1 Fold either created or found existing, and no
376
     * Folds without a corresponding input FoldInfo should remain in the hierarchy after the call. The mapping from the
377
     * input FoldInfo to the corresponding Fold (created or found existing) is returned.
378
     * <p/>
379
     * Note that Folds, which are blocked (e.g. by a higher-priority manager) will be added/removed/updated and
380
     * returned as well. In order to find whether a specific Fold is blocked, please call {@link #isBlocked}.
381
     * <p/>
382
     * If the {@code removed} or {@code created} parameters are not null, the removed Fold instances, or the FoldInfos
383
     * that created new Folds will be put into those collection as a supplemental return value. The caller may then
384
     * update its own data with respect to the changes and the current Fold set.
385
     * <p/>
386
     * <b>Note:</b> The method may be only called under {@link FoldHierarchy#lock}. This implies the document is also read-locked.
387
     * The caller <b>must check</b> whether a modification happen in between the FoldInfos were produced at the 
388
     * document + hierarchy lock. The method creates and commits its own {@link FoldTransaction} - they are not reentrant,
389
     * so do not call the method under a transaction.
390
     * 
391
     * @param infos current state of folds that should be updated to the hierarchy
392
     * @param removed Collection that will receive Folds, which have been removed from the hierarchy, or {@code null}, if the caller
393
     * does not want to receive the information
394
     * @param created Collection that will receive FoldInfos that created new Folds in the hierarchy, {@code null} means
395
     * the caller is not interested in the creation information.
396
     * 
397
     * @return the mapping from FoldInfos supplied as an input to current Folds. {@code null}, if the manager has been
398
     * released.
399
     * @since 1.34
400
     */
401
    public Map<FoldInfo, Fold> update(
402
            Collection<FoldInfo> infos, 
403
            Collection<Fold> removed, 
404
            Collection<FoldInfo> created) throws BadLocationException {
405
        Parameters.notNull("infos", infos);
406
        return impl.update(infos, removed, created);
407
    }
277
    
408
    
278
    private static final class SpiPackageAccessorImpl extends SpiPackageAccessor {
409
    private static final class SpiPackageAccessorImpl extends SpiPackageAccessor {
279
410
411
        @Override
280
        public FoldHierarchyTransaction createFoldHierarchyTransaction(
412
        public FoldHierarchyTransaction createFoldHierarchyTransaction(
281
        FoldHierarchyTransactionImpl impl) {
413
        FoldHierarchyTransactionImpl impl) {
282
            return new FoldHierarchyTransaction(impl);
414
            return new FoldHierarchyTransaction(impl);
283
        }
415
        }
284
        
416
        
417
        @Override
285
        public FoldHierarchyTransactionImpl getImpl(FoldHierarchyTransaction transaction) {
418
        public FoldHierarchyTransactionImpl getImpl(FoldHierarchyTransaction transaction) {
286
            return transaction.getImpl();
419
            return transaction.getImpl();
287
        }
420
        }
288
        
421
        
422
        @Override
289
        public FoldOperation createFoldOperation(FoldOperationImpl impl) {
423
        public FoldOperation createFoldOperation(FoldOperationImpl impl) {
290
            return new FoldOperation(impl);
424
            return new FoldOperation(impl);
291
        }
425
        }
(-)a/editor.fold/src/org/netbeans/spi/editor/fold/FoldTypeProvider.java (+85 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2012 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 2012 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.spi.editor.fold;
43
44
import java.util.Collection;
45
import org.netbeans.api.editor.fold.FoldType;
46
import org.netbeans.spi.editor.mimelookup.MimeLocation;
47
48
/**
49
 * Provider of FoldType constants for the MimeType. 
50
 * The Provider should enumerate FoldTypes that apply to the given MIME type.
51
 * There can be multiple providers for a MIME type - some advanced constructions in
52
 * the language can be recognized / folded by extension modules. Consider Java vs.
53
 * Bean Patterns, or XML vs. Spring bean config.
54
 * <p/>
55
 * FoldTypes will be collected and some pieces of UI can present the folds, such 
56
 * as Auto-folding options.
57
 * <p/>
58
 * The Provider may specify inheritable=true; in that case the contributed FoldTypes
59
 * will become available for more specific MIME types, too. For example, if a FoldTypeProvider
60
 * for text/xml registers FoldTypes TAG and COMMENT with inheritable=true,
61
 * those FoldTypes will be listed also for text/x-ant+xml. This feature allows 
62
 * to "inject" Fold types and FoldManager on general MIME type ("") for all 
63
 * types of files.
64
 * 
65
 * @author sdedic
66
 * @since 1.34
67
 */
68
@MimeLocation(subfolderName = "FoldManager")
69
public interface FoldTypeProvider {
70
    /**
71
     * Enumerates values for the given type.
72
     * @return FoldType values.
73
     */
74
    public Collection getValues(Class type);
75
    
76
    /**
77
     * Determines whether the folds propagate to child mime types(paths).
78
     * If the method returns true, then more specific MIME types will also
79
     * list FoldTypes returned by this Provider. 
80
     * 
81
     * @return whether the provided FoldTypes should be inherited (true).
82
     */
83
    public boolean inheritable();
84
    
85
}
(-)a/editor.fold/test/unit/src/org/netbeans/modules/editor/fold/FoldHierarchyTestEnv.java (-1 / +1 lines)
Lines 68-74 Link Here
68
        this(new FoldManagerFactory[] { factory });
68
        this(new FoldManagerFactory[] { factory });
69
    }
69
    }
70
70
71
    FoldHierarchyTestEnv(FoldManagerFactory[] factories) {
71
    FoldHierarchyTestEnv(FoldManagerFactory... factories) {
72
        pane = new JEditorPane();
72
        pane = new JEditorPane();
73
        assert (getMimeType() != null);
73
        assert (getMimeType() != null);
74
74
(-)a/editor.fold/test/unit/src/org/netbeans/modules/editor/fold/FoldOperationTest.java (+559 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.util.ArrayList;
45
import java.util.Collection;
46
import java.util.Iterator;
47
import java.util.List;
48
import java.util.Map;
49
import javax.swing.JPanel;
50
import javax.swing.SwingUtilities;
51
import javax.swing.event.DocumentEvent;
52
import javax.swing.text.AbstractDocument;
53
import javax.swing.text.BadLocationException;
54
import static junit.framework.Assert.assertEquals;
55
import static junit.framework.Assert.assertFalse;
56
import static junit.framework.Assert.assertNotNull;
57
import static junit.framework.Assert.assertSame;
58
import static junit.framework.Assert.assertTrue;
59
import org.netbeans.api.editor.fold.Fold;
60
import org.netbeans.api.editor.fold.FoldHierarchy;
61
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
62
import org.netbeans.api.editor.fold.FoldHierarchyListener;
63
import org.netbeans.api.editor.fold.FoldStateChange;
64
import org.netbeans.api.editor.fold.FoldTemplate;
65
import org.netbeans.api.editor.fold.FoldType;
66
import org.netbeans.api.editor.fold.FoldUtilities;
67
import org.netbeans.junit.NbTestCase;
68
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
69
import org.netbeans.spi.editor.fold.FoldInfo;
70
import org.netbeans.spi.editor.fold.FoldManager;
71
import org.netbeans.spi.editor.fold.FoldManagerFactory;
72
import org.netbeans.spi.editor.fold.FoldOperation;
73
74
/**
75
 *
76
 * @author sdedic
77
 */
78
public class FoldOperationTest extends NbTestCase {
79
80
    public FoldOperationTest(String name) {
81
        super(name);
82
    }
83
84
    /*
85
     * update* tests use a simple fold manager, which defines several folds, some of them nested.
86
     * A[0,1]
87
     * B[2,7]
88
     *  C[3,4]
89
     *  D[5,6]
90
     * E[8,9]
91
     */
92
    
93
    private static int[][] foldRanges = new int[][] {
94
        {0, 1},
95
        {3,12},
96
            {5, 6},
97
            {9, 10},
98
        {14, 16}
99
    };
100
    
101
    /**
102
     * Checks that no changes (update with the same infos) will not fire events, remove or add any folds.
103
     * 
104
     * @throws Exception 
105
     */
106
    public void testUpdateNoChanges() throws Exception {
107
        final TestFoldManager[] mgr = new TestFoldManager[1];
108
        FoldHierarchyTestEnv env = new FoldHierarchyTestEnv(new FoldManagerFactory() {
109
            @Override
110
            public FoldManager createFoldManager() {
111
                return mgr[0] = new TestFoldManager();
112
            }
113
        });
114
        AbstractDocument doc = env.getDocument();
115
        doc.insertString(0, "12345678901234567890", null);
116
117
        FoldHierarchy hierarchy = env.getHierarchy();
118
        
119
        Collection<FoldInfo> infos = new ArrayList<FoldInfo>(10);
120
        infos.add(FoldInfo.range(0, 1, FoldType.MEMBER));
121
        infos.add(FoldInfo.range(9, 10, FoldType.MEMBER));
122
        infos.add(FoldInfo.range(14, 16, FoldType.MEMBER));
123
        // not sorted, check :)
124
        infos.add(FoldInfo.range(3, 12, FoldType.MEMBER));
125
        infos.add(FoldInfo.range(5, 6, FoldType.MEMBER));
126
        
127
        doc.readLock();
128
        try {
129
            hierarchy.lock();
130
            TestFoldManager m = mgr[0];
131
            try {
132
                Collection<Fold> remove = new ArrayList<Fold>();
133
                Collection<FoldInfo> create = new ArrayList<FoldInfo>();
134
                
135
                final boolean[] changed = new boolean[1];
136
                
137
                hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
138
139
                    @Override
140
                    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
141
                        changed[0] = true;
142
                    }
143
                });
144
                
145
                Map<FoldInfo, Fold> mapping = m.operation.update(infos, remove, create);
146
                
147
                assertEquals(m.initFolds.size(), mapping.size());
148
                assertTrue(m.initFolds.containsAll(mapping.values()));
149
                assertFalse(changed[0]);
150
                
151
            } finally {
152
                hierarchy.unlock();
153
            }
154
        } finally {
155
            doc.readUnlock();
156
        }
157
    }
158
    
159
    private List<FoldInfo> createDefaultInfos() {
160
        List<FoldInfo> infos = new ArrayList<FoldInfo>(10);
161
        infos.add(FoldInfo.range(0, 1, FoldType.MEMBER));
162
        infos.add(FoldInfo.range(3, 12, FoldType.MEMBER));
163
        infos.add(FoldInfo.range(5, 6, FoldType.MEMBER));
164
        infos.add(FoldInfo.range(9, 10, FoldType.MEMBER));
165
        infos.add(FoldInfo.range(14, 16, FoldType.MEMBER));
166
        return infos;
167
    }
168
    
169
    /**
170
     * Checks that folds are created beween two folds, encapsulating existing folds.
171
     * 
172
     * @throws Exception 
173
     */
174
    public void testUpdateCreateFold() throws Exception {
175
        final TestFoldManager[] mgr = new TestFoldManager[1];
176
        FoldHierarchyTestEnv env = new FoldHierarchyTestEnv(new FoldManagerFactory() {
177
            @Override
178
            public FoldManager createFoldManager() {
179
                return mgr[0] = new TestFoldManager();
180
            }
181
        });
182
        AbstractDocument doc = env.getDocument();
183
        doc.insertString(0, "12345678901234567890", null);
184
185
        FoldHierarchy hierarchy = env.getHierarchy();
186
        
187
        List<FoldInfo> infos = createDefaultInfos();
188
        
189
        // add a new fold between #1 and #2
190
        infos.add(FoldInfo.range(2, 3, FoldType.MEMBER));
191
        
192
        // add a new fold at the end:
193
        infos.add(FoldInfo.range(19,20, FoldType.MEMBER));
194
        
195
        // add a fold, which encapsulates #2 - #5
196
        infos.add(FoldInfo.range(3, 18, FoldType.MEMBER));
197
        
198
        // add a fold, which encapsulates ##5
199
        infos.add(FoldInfo.range(13, 16, FoldType.MEMBER));
200
        
201
        doc.readLock();
202
        try {
203
            hierarchy.lock();
204
            TestFoldManager m = mgr[0];
205
            try {
206
                Collection<Fold> remove = new ArrayList<Fold>();
207
                Collection<FoldInfo> create = new ArrayList<FoldInfo>();
208
                
209
                final boolean[] changed = new boolean[1];
210
                
211
                hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
212
                    @Override
213
                    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
214
                        changed[0] = true;
215
                    }
216
                });
217
                Map<FoldInfo, Fold> mapping = m.operation.update(infos, remove, create);
218
                
219
                // 3 folds added, no deleted:
220
                assertEquals(4, create.size());
221
                assertEquals(0, remove.size());
222
                
223
            } finally {
224
                hierarchy.unlock();
225
            }
226
        } finally {
227
            doc.readUnlock();
228
        }
229
    }
230
    
231
    /**
232
     * Checks that updated folds can SHIFT. If a fold should be replaced by a same-type fold, which 
233
     * fully contains the original one, or is fully contained by the original one, the original fold will
234
     * be updated rather than removed/created. This behaviour eliminates issues with collapsing imports.
235
     * 
236
     * @throws Exception 
237
     */
238
    public void testUpdateShiftFolds() throws Exception {
239
        final TestFoldManager[] mgr = new TestFoldManager[1];
240
        FoldHierarchyTestEnv env = new FoldHierarchyTestEnv(new FoldManagerFactory() {
241
            @Override
242
            public FoldManager createFoldManager() {
243
                return mgr[0] = new TestFoldManager();
244
            }
245
        });
246
        AbstractDocument doc = env.getDocument();
247
        doc.insertString(0, "12345678901234567890", null);
248
249
        FoldHierarchy hierarchy = env.getHierarchy();
250
        
251
        List<FoldInfo> infos = createDefaultInfos();
252
        
253
        // Fold #3 is not shifted, as the new fold does not intersect with the old one. Fold will be replaced.
254
        FoldInfo cInfo;
255
        infos.add(cInfo = FoldInfo.range(4, 5, FoldType.MEMBER));
256
        
257
        // Fold #4 is extended backwards
258
        infos.add(FoldInfo.range(8, 10, FoldType.MEMBER));
259
        
260
        // Fold #5 is extended in both directions
261
        infos.add(FoldInfo.range(13, 17, FoldType.MEMBER));
262
        
263
        infos.remove(4); infos.remove(3); infos.remove(2);
264
        
265
        TestFoldManager m = mgr[0];
266
        final Fold fold8 = FoldUtilities.findNearestFold(hierarchy, 8);
267
        final Fold fold13 = FoldUtilities.findNearestFold(hierarchy, 13);
268
        
269
        class FHL implements FoldHierarchyListener {
270
            boolean changed;
271
            
272
            @Override
273
            public void foldHierarchyChanged(FoldHierarchyEvent evt) {
274
                changed = true;
275
                assertEquals(2, evt.getFoldStateChangeCount());
276
                for (int i = evt.getFoldStateChangeCount() - 1; i >= 0; i--) {
277
                    FoldStateChange chg = evt.getFoldStateChange(i);
278
                    if (chg.getFold() == fold8) {
279
                        assertEquals(9, chg.getOriginalStartOffset());
280
                        assertEquals(-1, chg.getOriginalEndOffset());
281
                    } else if (chg.getFold() == fold13) {
282
                        assertEquals(14, chg.getOriginalStartOffset());
283
                        assertEquals(16, chg.getOriginalEndOffset());
284
                    } else {
285
                        fail("Unexpected change");
286
                    }
287
                }
288
            }
289
        }
290
        
291
        FHL fhl = new FHL();
292
        doc.readLock();
293
        try {
294
            hierarchy.lock();
295
            try {
296
                Collection<Fold> remove = new ArrayList<Fold>();
297
                Collection<FoldInfo> create = new ArrayList<FoldInfo>();
298
                
299
                hierarchy.addFoldHierarchyListener(fhl);
300
                Map<FoldInfo, Fold> mapping = m.operation.update(infos, remove, create);
301
                
302
                // 3 folds added, no deleted:
303
                assertEquals(1, create.size());
304
                assertEquals(1, remove.size());
305
                
306
                assertSame(cInfo, create.iterator().next());
307
                
308
                Fold f = remove.iterator().next();
309
                // old fold
310
                assertEquals(5, f.getStartOffset());
311
                
312
                // new fold
313
                FoldInfo newInfo = create.iterator().next();
314
                assertSame(cInfo, newInfo);
315
                f = mapping.get(cInfo);
316
                assertEquals(4, f.getStartOffset());
317
            } finally {
318
                hierarchy.unlock();
319
            }
320
        } finally {
321
            doc.readUnlock();
322
        }
323
    }
324
    
325
    /**
326
     * Checks that a released operation will not update anything
327
     * 
328
     * @throws Exception 
329
     */
330
    public void testNoUpdateAfterRelease() throws Exception {
331
        final TestFoldManager[] mgr = new TestFoldManager[1];
332
        final FoldHierarchyTestEnv env = new FoldHierarchyTestEnv(new FoldManagerFactory() {
333
            @Override
334
            public FoldManager createFoldManager() {
335
                return mgr[0] = new TestFoldManager();
336
            }
337
        });
338
        AbstractDocument doc = env.getDocument();
339
        doc.insertString(0, "12345678901234567890", null);
340
341
        FoldHierarchy hierarchy = env.getHierarchy();
342
        List<FoldInfo> infos = createDefaultInfos();
343
        
344
        // add a new fold between #1 and #2
345
        infos.add(FoldInfo.range(2, 3, FoldType.MEMBER));
346
        
347
        // add a new fold at the end:
348
        infos.add(FoldInfo.range(19,20, FoldType.MEMBER));
349
        
350
        // add a fold, which encapsulates #2 - #5
351
        infos.add(FoldInfo.range(3, 18, FoldType.MEMBER));
352
        
353
        infos.remove(4); infos.remove(3); infos.remove(2);
354
        
355
        SwingUtilities.invokeAndWait(new Runnable() {
356
            public void run() {
357
                JPanel outer = new JPanel();
358
                // force parent change 
359
                outer.add(env.getPane());
360
            }
361
        });
362
        // listener is attached in between these events
363
        SwingUtilities.invokeAndWait(new Runnable() {
364
            public void run() {
365
                // nothing, just wait for the delayed events to process.
366
            }
367
        });
368
        FoldHierarchyExecution.waitAllTasks();
369
        
370
        doc.readLock();
371
        try {
372
            hierarchy.lock();
373
            TestFoldManager m = mgr[0];
374
            try {
375
                
376
                Collection<Fold> remove = new ArrayList<Fold>();
377
                Collection<FoldInfo> create = new ArrayList<FoldInfo>();
378
                final boolean[] changed = new boolean[1];
379
                
380
                hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
381
                    @Override
382
                    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
383
                        changed[0] = true;
384
                    }
385
                });
386
                Map<FoldInfo, Fold> mapping = m.operation.update(infos, remove, create);
387
                assertNull(mapping);
388
                // 3 folds added, no deleted:
389
                assertEquals(0, create.size());
390
                assertEquals(0, remove.size());
391
                assertFalse(changed[0]);
392
                
393
            } finally {
394
                hierarchy.unlock();
395
            }
396
        } finally {
397
            doc.readUnlock();
398
        }
399
    }
400
    
401
    
402
    /**
403
     * Checks that foldIterator() enumerates blocked folds, and blocked folds blocked by blocked folds
404
     * 
405
     * @throws Exception 
406
     */
407
    public void testFoldIterator() throws Exception {
408
        TestFoldManager baseFoldManager = new TestFoldManager(BASE_RANGE, FoldType.MEMBER);
409
        TestFoldManager overrideFoldManager = new TestFoldManager(OVERRIDE_RANGE, FoldType.NESTED);
410
        TestFoldManager uberFoldManager = new TestFoldManager(UBER_RANGE, FoldType.USER);
411
        
412
        class FMF implements FoldManagerFactory {
413
            private FoldManager fm;
414
415
            public FMF(FoldManager fm) {
416
                this.fm = fm;
417
            }
418
419
            @Override
420
            public FoldManager createFoldManager() {
421
                return fm;
422
            }
423
        }
424
        
425
        final FoldHierarchyTestEnv env = new FoldHierarchyTestEnv(
426
                new FMF(uberFoldManager), new FMF(overrideFoldManager), new FMF(baseFoldManager)
427
        );
428
        AbstractDocument doc = env.getDocument();
429
        StringBuilder sb = new StringBuilder();
430
        for (int i = 0; i < 20; i++) {
431
            sb.append("12345678901234567890");
432
        }
433
        doc.insertString(0, sb.toString(), null);
434
        
435
        FoldHierarchy hierarchy = env.getHierarchy();
436
        hierarchy.lock();
437
        // now update the uber-range, so that the new fold blocks the blocking fold:
438
        FoldOperation fo = uberFoldManager.operation;
439
        FoldHierarchyTransaction t = fo.openTransaction();
440
        fo.addToHierarchy(FoldType.USER, 110, 170, null, null, null, null, t);
441
        t.commit();
442
        
443
        // check that the hierarchy contains the proper overrides:
444
        Fold f = FoldUtilities.findOffsetFold(hierarchy, 2);
445
        assertNotNull(f);
446
        assertSame(FoldType.NESTED, f.getType());
447
        
448
        f = FoldUtilities.findOffsetFold(hierarchy, 122);
449
        assertNotNull(f);
450
        assertSame(FoldType.USER, f.getType());
451
        
452
        // enumerate all folds of the base iterator
453
        fo = baseFoldManager.operation;
454
        
455
        Iterator<Fold> folds = fo.foldIterator();
456
        for (int i = 0; i < BASE_RANGE.length; i++) {
457
            int[] rng = BASE_RANGE[i];
458
            f = folds.next();
459
            
460
            assertSame(FoldType.MEMBER, f.getType());
461
            assertEquals(rng[0], f.getStartOffset());
462
            assertEquals(rng[1], f.getEndOffset());
463
        }
464
        assertFalse(folds.hasNext());
465
    }
466
    
467
    private static int[][] BASE_RANGE = {
468
        { 2, 4 },
469
        { 7, 100},
470
            { 10, 30 },
471
            { 40, 50 },
472
        { 120, 200 },
473
            { 130, 140 },
474
            { 150, 160 }
475
    };
476
477
    private static int[][] OVERRIDE_RANGE = {
478
        { 1, 3 },   
479
        { 7, 100},  
480
            { 8, 20 }, // intersect at the beginning
481
            { 45, 60 }, // intersect at the end
482
        { 120, 200 },   // block
483
            { 125, 142 }, // fully contain
484
            { 155, 157 }  // proper child
485
    };
486
    
487
    private static int[][] UBER_RANGE = {
488
    };
489
    
490
    private class TestFoldManager implements FoldManager {
491
        FoldOperation operation;
492
        Collection<Fold> initFolds = new ArrayList<Fold>(6);
493
        int[][] ranges;
494
        FoldType type;
495
        
496
        public TestFoldManager() {
497
            ranges = foldRanges;
498
            type = FoldType.MEMBER;
499
        }
500
        
501
        public TestFoldManager(int[][] ranges, FoldType type) {
502
            this.ranges = ranges;
503
            this.type = type;
504
        }
505
        
506
        
507
        @Override
508
        public void init(FoldOperation operation) {
509
            this.operation = operation;
510
        }
511
512
        public void initFolds(FoldHierarchyTransaction transaction)  {
513
            for (int i = 0; i < ranges.length; i++) {
514
                int[] range = ranges[i];
515
                try {
516
                    initFolds.add(operation.addToHierarchy(
517
                            type, 
518
                            range[0], 
519
                            range[1], 
520
                            null, 
521
                            null, 
522
                            null, 
523
                            null, transaction
524
                    ));
525
                } catch (BadLocationException ex) {
526
                }
527
            }
528
        }
529
530
        @Override
531
        public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
532
        }
533
534
        @Override
535
        public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
536
        }
537
538
        @Override
539
        public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
540
        }
541
542
        @Override
543
        public void removeEmptyNotify(Fold epmtyFold) {
544
        }
545
546
        @Override
547
        public void removeDamagedNotify(Fold damagedFold) {
548
        }
549
550
        @Override
551
        public void expandNotify(Fold expandedFold) {
552
        }
553
554
        @Override
555
        public void release() {
556
            // purposely ignore
557
        }
558
    }   
559
}
(-)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="export" 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.33"/>
68
            </trigger-dependency>
69
            <implies>
70
                <result>
71
                    <module-dependency codenamebase="org.netbeans.modules.editor.fold" major="1" spec="1.33"/>
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
     * @deprecated Without any replacement.
344
     */
345
    protected Font getColoringFont() {
346
        if (font == null) {
347
            updateColors();
348
        }
349
        return font;
350
    }
351
    
352
    // overriding due to issue #60304
353
    public @Override void update(Graphics g) {
354
    }
355
    
356
    protected void collectPaintInfos(
357
        View rootView, Fold fold, Map<Integer, PaintInfo> map, int level, int startIndex, int endIndex
358
    ) throws BadLocationException {
359
        //never called
360
    }
361
362
    /**
363
     * Adjust lowest/topmost boundaries from the Fold range y1-y2.
364
     * @param y1
365
     * @param y2
366
     * @param level 
367
     */
368
    private void setMouseBoundaries(int y1, int y2, int level) {
369
        if (!hasMousePoint() || mousePointConsumed) {
370
            return;
371
        }
372
        int y = mousePoint;
373
        if (y2 < y && lowestAboveMouse < y2) {
374
            LOG.log(Level.FINEST, "lowestAbove at {1}: {0}", new Object[] { y2, level });
375
            lowestAboveMouse = y2;
376
        }
377
        if (y1 > y && topmostBelowMouse > y1) {
378
            LOG.log(Level.FINEST, "topmostBelow at {1}: {0}", new Object[] { y1, level });
379
            topmostBelowMouse = y1;
380
        }
381
    }
382
    
383
    /*
384
     * Even collapsed fold MAY contain a continuation line, IF one of folds on the same line is NOT collapsed. Such a fold should
385
     * then visually span multiple lines && be marked as collapsed.
386
     */
387
388
    protected List<? extends PaintInfo> getPaintInfo(Rectangle clip) throws BadLocationException {
389
        javax.swing.plaf.TextUI textUI = component.getUI();
390
        if (!(textUI instanceof BaseTextUI)) {
391
            return Collections.<PaintInfo>emptyList();
392
        }
393
        BaseTextUI baseTextUI = (BaseTextUI)textUI;
394
        BaseDocument bdoc = Utilities.getDocument(component);
395
        if (bdoc == null) {
396
            return Collections.<PaintInfo>emptyList();
397
        }
398
        mousePointConsumed = false;
399
        mouseBoundary = null;
400
        topmostBelowMouse = Integer.MAX_VALUE;
401
        lowestAboveMouse = -1;
402
        bdoc.readLock();
403
        try {
404
            int startPos = baseTextUI.getPosFromY(clip.y);
405
            int endPos = baseTextUI.viewToModel(component, Short.MAX_VALUE / 2, clip.y + clip.height);
406
            
407
            if (startPos < 0 || endPos < 0) {
408
                // editor window is not properly sized yet; return no infos
409
                return Collections.<PaintInfo>emptyList();
410
            }
411
            
412
            // #218282: if the view hierarchy is not yet updated, the Y coordinate may map to an incorrect offset outside
413
            // the document.
414
            int docLen = bdoc.getLength();
415
            if (startPos >= docLen || endPos > docLen) {
416
                return Collections.<PaintInfo>emptyList();
417
            }
418
            
419
            startPos = Utilities.getRowStart(bdoc, startPos);
420
            endPos = Utilities.getRowEnd(bdoc, endPos);
421
            
422
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
423
            hierarchy.lock();
424
            try {
425
                View rootView = Utilities.getDocumentView(component);
426
                if (rootView != null) {
427
                    Object [] arr = getFoldList(hierarchy.getRootFold(), startPos, endPos);
428
                    @SuppressWarnings("unchecked")
429
                    List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
430
                    int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
431
432
                    /*
433
                     * Note:
434
                     * 
435
                     * The Map is keyed by Y-VISUAL position of the fold mark, not the textual offset of line start.
436
                     * This is because several folds may occupy the same line, while only one + sign is displayed,
437
                     * and affect the last fold in the row.
438
                     */
439
                    NavigableMap<Integer, PaintInfo> map = new TreeMap<Integer, PaintInfo>();
440
                    // search backwards
441
                    for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
442
                        Fold fold = foldList.get(i);
443
                        if (!traverseBackwards(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
444
                            break;
445
                        }
446
                    }
447
448
                    // search forward
449
                    for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
450
                        Fold fold = foldList.get(i);
451
                        if (!traverseForward(fold, bdoc, baseTextUI, startPos, endPos, 0, map)) {
452
                            break;
453
                        }
454
                    }
455
                    
456
                    if (map.isEmpty() && foldList.size() > 0) {
457
                        assert foldList.size() == 1;
458
                        PaintInfo pi = new PaintInfo(PAINT_LINE, 0, clip.y, clip.height, -1, -1);
459
                        mouseBoundary = new Rectangle(0, 0, 0, clip.height);
460
                        LOG.log(Level.FINEST, "Mouse boundary for full side line set to: {0}", mouseBoundary);
461
                        if (hasMousePoint()) {
462
                            pi.markActive(true, true, true);
463
                        }
464
                        return Collections.singletonList(pi);
465
                    } else {
466
                        if (mouseBoundary == null) {
467
                            mouseBoundary = makeMouseBoundary(clip.y, clip.y + clip.height);
468
                            LOG.log(Level.FINEST, "Mouse boundary not set, defaulting to: {0}", mouseBoundary);
469
                        }
470
                        return new ArrayList<PaintInfo>(map.values());
471
                    }
472
                } else {
473
                    return Collections.<PaintInfo>emptyList();
474
                }
475
            } finally {
476
                hierarchy.unlock();
477
            }
478
        } finally {
479
            bdoc.readUnlock();
480
        }
481
    }
482
    
483
    /**
484
     * Adds a paint info to the map. If a paintinfo already exists, it merges
485
     * the structures, so the painting process can just follow the instructions.
486
     * 
487
     * @param infos
488
     * @param yOffset
489
     * @param nextInfo 
490
     */
491
    private void addPaintInfo(Map<Integer, PaintInfo> infos, int yOffset, PaintInfo nextInfo) {
492
        PaintInfo prevInfo = infos.get(yOffset);
493
        nextInfo.mergeWith(prevInfo);
494
        infos.put(yOffset, nextInfo);
495
    }
496
497
    private boolean traverseForward(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary,int level,  NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
498
//        System.out.println("~~~ traverseForward<" + lowerBoundary + ", " + upperBoundary
499
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
500
//                + (f.getStartOffset() > upperBoundary ? ", f.gSO > uB" : "")
501
//                + ", level=" + level);
502
        
503
        if (f.getStartOffset() > upperBoundary) {
504
            return false;
505
        }
506
507
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
508
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
509
        int y1 = btui.getYFromPos(lineStartOffset1);
510
        int h = btui.getEditorUI().getLineHeight();
511
        int y2 = btui.getYFromPos(lineStartOffset2);
512
         
513
        // the 'active' flags can be set only after children are processed; highlights
514
        // correspond to the innermost expanded child.
515
        boolean activeMark = false;
516
        boolean activeIn = false;
517
        boolean activeOut = false;
518
        PaintInfo spi;
519
        boolean activated;
520
        
521
        if (y1 == y2) {
522
            // whole fold is on a single line
523
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
524
            if (activated = isActivated(y1, y1 + h)) {
525
                activeMark = true;
526
            }
527
            addPaintInfo(infos, y1, spi);
528
        } else {
529
            // fold spans multiple lines
530
            spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
531
            if (activated = isActivated(y1, y2 + h / 2)) {
532
                activeMark = true;
533
                activeOut = true;
534
            }
535
            addPaintInfo(infos, y1, spi);
536
        }
537
538
        setMouseBoundaries(y1, y2 + h / 2, level);
539
540
        // Handle end mark after possible inner folds were processed because
541
        // otherwise if there would be two nested folds both ending at the same line
542
        // then the end mark for outer one would be replaced by an end mark for inner one
543
        // (same key in infos map) and the painting code would continue to paint line marking a fold
544
        // until next fold is reached (or end of doc).
545
        PaintInfo epi = null;
546
        if (y1 != y2 && !f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
547
            epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
548
            addPaintInfo(infos, y2, epi);
549
        }
550
551
        // save the topmost/lowest information, reset for child processing
552
        int topmost = topmostBelowMouse;
553
        int lowest = lowestAboveMouse;
554
        topmostBelowMouse = y2 + h / 2;
555
        lowestAboveMouse = y1;
556
557
        try {
558
            if (!f.isCollapsed()) {
559
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
560
                @SuppressWarnings("unchecked")
561
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
562
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
563
564
                // search backwards
565
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
566
                    Fold fold = foldList.get(i);
567
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
568
                        break;
569
                    }
570
                }
571
572
                // search forward
573
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
574
                    Fold fold = foldList.get(i);
575
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
576
                        return false;
577
                    }
578
                }
579
            }
580
            if (!mousePointConsumed && activated) {
581
                mousePointConsumed = true;
582
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
583
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
584
                spi.markActive(activeMark, activeIn, activeOut);
585
                if (epi != null) {
586
                    epi.markActive(true, true, false);
587
                }
588
                markDeepChildrenActive(infos, y1, y2, level);
589
            }
590
        } finally {
591
            topmostBelowMouse = topmost;
592
            lowestAboveMouse = lowest;
593
        }
594
        return true;
595
    }
596
     
597
    /**
598
     * Sets outlines of all children to 'active'. Assuming yFrom and yTo are from-to Y-coordinates of the parent
599
     * fold, it finds all nested folds (folds, which are in between yFrom and yTo) and changes their in/out lines
600
     * as active.
601
     * The method returns Y start coordinate of the 1st child found.
602
     * 
603
     * @param infos fold infos collected so far
604
     * @param yFrom upper Y-coordinate of the parent fold
605
     * @param yTo lower Y-coordinate of the parent fold
606
     * @param level level of the parent fold
607
     * @return Y-coordinate of the 1st child.
608
     */
609
    private int markDeepChildrenActive(NavigableMap<Integer, PaintInfo> infos, int yFrom, int yTo, int level) {
610
        int result = Integer.MAX_VALUE;
611
        Map<Integer, PaintInfo> m = infos.subMap(yFrom, yTo);
612
        for (Map.Entry<Integer, PaintInfo> me : m.entrySet()) {
613
            PaintInfo pi = me.getValue();
614
            int y = pi.getPaintY();
615
            if (y > yFrom && y < yTo) {
616
                if (LOG.isLoggable(Level.FINEST)) {
617
                    LOG.log(Level.FINEST, "Marking chind as active: {0}", pi);
618
                }
619
                pi.markActive(false, true, true);
620
                if (y < result) {
621
                    y = result;
622
                }
623
            }
624
        }
625
        return result;
626
    }
627
    
628
    /**
629
     * Returns stroke appropriate for painting (in)active outlines
630
     * @param s the default stroke
631
     * @param active true for active outlines
632
     * @return value of 's' or a Stroke which should be used to paint the outline.
633
     */
634
    private static Stroke getStroke(Stroke s, boolean active) {
635
        if (active) {
636
            return LINE_BOLD;
637
        } else {
638
            return s;
639
        }
640
    }
641
    
642
    private boolean traverseBackwards(Fold f, BaseDocument doc, BaseTextUI btui, int lowerBoundary, int upperBoundary, int level, NavigableMap<Integer, PaintInfo> infos) throws BadLocationException {
643
//        System.out.println("~~~ traverseBackwards<" + lowerBoundary + ", " + upperBoundary
644
//                + ">: fold=<" + f.getStartOffset() + ", " + f.getEndOffset() + "> "
645
//                + (f.getEndOffset() < lowerBoundary ? ", f.gEO < lB" : "")
646
//                + ", level=" + level);
647
648
        if (f.getEndOffset() < lowerBoundary) {
649
            return false;
650
        }
651
652
        int lineStartOffset1 = Utilities.getRowStart(doc, f.getStartOffset());
653
        int lineStartOffset2 = Utilities.getRowStart(doc, f.getEndOffset());
654
        int h = btui.getEditorUI().getLineHeight();
655
656
        boolean activeMark = false;
657
        boolean activeIn = false;
658
        boolean activeOut = false;
659
        PaintInfo spi = null;
660
        PaintInfo epi = null;
661
        boolean activated = false;
662
        int y1 = 0;
663
        int y2 = 0;
664
        
665
        if (lineStartOffset1 == lineStartOffset2) {
666
            // whole fold is on a single line
667
            y2 = y1 = btui.getYFromPos(lineStartOffset1);
668
            spi = new PaintInfo(SINGLE_PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset1);
669
            if (activated = isActivated(y1, y1 + h)) {
670
                activeMark = true;
671
            }
672
            addPaintInfo(infos, y1, spi);
673
        } else {
674
            y2 = btui.getYFromPos(lineStartOffset2);
675
            // fold spans multiple lines
676
            y1 = btui.getYFromPos(lineStartOffset1);
677
            activated = isActivated(y1, y2 + h / 2);
678
            if (f.getStartOffset() >= upperBoundary) {
679
                spi = new PaintInfo(PAINT_MARK, level, y1, h, f.isCollapsed(), lineStartOffset1, lineStartOffset2);
680
                if (activated) {
681
                    activeMark = true;
682
                    activeOut = true;
683
                }
684
                addPaintInfo(infos, y1, spi);
685
            }
686
687
            if (!f.isCollapsed() && f.getEndOffset() <= upperBoundary) {
688
                activated |= isActivated(y1, y2 + h / 2);
689
                epi = new PaintInfo(PAINT_END_MARK, level, y2, h, lineStartOffset1, lineStartOffset2);
690
                addPaintInfo(infos, y2, epi);
691
            }
692
        }
693
        
694
        setMouseBoundaries(y1, y2 + h / 2, level);
695
696
        // save the topmost/lowest information, reset for child processing
697
        int topmost = topmostBelowMouse;
698
        int lowest = lowestAboveMouse;
699
        topmostBelowMouse = y2 + h /2;
700
        lowestAboveMouse = y1;
701
702
        try {
703
            if (!f.isCollapsed()) {
704
                Object [] arr = getFoldList(f, lowerBoundary, upperBoundary);
705
                @SuppressWarnings("unchecked")
706
                List<? extends Fold> foldList = (List<? extends Fold>) arr[0];
707
                int idxOfFirstFoldStartingInsideClip = (Integer) arr[1];
708
709
                // search backwards
710
                for(int i = idxOfFirstFoldStartingInsideClip - 1; i >= 0; i--) {
711
                    Fold fold = foldList.get(i);
712
                    if (!traverseBackwards(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
713
                        return false;
714
                    }
715
                }
716
717
                // search forward
718
                for(int i = idxOfFirstFoldStartingInsideClip; i < foldList.size(); i++) {
719
                    Fold fold = foldList.get(i);
720
                    if (!traverseForward(fold, doc, btui, lowerBoundary, upperBoundary, level + 1, infos)) {
721
                        break;
722
                    }
723
                }
724
            }
725
            if (!mousePointConsumed && activated) {
726
                mousePointConsumed = true;
727
                mouseBoundary = makeMouseBoundary(y1, y2 + h);
728
                LOG.log(Level.FINEST, "Mouse boundary set to: {0}", mouseBoundary);
729
                if (spi != null) {
730
                    spi.markActive(activeMark, activeIn, activeOut);
731
                }
732
                if (epi != null) {
733
                    epi.markActive(true, true, false);
734
                }
735
                int lowestChild = markDeepChildrenActive(infos, y1, y2, level);
736
                if (lowestChild < Integer.MAX_VALUE && lineStartOffset1 < upperBoundary) {
737
                    // the fold starts above the screen clip region, and is 'activated'. We need to setup instructions to draw activated line up to the
738
                    // 1st child marker.
739
                    epi = new PaintInfo(PAINT_LINE, level, y1, y2 - y1, false, lineStartOffset1, lineStartOffset2);
740
                    epi.markActive(true, true, false);
741
                    addPaintInfo(infos, y1, epi);
742
                }
743
            }
744
        } finally {
745
            topmostBelowMouse = topmost;
746
            lowestAboveMouse = lowest;
747
        }
748
        return true;
749
    }
750
    
751
    private Rectangle makeMouseBoundary(int y1, int y2) {
752
        if (!hasMousePoint()) {
753
            return null;
754
        }
755
        if (topmostBelowMouse < Integer.MAX_VALUE) {
756
            y2 = topmostBelowMouse;
757
        }
758
        if (lowestAboveMouse  > -1) {
759
            y1 = lowestAboveMouse;
760
        }
761
        return new Rectangle(0, y1, 0, y2 - y1);
762
    }
763
    
764
    protected EditorUI getEditorUI(){
765
        return Utilities.getEditorUI(component);
766
    }
767
    
768
    protected Document getDocument(){
769
        return component.getDocument();
770
    }
771
772
773
    private Fold getLastLineFold(FoldHierarchy hierarchy, int rowStart, int rowEnd, boolean shift){
774
        Fold fold = FoldUtilities.findNearestFold(hierarchy, rowStart);
775
        Fold prevFold = fold;
776
        while (fold != null && fold.getStartOffset()<rowEnd){
777
            Fold nextFold = FoldUtilities.findNearestFold(hierarchy, (fold.isCollapsed()) ? fold.getEndOffset() : fold.getStartOffset()+1);
778
            if (nextFold == fold) return fold;
779
            if (nextFold!=null && nextFold.getStartOffset() < rowEnd){
780
                prevFold = shift ? fold : nextFold;
781
                fold = nextFold;
782
            }else{
783
                return prevFold;
784
            }
785
        }
786
        return prevFold;
787
    }
788
    
789
    protected void performAction(Mark mark) {
790
        performAction(mark, false);
791
    }
792
    
793
    private void performActionAt(Mark mark, int mouseY) throws BadLocationException {
794
        if (mark != null) {
795
            return;
796
        }
797
        BaseDocument bdoc = Utilities.getDocument(component);
798
        BaseTextUI textUI = (BaseTextUI)component.getUI();
799
800
        View rootView = Utilities.getDocumentView(component);
801
        if (rootView == null) return;
802
        
803
        bdoc.readLock();
804
        try {
805
            int yOffset = textUI.getPosFromY(mouseY);
806
            FoldHierarchy hierarchy = FoldHierarchy.get(component);
807
            hierarchy.lock();
808
            try {
809
                Fold f = FoldUtilities.findOffsetFold(hierarchy, yOffset);
810
                if (f == null) {
811
                    return;
812
                }
813
                if (f.isCollapsed()) {
814
                    LOG.log(Level.WARNING, "Clicked on a collapsed fold {0} at {1}", new Object[] { f, mouseY });
815
                    return;
816
                }
817
                int startOffset = f.getStartOffset();
818
                int endOffset = f.getEndOffset();
819
                
820
                int startY = textUI.getYFromPos(startOffset);
821
                int nextLineOffset = Utilities.getRowStart(bdoc, startOffset, 1);
822
                int nextY = textUI.getYFromPos(nextLineOffset);
823
824
                if (mouseY >= startY && mouseY <= nextY) {
825
                    LOG.log(Level.FINEST, "Starting line clicked, ignoring. MouseY={0}, startY={1}, nextY={2}",
826
                            new Object[] { mouseY, startY, nextY });
827
                    return;
828
                }
829
830
                startY = textUI.getYFromPos(endOffset);
831
                nextLineOffset = Utilities.getRowStart(bdoc, endOffset, 1);
832
                nextY = textUI.getYFromPos(nextLineOffset);
833
834
                if (mouseY >= startY && mouseY <= nextY) {
835
                    // the mouse can be positioned above the marker (the fold found above), or
836
                    // below it; in that case, the immediate enclosing fold should be used - should be the fold
837
                    // that corresponds to the nextLineOffset, if any
838
                    int h2 = (startY + nextY) / 2;
839
                    if (mouseY >= h2) {
840
                        Fold f2 = f;
841
                        
842
                        f = FoldUtilities.findOffsetFold(hierarchy, nextLineOffset);
843
                        if (f == null) {
844
                            // fold does not exist for the position below end-of-fold indicator
845
                            return;
846
                        }
847
                    }
848
                    
849
                }
850
                
851
                LOG.log(Level.FINEST, "Collapsing fold: {0}", f);
852
                hierarchy.collapse(f);
853
            } finally {
854
                hierarchy.unlock();
855
            }
856
        } finally {
857
            bdoc.readUnlock();
858
        }        
859
    }
860
    
861
    private void performAction(final Mark mark, final boolean shiftFold) {
862
        Document doc = component.getDocument();
863
        doc.render(new Runnable() {
864
            @Override
865
            public void run() {
866
                ViewHierarchy vh = ViewHierarchy.get(component);
867
                LockedViewHierarchy lockedVH = vh.lock();
868
                try {
869
                    int pViewIndex = lockedVH.yToParagraphViewIndex(mark.y + mark.size / 2);
870
                    if (pViewIndex >= 0) {
871
                        ParagraphViewDescriptor pViewDesc = lockedVH.getParagraphViewDescriptor(pViewIndex);
872
                        int pViewStartOffset = pViewDesc.getStartOffset();
873
                        int pViewEndOffset = pViewStartOffset + pViewDesc.getLength();
874
                        // Find corresponding fold
875
                        FoldHierarchy foldHierarchy = FoldHierarchy.get(component);
876
                        foldHierarchy.lock();
877
                        try {
878
                            int rowStart = javax.swing.text.Utilities.getRowStart(component, pViewStartOffset);
879
                            int rowEnd = javax.swing.text.Utilities.getRowEnd(component, pViewStartOffset);
880
                            Fold clickedFold = getLastLineFold(foldHierarchy, rowStart, rowEnd, shiftFold);//FoldUtilities.findNearestFold(foldHierarchy, viewStartOffset);
881
                            if (clickedFold != null && clickedFold.getStartOffset() < pViewEndOffset) {
882
                                foldHierarchy.toggle(clickedFold);
883
                            }
884
                        } catch (BadLocationException ble) {
885
                            LOG.log(Level.WARNING, null, ble);
886
                        } finally {
887
                            foldHierarchy.unlock();
888
                        }
889
                    }
890
                } finally {
891
                    lockedVH.unlock();
892
                }
893
            }
894
        });
895
    }
896
    
897
    protected int getMarkSize(Graphics g){
898
        if (g != null){
899
            FontMetrics fm = g.getFontMetrics(getColoring().getFont());
900
            if (fm != null){
901
                int ret = fm.getAscent() - fm.getDescent();
902
                return ret - ret%2;
903
            }
904
        }
905
        return -1;
906
    }
907
    
908
    private boolean hasMousePoint() {
909
        return mousePoint >= 0;
910
    }
911
    
912
    private boolean isActivated(int y1, int y2) {
913
        return hasMousePoint() && 
914
               (mousePoint >= y1 && mousePoint < y2);
915
    }
916
    
917
    private void drawFoldLine(Graphics2D g2d, boolean active, int x1, int y1, int x2, int y2) {
918
        Stroke origStroke = g2d.getStroke();
919
        g2d.setStroke(getStroke(origStroke, active));
920
        g2d.drawLine(x1, y1, x2, y2);
921
        g2d.setStroke(origStroke);
922
    }
923
    
924
    protected @Override void paintComponent(Graphics g) {
925
        if (!enabled) {
926
            return;
927
        }
928
        
929
        Rectangle clip = getVisibleRect();//g.getClipBounds();
930
        visibleMarks.clear();
931
        
932
        Coloring coloring = getColoring();
933
        g.setColor(coloring.getBackColor());
934
        g.fillRect(clip.x, clip.y, clip.width, clip.height);
935
        g.setColor(coloring.getForeColor());
936
937
        AbstractDocument adoc = (AbstractDocument)component.getDocument();
938
        adoc.readLock();
939
        try {
940
            List<? extends PaintInfo> ps = getPaintInfo(clip);
941
            Font defFont = coloring.getFont();
942
            int markSize = getMarkSize(g);
943
            int halfMarkSize = markSize / 2;
944
            int markX = (defFont.getSize() - markSize) / 2; // x position of mark rectangle
945
            int plusGap = (int)Math.round(markSize / 3.8); // distance between mark rectangle vertical side and start/end of minus sign
946
            int lineX = markX + halfMarkSize; // x position of the centre of mark
947
948
            LOG.fine("CFSBar: PAINT START ------\n");
949
            int descent = g.getFontMetrics(defFont).getDescent();
950
            PaintInfo previousInfo = null;
951
            Graphics2D g2d = (Graphics2D)g;
952
            LOG.log(Level.FINEST, "MousePoint: {0}", mousePoint);
953
954
            for(PaintInfo paintInfo : ps) {
955
                boolean isFolded = paintInfo.isCollapsed();
956
                int y = paintInfo.getPaintY();
957
                int height = paintInfo.getPaintHeight();
958
                int markY = y + descent; // y position of mark rectangle
959
                int paintOperation = paintInfo.getPaintOperation();
960
961
                if (previousInfo == null) {
962
                    if (paintInfo.hasLineIn()) {
963
                        if (LOG.isLoggable(Level.FINE)) {
964
                            LOG.fine("prevInfo=NULL; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
965
                        }
966
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, clip.y, lineX, y);
967
                    }
968
                } else {
969
                    if (previousInfo.hasLineOut() || paintInfo.hasLineIn()) {
970
                        // Draw middle vertical line
971
                        int prevY = previousInfo.getPaintY();
972
                        if (LOG.isLoggable(Level.FINE)) {
973
                            LOG.log(Level.FINE, "prevInfo={0}; y=" + y + ", PI:" + paintInfo + "\n", previousInfo); // NOI18N
974
                        }
975
                        drawFoldLine(g2d, previousInfo.lineOutActive || paintInfo.lineInActive, lineX, prevY + previousInfo.getPaintHeight(), lineX, y);
976
                    }
977
                }
978
979
                if (paintInfo.hasSign()) {
980
                    g.drawRect(markX, markY, markSize, markSize);
981
                    g.drawLine(plusGap + markX, markY + halfMarkSize, markSize + markX - plusGap, markY + halfMarkSize);
982
                    String opStr = (paintOperation == PAINT_MARK) ? "PAINT_MARK" : "SINGLE_PAINT_MARK"; // NOI18N
983
                    if (isFolded) {
984
                        if (LOG.isLoggable(Level.FINE)) {
985
                            LOG.fine(opStr + ": folded; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
986
                        }
987
                        g.drawLine(lineX, markY + plusGap, lineX, markY + markSize - plusGap);
988
                    }
989
                    if (paintOperation != SINGLE_PAINT_MARK) {
990
                        if (LOG.isLoggable(Level.FINE)) {
991
                            LOG.fine(opStr + ": non-single; y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
992
                        }
993
                    }
994
                    if (paintInfo.hasLineIn()) { //[PENDING]
995
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, markY);
996
                    }
997
                    if (paintInfo.hasLineOut()) {
998
                        // This is an error in case there's a next paint info at the same y which is an end mark
999
                        // for this mark (it must be cleared explicitly).
1000
                        drawFoldLine(g2d, paintInfo.lineOutActive, lineX, markY + markSize, lineX, y + height);
1001
                    }
1002
                    visibleMarks.add(new Mark(markX, markY, markSize, isFolded));
1003
1004
                } else if (paintOperation == PAINT_LINE) {
1005
                    if (LOG.isLoggable(Level.FINE)) {
1006
                        LOG.fine("PAINT_LINE: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1007
                    }
1008
                    // FIXME !!
1009
                    drawFoldLine(g2d, paintInfo.signActive, lineX, y, lineX, y + height );
1010
                } else if (paintOperation == PAINT_END_MARK) {
1011
                    if (LOG.isLoggable(Level.FINE)) {
1012
                        LOG.fine("PAINT_END_MARK: y=" + y + ", PI:" + paintInfo + "\n"); // NOI18N
1013
                    }
1014
                    if (previousInfo == null || y != previousInfo.getPaintY()) {
1015
                        drawFoldLine(g2d, paintInfo.lineInActive, lineX, y, lineX, y + height / 2);
1016
                        drawFoldLine(g2d, paintInfo.signActive, lineX, y + height / 2, lineX + halfMarkSize, y + height / 2);
1017
                        if (paintInfo.getInnerLevel() > 0) {//[PENDING]
1018
                            if (LOG.isLoggable(Level.FINE)) {
1019
                                LOG.fine("  PAINT middle-line\n"); // NOI18N
1020
                            }
1021
                            drawFoldLine(g2d, paintInfo.lineOutActive, lineX, y + height / 2, lineX, y + height);
1022
                        }
1023
                    }
1024
                }
1025
1026
                previousInfo = paintInfo;
1027
            }
1028
1029
            if (previousInfo != null &&
1030
                (previousInfo.getInnerLevel() > 0 ||
1031
                 (previousInfo.getPaintOperation() == PAINT_MARK && !previousInfo.isCollapsed()))
1032
            ) {
1033
                drawFoldLine(g2d, previousInfo.lineOutActive, 
1034
                        lineX, previousInfo.getPaintY() + previousInfo.getPaintHeight(), lineX, clip.y + clip.height);
1035
            }
1036
1037
        } catch (BadLocationException ble) {
1038
            LOG.log(Level.WARNING, null, ble);
1039
        } finally {
1040
            LOG.fine("CFSBar: PAINT END ------\n\n");
1041
            adoc.readUnlock();
1042
        }
1043
    }
1044
    
1045
    private static Object [] getFoldList(Fold parentFold, int start, int end) {
1046
        List<Fold> ret = new ArrayList<Fold>();
1047
1048
        int index = FoldUtilities.findFoldEndIndex(parentFold, start);
1049
        int foldCount = parentFold.getFoldCount();
1050
        int idxOfFirstFoldStartingInside = -1;
1051
        while (index < foldCount) {
1052
            Fold f = parentFold.getFold(index);
1053
            if (f.getStartOffset() <= end) {
1054
                ret.add(f);
1055
            } else {
1056
                break; // no more relevant folds
1057
            }
1058
            if (idxOfFirstFoldStartingInside == -1 && f.getStartOffset() >= start) {
1059
                idxOfFirstFoldStartingInside = ret.size() - 1;
1060
            }
1061
            index++;
1062
        }
1063
1064
        return new Object [] { ret, idxOfFirstFoldStartingInside != -1 ? idxOfFirstFoldStartingInside : ret.size() };
1065
    }
1066
1067
    /**
1068
     * This class should be never used by other code; will be made private
1069
     */
1070
    public class PaintInfo {
1071
        
1072
        int paintOperation;
1073
        /**
1074
         * level of the 1st marker on the line
1075
         */
1076
        int innerLevel;
1077
        
1078
        /**
1079
         * Y-coordinate of the cell
1080
         */
1081
        int paintY;
1082
        
1083
        /**
1084
         * Height of the paint cell
1085
         */
1086
        int paintHeight;
1087
        
1088
        /**
1089
         * State of the marker (+/-)
1090
         */
1091
        boolean isCollapsed;
1092
        
1093
        /**
1094
         * all markers on the line are collapsed
1095
         */
1096
        boolean allCollapsed;
1097
        int startOffset;
1098
        int endOffset;
1099
        /**
1100
         * nesting level of the last marker on the line
1101
         */
1102
        int outgoingLevel;
1103
        
1104
        /**
1105
         * Force incoming line (from above) to be present
1106
         */
1107
        boolean lineIn;
1108
        
1109
        /**
1110
         * Force outgoing line (down from marker) to be present
1111
         */
1112
        boolean lineOut;
1113
        
1114
        /**
1115
         * The 'incoming' (upper) line should be painted as active
1116
         */
1117
        boolean lineInActive;
1118
        
1119
        /**
1120
         * The 'outgoing' (down) line should be painted as active
1121
         */
1122
        boolean lineOutActive;
1123
        
1124
        /**
1125
         * The sign/marker itself should be painted as active
1126
         */
1127
        boolean signActive;
1128
        
1129
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, boolean isCollapsed, int startOffset, int endOffset){
1130
            this.paintOperation = paintOperation;
1131
            this.innerLevel = this.outgoingLevel = innerLevel;
1132
            this.paintY = paintY;
1133
            this.paintHeight = paintHeight;
1134
            this.isCollapsed = this.allCollapsed = isCollapsed;
1135
            this.startOffset = startOffset;
1136
            this.endOffset = endOffset;
1137
1138
            switch (paintOperation) {
1139
                case PAINT_MARK:
1140
                    lineIn = false;
1141
                    lineOut = true;
1142
                    outgoingLevel++;
1143
                    break;
1144
                case SINGLE_PAINT_MARK:
1145
                    lineIn = false;
1146
                    lineOut = false;
1147
                    break;
1148
                case PAINT_END_MARK:
1149
                    lineIn = true;
1150
                    lineOut = false;
1151
                    isCollapsed = true;
1152
                    allCollapsed = true;
1153
                    break;
1154
                case PAINT_LINE:
1155
                    lineIn = lineOut = true;
1156
                    break;
1157
            }
1158
        }
1159
        
1160
        /**
1161
         * Sets active flags on inidivual parts of the mark
1162
         * @param mark
1163
         * @param lineIn
1164
         * @param lineOut S
1165
         */
1166
        void markActive(boolean mark, boolean lineIn, boolean lineOut) {
1167
            this.signActive |= mark;
1168
            this.lineInActive |= lineIn;
1169
            this.lineOutActive |= lineOut;
1170
        }
1171
        
1172
        boolean hasLineIn() {
1173
            return lineIn || innerLevel > 0;
1174
        }
1175
        
1176
        boolean hasLineOut() {
1177
            return lineOut || outgoingLevel > 0 || (paintOperation != SINGLE_PAINT_MARK && !isAllCollapsed());
1178
        }
1179
1180
        public PaintInfo(int paintOperation, int innerLevel, int paintY, int paintHeight, int startOffset, int endOffset){
1181
            this(paintOperation, innerLevel, paintY, paintHeight, false, startOffset, endOffset);
1182
        }
1183
        
1184
        public int getPaintOperation(){
1185
            return paintOperation;
1186
        }
1187
        
1188
        public int getInnerLevel(){
1189
            return innerLevel;
1190
        }
1191
        
1192
        public int getPaintY(){
1193
            return paintY;
1194
        }
1195
        
1196
        public int getPaintHeight(){
1197
            return paintHeight;
1198
        }
1199
        
1200
        public boolean isCollapsed(){
1201
            return isCollapsed;
1202
        }
1203
        
1204
         boolean isAllCollapsed() {
1205
            return allCollapsed;
1206
        }
1207
        
1208
        public void setPaintOperation(int paintOperation){
1209
            this.paintOperation = paintOperation;
1210
        }
1211
        
1212
        public void setInnerLevel(int innerLevel){
1213
            this.innerLevel = innerLevel;
1214
        }
1215
        
1216
        public @Override String toString(){
1217
            StringBuffer sb = new StringBuffer("");
1218
            if (paintOperation == PAINT_MARK){
1219
                sb.append("PAINT_MARK"); // NOI18N
1220
            }else if (paintOperation == PAINT_LINE){
1221
                sb.append("PAINT_LINE"); // NOI18N
1222
            }else if (paintOperation == PAINT_END_MARK) {
1223
                sb.append("PAINT_END_MARK"); // NOI18N
1224
            }else if (paintOperation == SINGLE_PAINT_MARK) {
1225
                sb.append("SINGLE_PAINT_MARK");
1226
            }
1227
            sb.append(",L:").append(innerLevel).append("/").append(outgoingLevel); // NOI18N
1228
            sb.append(',').append(isCollapsed ? "C" : "E"); // NOI18N
1229
            sb.append(", start=").append(startOffset).append(", end=").append(endOffset);
1230
            sb.append(", lineIn=").append(lineIn).append(", lineOut=").append(lineOut);
1231
            return sb.toString();
1232
        }
1233
        
1234
        boolean hasSign() {
1235
            return paintOperation == PAINT_MARK || paintOperation == SINGLE_PAINT_MARK;
1236
        }
1237
        
1238
        
1239
        void mergeWith(PaintInfo prevInfo) {
1240
            if (prevInfo == null) {
1241
                return;
1242
            }
1243
1244
            int operation = this.paintOperation;
1245
            boolean lineIn = prevInfo.lineIn;
1246
            boolean lineOut = prevInfo.lineOut;
1247
            
1248
            LOG.log(Level.FINE, "Merging {0} with {1}: ", new Object[] { this, prevInfo });
1249
            if (prevInfo.getPaintOperation() == PAINT_END_MARK) {
1250
                // merge with start|single -> start mark + line-in
1251
                lineIn = true;
1252
            } else {
1253
                operation = PAINT_MARK;
1254
            }
1255
1256
            int level1 = Math.min(prevInfo.innerLevel, innerLevel);
1257
            int level2 = prevInfo.outgoingLevel;
1258
1259
            if (getPaintOperation() == PAINT_END_MARK 
1260
                && innerLevel == prevInfo.outgoingLevel) {
1261
                // if merging end marker at the last level, update to the new outgoing level
1262
                level2 = outgoingLevel;
1263
            } else if (!isCollapsed) {
1264
                level2 = Math.max(prevInfo.outgoingLevel, outgoingLevel);
1265
            }
1266
1267
            if (prevInfo.getInnerLevel() < getInnerLevel()) {
1268
                int paintFrom = Math.min(prevInfo.paintY, paintY);
1269
                int paintTo = Math.max(prevInfo.paintY + prevInfo.paintHeight, paintY + paintHeight);
1270
                // at least one collapsed -> paint plus sign
1271
                boolean collapsed = prevInfo.isCollapsed() || isCollapsed();
1272
                int offsetFrom = Math.min(prevInfo.startOffset, startOffset);
1273
                int offsetTo = Math.max(prevInfo.endOffset, endOffset);
1274
                
1275
                this.paintY = paintFrom;
1276
                this.paintHeight = paintTo - paintFrom;
1277
                this.isCollapsed = collapsed;
1278
                this.startOffset = offsetFrom;
1279
                this.endOffset = offsetTo;
1280
            }
1281
            this.paintOperation = operation;
1282
            this.allCollapsed = prevInfo.allCollapsed && allCollapsed;
1283
            this.innerLevel = level1;
1284
            this.outgoingLevel = level2;
1285
            this.lineIn |= lineIn;
1286
            this.lineOut |= lineOut;
1287
            
1288
            this.signActive |= prevInfo.signActive;
1289
            this.lineInActive |= prevInfo.lineInActive;
1290
            this.lineOutActive |= prevInfo.lineOutActive;
1291
            
1292
            LOG.log(Level.FINE, "Merged result: {0}", this);
1293
        }
1294
    }
1295
    
1296
    /** Keeps info of visible folding mark */
1297
    public class Mark{
1298
        public int x;
1299
        public int y;
1300
        public int size;
1301
        public boolean isFolded;
1302
        
1303
        public Mark(int x, int y, int size, boolean isFolded){
1304
            this.x = x;
1305
            this.y = y;
1306
            this.size = size;
1307
            this.isFolded = isFolded;
1308
        }
1309
    }
1310
    
1311
    private final class Listener extends MouseAdapter implements FoldHierarchyListener, DocumentListener, Runnable {
1312
    
1313
        public Listener(){
1314
        }
1315
1316
        // --------------------------------------------------------------------
1317
        // FoldHierarchyListener implementation
1318
        // --------------------------------------------------------------------
1319
1320
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
1321
            refresh();
1322
        }
1323
1324
        // --------------------------------------------------------------------
1325
        // DocumentListener implementation
1326
        // --------------------------------------------------------------------
1327
1328
        public void insertUpdate(DocumentEvent evt) {
1329
            if (!(evt instanceof BaseDocumentEvent)) return;
1330
1331
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1332
            if (bevt.getLFCount() > 0) { // one or more lines inserted
1333
                refresh();
1334
            }
1335
        }
1336
1337
        public void removeUpdate(DocumentEvent evt) {
1338
            if (!(evt instanceof BaseDocumentEvent)) return;
1339
1340
            BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
1341
            if (bevt.getLFCount() > 0) { // one or more lines removed
1342
                refresh();
1343
            }
1344
        }
1345
1346
        public void changedUpdate(DocumentEvent evt) {
1347
        }
1348
1349
        // --------------------------------------------------------------------
1350
        // MouseListener implementation
1351
        // --------------------------------------------------------------------
1352
1353
        @Override
1354
        public void mousePressed (MouseEvent e) {
1355
            Mark mark = getClickedMark(e);
1356
            if (mark!=null){
1357
                e.consume();
1358
                performAction(mark, (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) > 0);
1359
            }
1360
        }
1361
1362
        @Override
1363
        public void mouseClicked(MouseEvent e) {
1364
            // #102288 - missing event consuming caused quick doubleclicks to break
1365
            // fold expanding/collapsing and move caret to the particular line
1366
            if (e.getClickCount() > 1) {
1367
                LOG.log(Level.FINEST, "Mouse {0}click at {1}", new Object[] { e.getClickCount(), e.getY()});
1368
                Mark mark = getClickedMark(e);
1369
                try {
1370
                    performActionAt(mark, e.getY());
1371
                } catch (BadLocationException ex) {
1372
                    LOG.log(Level.WARNING, "Error during fold expansion using sideline", ex);
1373
                }
1374
            } else {
1375
                e.consume();
1376
            }
1377
        }
1378
1379
        private void refreshIfMouseOutside(Point pt) {
1380
            mousePoint = (int)pt.getY();
1381
            if (LOG.isLoggable(Level.FINEST)) {
1382
                if (mouseBoundary == null) {
1383
                    LOG.log(Level.FINEST, "Mouse boundary not set, refreshing: {0}", mousePoint);
1384
                } else {
1385
                    LOG.log(Level.FINEST, "Mouse {0} inside known mouse boundary: {1}-{2}", 
1386
                            new Object[] { mousePoint, mouseBoundary.y, mouseBoundary.getMaxY() });
1387
                }
1388
            }
1389
            if (mouseBoundary == null || mousePoint < mouseBoundary.y || mousePoint > mouseBoundary.getMaxY()) {
1390
                refresh();
1391
            }
1392
        }
1393
        
1394
        @Override
1395
        public void mouseMoved(MouseEvent e) {
1396
            refreshIfMouseOutside(e.getPoint());
1397
        }
1398
        
1399
        public void mouseEntered(MouseEvent e) {
1400
            refreshIfMouseOutside(e.getPoint());
1401
        }
1402
        
1403
        public void mouseExited(MouseEvent e) {
1404
            mousePoint = NO_MOUSE_POINT;
1405
            refresh();
1406
        }
1407
        
1408
1409
        // --------------------------------------------------------------------
1410
        // private implementation
1411
        // --------------------------------------------------------------------
1412
1413
        private Mark getClickedMark(MouseEvent e){
1414
            if (e == null || !SwingUtilities.isLeftMouseButton(e)) {
1415
                return null;
1416
            }
1417
            
1418
            int x = e.getX();
1419
            int y = e.getY();
1420
            for (Mark mark : visibleMarks) {
1421
                if (x >= mark.x && x <= (mark.x + mark.size) && y >= mark.y && y <= (mark.y + mark.size)) {
1422
                    return mark;
1423
                }
1424
            }
1425
            return null;
1426
        }
1427
1428
        private void refresh() {
1429
            SwingUtilities.invokeLater(this);
1430
        }
1431
1432
        @Override
1433
        public void run() {
1434
            repaint();
1435
        }
1436
    } // End of Listener class
1437
    
1438
    @Override
1439
    public AccessibleContext getAccessibleContext() {
1440
        if (accessibleContext == null) {
1441
            accessibleContext = new AccessibleJComponent() {
1442
                public @Override AccessibleRole getAccessibleRole() {
1443
                    return AccessibleRole.PANEL;
1444
                }
1445
            };
1446
            accessibleContext.setAccessibleName(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSN_CodeFoldingSideBar")); //NOI18N
1447
        accessibleContext.setAccessibleDescription(NbBundle.getMessage(CodeFoldingSideBar.class, "ACSD_CodeFoldingSideBar")); //NOI18N
1448
        }
1449
        return accessibleContext;
1450
    }
1451
1452
    private Coloring getColoring() {
1453
        if (attribs == null) {
1454
            if (fcsLookupResult == null) {
1455
                fcsLookupResult = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component))
1456
                        .lookupResult(FontColorSettings.class);
1457
                fcsLookupResult.addLookupListener(WeakListeners.create(LookupListener.class, fcsTracker, fcsLookupResult));
1458
            }
1459
            
1460
            FontColorSettings fcs = fcsLookupResult.allInstances().iterator().next();
1461
            AttributeSet attr = fcs.getFontColors(FontColorNames.CODE_FOLDING_BAR_COLORING);
1462
            if (attr == null) {
1463
                attr = fcs.getFontColors(FontColorNames.DEFAULT_COLORING);
1464
            } else {
1465
                attr = AttributesUtilities.createComposite(attr, fcs.getFontColors(FontColorNames.DEFAULT_COLORING));
1466
            }
1467
            attribs = attr;
1468
        }        
1469
        return Coloring.fromAttributeSet(attribs);
1470
    }
1471
    
1472
}
(-)a/editor.lib/src/org/netbeans/editor/GlyphGutter.java (-29 lines)
Lines 88-96 Link Here
88
import javax.swing.text.Element;
88
import javax.swing.text.Element;
89
import javax.swing.text.JTextComponent;
89
import javax.swing.text.JTextComponent;
90
import javax.swing.text.View;
90
import javax.swing.text.View;
91
import org.netbeans.api.editor.fold.FoldHierarchy;
92
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
93
import org.netbeans.api.editor.fold.FoldHierarchyListener;
94
import org.netbeans.api.editor.mimelookup.MimeLookup;
91
import org.netbeans.api.editor.mimelookup.MimeLookup;
95
import org.netbeans.api.editor.settings.EditorStyleConstants;
92
import org.netbeans.api.editor.settings.EditorStyleConstants;
96
import org.netbeans.api.editor.settings.FontColorNames;
93
import org.netbeans.api.editor.settings.FontColorNames;
Lines 182-190 Link Here
182
    /** Property change listener on AnnotationTypes changes */
179
    /** Property change listener on AnnotationTypes changes */
183
    private PropertyChangeListener annoTypesListener;
180
    private PropertyChangeListener annoTypesListener;
184
    private PropertyChangeListener editorUIListener;
181
    private PropertyChangeListener editorUIListener;
185
    private GlyphGutter.GlyphGutterFoldHierarchyListener glyphGutterFoldHierarchyListener;
186
    private GutterMouseListener gutterMouseListener;
182
    private GutterMouseListener gutterMouseListener;
187
    private FoldHierarchy foldHierarchy;
188
183
189
    private ColoringMap coloringMap;
184
    private ColoringMap coloringMap;
190
    private final PropertyChangeListener coloringMapListener = new PropertyChangeListener() {
185
    private final PropertyChangeListener coloringMapListener = new PropertyChangeListener() {
Lines 234-242 Link Here
234
        init();
229
        init();
235
        update();
230
        update();
236
        setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
231
        setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
237
        foldHierarchy = FoldHierarchy.get(eui.getComponent());
238
        glyphGutterFoldHierarchyListener = new GlyphGutterFoldHierarchyListener();
239
        foldHierarchy.addFoldHierarchyListener(glyphGutterFoldHierarchyListener);
240
        editorUIListener = new EditorUIListener();
232
        editorUIListener = new EditorUIListener();
241
        eui.addPropertyChangeListener(editorUIListener);
233
        eui.addPropertyChangeListener(editorUIListener);
242
        eui.getComponent().addPropertyChangeListener(editorUIListener);
234
        eui.getComponent().addPropertyChangeListener(editorUIListener);
Lines 1096-1119 Link Here
1096
        
1088
        
1097
    }
1089
    }
1098
1090
1099
    class GlyphGutterFoldHierarchyListener implements FoldHierarchyListener, Runnable {
1100
    
1101
        public GlyphGutterFoldHierarchyListener(){
1102
        }
1103
        
1104
        public @Override void foldHierarchyChanged(FoldHierarchyEvent evt) {
1105
            // Do not react to fold changes since fold expanding/collapsing should trigger
1106
            // corresponding view hierarchy changes covered by view hierarchy listener.
1107
1108
//            SwingUtilities.invokeLater(this);
1109
        }
1110
1111
        @Override
1112
        public void run() {
1113
            repaint();
1114
        }
1115
    }
1116
    
1117
    /** Listening to EditorUI to properly deinstall attached listeners */
1091
    /** Listening to EditorUI to properly deinstall attached listeners */
1118
    class EditorUIListener implements PropertyChangeListener{
1092
    class EditorUIListener implements PropertyChangeListener{
1119
        public @Override void propertyChange (PropertyChangeEvent evt) {
1093
        public @Override void propertyChange (PropertyChangeEvent evt) {
Lines 1126-1132 Link Here
1126
                            ((JTextComponent) evt.getOldValue()).removePropertyChangeListener(this);
1100
                            ((JTextComponent) evt.getOldValue()).removePropertyChangeListener(this);
1127
                        }
1101
                        }
1128
                        annos.removeAnnotationsListener(GlyphGutter.this);
1102
                        annos.removeAnnotationsListener(GlyphGutter.this);
1129
                        foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
1130
                        if (gutterMouseListener!=null){
1103
                        if (gutterMouseListener!=null){
1131
                            removeMouseListener(gutterMouseListener);
1104
                            removeMouseListener(gutterMouseListener);
1132
                            removeMouseMotionListener(gutterMouseListener);
1105
                            removeMouseMotionListener(gutterMouseListener);
Lines 1134-1141 Link Here
1134
                        if (annoTypesListener !=null){
1107
                        if (annoTypesListener !=null){
1135
                            AnnotationTypes.getTypes().removePropertyChangeListener(annoTypesListener);
1108
                            AnnotationTypes.getTypes().removePropertyChangeListener(annoTypesListener);
1136
                        }
1109
                        }
1137
                        foldHierarchy.removeFoldHierarchyListener(glyphGutterFoldHierarchyListener);
1138
                        foldHierarchy = null;
1139
                        editorUI = null;
1110
                        editorUI = null;
1140
                        annos = null;
1111
                        annos = null;
1141
                    }
1112
                    }
(-)a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineDocView.java (-201 / +2 lines)
Lines 48-67 Link Here
48
import java.awt.Rectangle;
48
import java.awt.Rectangle;
49
import java.awt.Shape;
49
import java.awt.Shape;
50
import java.beans.PropertyChangeListener;
50
import java.beans.PropertyChangeListener;
51
import java.util.ArrayList;
52
import java.util.Iterator;
53
import java.util.List;
54
import javax.swing.plaf.TextUI;
51
import javax.swing.plaf.TextUI;
55
import javax.swing.text.AbstractDocument;
52
import javax.swing.text.AbstractDocument;
56
import javax.swing.text.Element;
53
import javax.swing.text.Element;
57
import javax.swing.text.JTextComponent;
54
import javax.swing.text.JTextComponent;
58
import javax.swing.text.View;
55
import javax.swing.text.View;
59
import javax.swing.text.ViewFactory;
56
import javax.swing.text.ViewFactory;
60
import org.netbeans.api.editor.fold.Fold;
61
import org.netbeans.api.editor.fold.FoldHierarchy;
62
import org.netbeans.api.editor.fold.FoldUtilities;
63
import org.netbeans.api.editor.fold.FoldHierarchyEvent;
64
import org.netbeans.api.editor.fold.FoldHierarchyListener;
65
import org.netbeans.editor.BaseTextUI;
57
import org.netbeans.editor.BaseTextUI;
66
import org.netbeans.editor.EditorUI;
58
import org.netbeans.editor.EditorUI;
67
import org.netbeans.lib.editor.view.GapDocumentView;
59
import org.netbeans.lib.editor.view.GapDocumentView;
Lines 73-91 Link Here
73
 * @author Miloslav Metelka
65
 * @author Miloslav Metelka
74
 */
66
 */
75
public class DrawEngineDocView extends GapDocumentView
67
public class DrawEngineDocView extends GapDocumentView
76
implements FoldHierarchyListener, PropertyChangeListener {
68
implements PropertyChangeListener {
77
    
69
    
78
    private static final boolean debugRebuild
70
    private static final boolean debugRebuild
79
        = Boolean.getBoolean("netbeans.debug.editor.view.rebuild"); // NOI18N
71
        = Boolean.getBoolean("netbeans.debug.editor.view.rebuild"); // NOI18N
80
72
81
    private FoldHierarchy foldHierarchy;
82
    /** Editor UI listening to */
73
    /** Editor UI listening to */
83
    private EditorUI editorUI;
74
    private EditorUI editorUI;
84
    
75
    
85
    private Iterator collapsedFoldIterator;
86
    private Fold collapsedFold;
87
    private int collapsedFoldStartOffset;
76
    private int collapsedFoldStartOffset;
88
    private int collapsedFoldEndOffset;
89
    
77
    
90
    private boolean collapsedFoldsInPresentViews;
78
    private boolean collapsedFoldsInPresentViews;
91
    
79
    
Lines 104-111 Link Here
104
    public @Override void setParent(View parent) {
92
    public @Override void setParent(View parent) {
105
        if (parent != null) { // start listening
93
        if (parent != null) { // start listening
106
            JTextComponent component = (JTextComponent)parent.getContainer();
94
            JTextComponent component = (JTextComponent)parent.getContainer();
107
            foldHierarchy = FoldHierarchy.get(component);
108
            foldHierarchy.addFoldHierarchyListener(this);
109
            TextUI tui = component.getUI();
95
            TextUI tui = component.getUI();
110
            if (tui instanceof BaseTextUI){
96
            if (tui instanceof BaseTextUI){
111
                editorUI = ((BaseTextUI)tui).getEditorUI();
97
                editorUI = ((BaseTextUI)tui).getEditorUI();
Lines 118-125 Link Here
118
        super.setParent(parent);
104
        super.setParent(parent);
119
        
105
        
120
        if (parent == null) {
106
        if (parent == null) {
121
            foldHierarchy.removeFoldHierarchyListener(this);
122
            foldHierarchy = null;
123
            if (editorUI!=null){
107
            if (editorUI!=null){
124
                editorUI.removePropertyChangeListener(this);
108
                editorUI.removePropertyChangeListener(this);
125
                editorUI = null;
109
                editorUI = null;
Lines 128-238 Link Here
128
    }
112
    }
129
    
113
    
130
    protected void attachListeners(){
114
    protected void attachListeners(){
131
        if (foldHierarchy != null) {
132
        }
133
    }
134
    
135
    private FoldHierarchy getFoldHierarchy() {
136
        return foldHierarchy;
137
    }
115
    }
138
    
116
    
139
    protected @Override boolean useCustomReloadChildren() {
117
    protected @Override boolean useCustomReloadChildren() {
140
        return true;
118
        return true;
141
    }
119
    }
142
120
143
    /**
144
     * Find next collapsed fold in the given offset range.
145
     * @param lastCollapsedFold last collapsed fold returned by this method.
146
     * @param startOffset starting offset of the area in which the collapsed folds
147
     *  should be searched.
148
     * @param endOffset ending offset of the area in which the collapsed folds
149
     *  should be searched.
150
     */
151
    protected Fold nextCollapsedFold() {
152
        while (true) {
153
            Fold fold = collapsedFoldIterator.hasNext() ? (Fold)collapsedFoldIterator.next() : null;
154
155
            // Check whether the fold is not past the doc
156
            if (fold != null) {
157
                collapsedFoldStartOffset = fold.getStartOffset();
158
                collapsedFoldEndOffset = fold.getEndOffset();
159
                /* Ignore the empty folds as they would make up
160
                 * no visible view anyway.
161
                 * Although the fold hierarchy removes the empty views
162
                 * automatically it may happen that the document listener
163
                 * that the fold hierarchy attaches may not be notified yet.
164
                 */
165
                if (collapsedFoldStartOffset == collapsedFoldEndOffset) {
166
                    if (debugRebuild) {
167
                        /*DEBUG*/System.err.println(
168
                            "GapBoxView.nextCollapsedFold(): ignored empty fold " // NOI18N
169
                            + fold
170
                        );
171
                    }
172
                    continue; // skip empty fold
173
                }
174
175
                if (collapsedFoldEndOffset > getDocument().getLength()) {
176
                    /* The fold is past the end of the document.
177
                     * If a document is going to be switched in the component
178
                     * the view hierarchy may be notified sooner
179
                     * than fold hierarchy about that change which
180
                     * can lead to this state.
181
                     * That fold is ignored together with the rest of the folds
182
                     * that would follow it.
183
                     */
184
                    fold = null;
185
                }
186
            }
187
188
            if (fold != null) {
189
                collapsedFoldsInPresentViews = true;
190
            }
191
192
            return fold;
193
        }
194
    }
195
    
196
    /**
197
     * Extra initialization for custom reload of children.
198
     */
199
    protected void initCustomReloadChildren(FoldHierarchy hierarchy,
200
    int startOffset, int endOffset) {
201
        collapsedFoldIterator = FoldUtilities.collapsedFoldIterator(hierarchy, startOffset, endOffset);
202
        collapsedFold = nextCollapsedFold();
203
    }
204
205
    /**
206
     * Free any resources required for custom reload of children.
207
     */
208
    protected void finishCustomReloadChildren(FoldHierarchy hierarchy) {
209
        collapsedFoldIterator = null;
210
        collapsedFold = null;
211
    }
212
213
    protected @Override void customReloadChildren(int index, int removeLength, int startOffset, int endOffset) {
214
        // if removing all the views reset the flag
215
        if (index == 0 && removeLength == getViewCount()) {
216
            collapsedFoldsInPresentViews = false; // suppose there will be no folds in line views
217
        }
218
219
        FoldHierarchy hierarchy = getFoldHierarchy();
220
        // Assuming the document lock was already acquired
221
        if (hierarchy != null) {
222
            hierarchy.lock();
223
            try {
224
                initCustomReloadChildren(hierarchy, startOffset, endOffset);
225
226
                super.customReloadChildren(index, removeLength, startOffset, endOffset);
227
228
                finishCustomReloadChildren(hierarchy);
229
230
            } finally {
231
                hierarchy.unlock();
232
            }
233
        }
234
    }
235
        
236
    protected @Override View createCustomView(ViewFactory f,
121
    protected @Override View createCustomView(ViewFactory f,
237
    int startOffset, int maxEndOffset, int elementIndex) {
122
    int startOffset, int maxEndOffset, int elementIndex) {
238
        if (elementIndex == -1) {
123
        if (elementIndex == -1) {
Lines 243-336 Link Here
243
128
244
        Element elem = getElement();
129
        Element elem = getElement();
245
        Element lineElem = elem.getElement(elementIndex);
130
        Element lineElem = elem.getElement(elementIndex);
246
        boolean createCollapsed = (collapsedFold != null);
131
        view = f.create(lineElem);
247
248
        if (createCollapsed) { // collapsedFold != null
249
            int lineElemEndOffset = lineElem.getEndOffset();
250
            createCollapsed = (collapsedFoldStartOffset < lineElemEndOffset);
251
            if (createCollapsed) { // need to find end of collapsed area
252
                Element firstLineElem = lineElem;
253
                List foldAndEndLineElemList = new ArrayList();
254
255
                while (true) {
256
                    int _collapsedFoldEndOffset = collapsedFold.getEndOffset();
257
                    // Find line element index of the line in which the collapsed fold ends
258
                    while (_collapsedFoldEndOffset > lineElemEndOffset) {
259
                        elementIndex++;
260
                        lineElem = elem.getElement(elementIndex);
261
                        lineElemEndOffset = lineElem.getEndOffset();
262
                    }
263
264
                    foldAndEndLineElemList.add(collapsedFold);
265
                    foldAndEndLineElemList.add(lineElem);
266
267
                    collapsedFold = nextCollapsedFold();
268
269
                    // No more collapsed or next collapsed does not start on current line
270
                    if (collapsedFold == null || collapsedFoldStartOffset >= lineElemEndOffset) {
271
                        break;
272
                    }
273
                }
274
                
275
                // Create the multi-line-view with collapsed fold(s)
276
                view = new FoldMultiLineView(firstLineElem, foldAndEndLineElemList);
277
            }
278
        }
279
        
280
        if (!createCollapsed) {
281
            view = f.create(lineElem);
282
        }
283
     
284
        return view;
132
        return view;
285
    }            
133
    }            
286
134
287
    public void foldHierarchyChanged(FoldHierarchyEvent evt) {
288
        LockView lockView = LockView.get(this);
289
        lockView.lock();
290
        try {
291
            layoutLock();
292
            try {
293
                FoldHierarchy hierarchy = (FoldHierarchy)evt.getSource();
294
                if (hierarchy.getComponent().getDocument() != lockView.getDocument()) {
295
                    // Comonent already has a different document assigned
296
                    // so this view will be abandoned anyway => do not rebuild
297
                    // the current chilren because of this change
298
                    return;
299
                }
300
301
                boolean rebuildViews = true;
302
                int affectedStartOffset = evt.getAffectedStartOffset();
303
                int affectedEndOffset = evt.getAffectedEndOffset();
304
305
                // Check whether it is not a case when there were
306
                // no collapsed folds before and no collapsed folds now
307
                if (!collapsedFoldsInPresentViews) { // no collapsed folds previously
308
                    // TODO Could Integer.MAX_VALUE be used?
309
                    if (FoldUtilities.findCollapsedFold(hierarchy,
310
                        affectedStartOffset, affectedEndOffset) == null
311
                    ) { // no collapsed folds => no need to rebuild
312
                        rebuildViews = false;
313
                    }
314
                }
315
316
                if (rebuildViews) {
317
                    /**
318
                     * Check the affected offsets against the current document boundaries
319
                     */
320
                    int docLength = getDocument().getLength();
321
                    int rebuildStartOffset = Math.min(affectedStartOffset, docLength);
322
                    int rebuildEndOffset = Math.min(affectedEndOffset, docLength);
323
                    offsetRebuild(rebuildStartOffset, rebuildEndOffset);
324
                }
325
            } finally {
326
                updateLayout();
327
                layoutUnlock();
328
            }
329
        } finally {
330
            lockView.unlock();
331
        }
332
    }
333
334
    public @Override void paint(Graphics g, Shape allocation) {
135
    public @Override void paint(Graphics g, Shape allocation) {
335
        java.awt.Component c = getContainer();
136
        java.awt.Component c = getContainer();
336
        if (c instanceof javax.swing.text.JTextComponent){
137
        if (c instanceof javax.swing.text.JTextComponent){
(-)a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/DrawEngineFakeDocView.java (-6 lines)
Lines 45-51 Link Here
45
package org.netbeans.modules.editor.lib.drawing;
45
package org.netbeans.modules.editor.lib.drawing;
46
46
47
import javax.swing.text.Element;
47
import javax.swing.text.Element;
48
import org.netbeans.api.editor.fold.Fold;
49
48
50
/**
49
/**
51
 *  Fake view of the whole document supporting the code folding, operating from given startOffset
50
 *  Fake view of the whole document supporting the code folding, operating from given startOffset
Lines 83-93 Link Here
83
        }
82
        }
84
        
83
        
85
        @Override
84
        @Override
86
        protected Fold nextCollapsedFold() {
87
            return null; // simulate no collapsed folds
88
        }
89
        
90
        @Override
91
        protected void attachListeners(){
85
        protected void attachListeners(){
92
        }
86
        }
93
    
87
    
(-)a/editor.lib/src/org/netbeans/modules/editor/lib/drawing/FoldMultiLineView.java (-37 lines)
Lines 46-59 Link Here
46
46
47
import java.util.ArrayList;
47
import java.util.ArrayList;
48
import java.util.List;
48
import java.util.List;
49
import javax.swing.text.BadLocationException;
50
import javax.swing.text.Element;
49
import javax.swing.text.Element;
51
import javax.swing.text.JTextComponent;
50
import javax.swing.text.JTextComponent;
52
import javax.swing.text.Position;
53
import javax.swing.text.View;
51
import javax.swing.text.View;
54
import javax.swing.text.ViewFactory;
52
import javax.swing.text.ViewFactory;
55
import org.netbeans.api.editor.fold.Fold;
56
import org.netbeans.editor.BaseDocument;
57
import org.netbeans.lib.editor.view.GapMultiLineView;
53
import org.netbeans.lib.editor.view.GapMultiLineView;
58
54
59
/**
55
/**
Lines 109-148 Link Here
109
            //   begining of the first line
105
            //   begining of the first line
110
            int lastViewEndOffset = lineElem.getStartOffset();
106
            int lastViewEndOffset = lineElem.getStartOffset();
111
107
112
            int cnt = foldAndEndLineElemList.size();
113
            List childViews = new ArrayList();
108
            List childViews = new ArrayList();
114
            for (int i = 0; i < cnt; i++) {
115
                Fold fold = (Fold)foldAndEndLineElemList.get(i);
116
                int foldStartOffset = fold.getStartOffset();
117
                int foldEndOffset = fold.getEndOffset();
118
119
                BaseDocument doc = (BaseDocument) lineElem.getDocument();
120
                try {
121
                    if (foldStartOffset > lastViewEndOffset) { // need to insert intra-line fragment
122
                        View lineView = f.create(lineElem); // normal line view
123
                        View intraFrag = lineView.createFragment(lastViewEndOffset, foldStartOffset);
124
                        childViews.add(intraFrag);
125
                        lastViewEndOffset = foldStartOffset;
126
                    }
127
128
                    // Create collapsed view
129
                    Position viewStartPos = doc.createPosition(foldStartOffset);
130
                    Position viewEndPos = doc.createPosition(foldEndOffset);
131
                    CollapsedView collapsedView = new CollapsedView(lineElem,
132
                        viewStartPos, viewEndPos, fold.getDescription());
133
                    childViews.add(collapsedView);
134
                    lastViewEndOffset = foldEndOffset;
135
136
                } catch (BadLocationException e) {
137
                    throw new IllegalStateException("Failed to create view boundary positions"); // NOI18N
138
                }
139
140
                // Fetch line element where the fold ends
141
                i++;
142
                lineElem = (Element)foldAndEndLineElemList.get(i);
143
                lineElemEndOffset = lineElem.getEndOffset();
144
            }
145
146
            // Append ending fragment if necessary
109
            // Append ending fragment if necessary
147
            // asserted non-empty list => foldEndOffset populated
110
            // asserted non-empty list => foldEndOffset populated
148
            if (lastViewEndOffset < lineElemEndOffset) { // need ending fragment
111
            if (lastViewEndOffset < lineElemEndOffset) { // need ending fragment
(-)a/editor.mimelookup/manifest.mf (-1 / +1 lines)
Lines 1-7 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.editor.mimelookup/1
2
OpenIDE-Module: org.netbeans.modules.editor.mimelookup/1
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/mimelookup/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/mimelookup/Bundle.properties
4
OpenIDE-Module-Specification-Version: 1.30
4
OpenIDE-Module-Specification-Version: 1.31
5
OpenIDE-Module-Recommends: org.netbeans.spi.editor.mimelookup.MimeDataProvider
5
OpenIDE-Module-Recommends: org.netbeans.spi.editor.mimelookup.MimeDataProvider
6
AutoUpdate-Essential-Module: true
6
AutoUpdate-Essential-Module: true
7
7
(-)a/editor.mimelookup/nbproject/org-netbeans-modules-editor-mimelookup.sig (-2 / +3 lines)
Lines 1-9 Link Here
1
#Signature file v4.1
1
#Signature file v4.1
2
#Version 1.26.1
2
#Version 1.30
3
3
4
CLSS public abstract interface !annotation java.lang.Deprecated
4
CLSS public abstract interface !annotation java.lang.Deprecated
5
 anno 0 java.lang.annotation.Documented()
5
 anno 0 java.lang.annotation.Documented()
6
 anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
6
 anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
7
 anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE])
7
intf java.lang.annotation.Annotation
8
intf java.lang.annotation.Annotation
8
9
9
CLSS public java.lang.Object
10
CLSS public java.lang.Object
Lines 124-129 Link Here
124
meth public abstract <%0 extends java.lang.Object> {%%0} lookup(java.lang.Class<{%%0}>)
125
meth public abstract <%0 extends java.lang.Object> {%%0} lookup(java.lang.Class<{%%0}>)
125
meth public static org.openide.util.Lookup getDefault()
126
meth public static org.openide.util.Lookup getDefault()
126
supr java.lang.Object
127
supr java.lang.Object
127
hfds defaultLookup
128
hfds LOG,defaultLookup
128
hcls DefLookup,Empty
129
hcls DefLookup,Empty
129
130
(-)a/editor.mimelookup/src/org/netbeans/api/editor/mimelookup/MimePath.java (+74 lines)
Lines 532-537 Link Here
532
        return true;
532
        return true;
533
    }
533
    }
534
    
534
    
535
    /**
536
     * Returns the inherited Mime type.
537
     * For {@link #EMPTY}, returns {@code null}. For most other mime types, returns
538
     * {@code ""}. If the mime type derives from another one, such as text/ant+xml derives
539
     * from xml, the return value will be the base mime type (text/xml in the example case).
540
     * <p/>
541
     * For MimePaths that identified embedded content (more components on the MimePath),
542
     * the method returns the parent MIME of the last MIME type on the path
543
     * 
544
     * @return inherited mime type, or {@code null}, if no parent exists (for {@link #EMPTY})
545
     */
546
    public String getInheritedType() {
547
        if ("".equals(mimeType)) {
548
            return null;
549
        }
550
        MimePath lastType = (size() == 1) ? this : MimePath.parse(mimeType);
551
        List<String> inheritedPaths = lastType.getInheritedPaths(null, null);
552
        if (inheritedPaths.size() > 1) {
553
            return inheritedPaths.get(1);
554
        } else {
555
            return null;
556
        }
557
    }
558
    
559
    
560
    /**
561
     * Returns the included Mime paths.
562
     * For MimePath, which nests several MIME types (i.e. text/php/text/html/text/css), it enumerates
563
     * sub-paths so that a following element represents one level of nesting of the content.
564
     * For the example example, the return would be:
565
     * <ol>
566
     * <li>text/php/text/html/text/css -- the full path
567
     * <li>text/html/text/css -- outer content is removed
568
     * <li>text/css -- the mime type of the identified content itself
569
     * <li> (empty string)
570
     * </ol>
571
     * <p/>
572
     * If a MIME type on the path has a generic MIME type (i.e. text/x-ant+xml 
573
     * has a generic MIME type text/xml), that generic type will be inserted. For example,
574
     * for text/java/text/x-ant+xml/text/javascript, the result will list:
575
     * <ol>
576
     * <li>text/java/text/x-ant+xml/text/javascript, -- the full MimePath
577
     * <li>text/x-ant+xml/text/javascript -- a prefix
578
     * <li>text/xml/text/javascript -- ant+xml is generalized to xml
579
     * <li>text/javascript
580
     * <li>  (empty string)
581
     * </ol>
582
     * For all but {@link #EMPTY} MimePaths, the list contains at least one entry, and the last
583
     * entry is the {@link #EMPTY}. Note also, that the complete MimePath is always returned
584
     * as the 1st member of the list.
585
     * <p/>
586
     * The returned sequence of MimePaths is suitable for searching settings or services
587
     * for the (embedded) content whose type is described by MimePath as it is ordered from the
588
     * most specific to the least specific paths (including generalization) and always contains
589
     * the mime type of the identified contents. The last component ({@link ""}) represents 
590
     * default settings (services).
591
     * <p/>
592
     * Note that for MimePaths created from a mime type (not empty!) string, the 
593
     * <code>getInheritedPaths().get(1)</code> is a parent mime type. Either empty,
594
     * or the generalized MIME.
595
     * <p/>
596
     * The caller should not modify the returned List.
597
     * 
598
     * @return list of inherited Mime paths
599
     */
600
    public List<MimePath> getIncludedPaths() {
601
        List<String> paths = getInheritedPaths(null, null);
602
        List<MimePath> mpaths = new ArrayList<MimePath>(paths.size());
603
        for (String p : paths) {
604
            mpaths.add(MimePath.parse(p));
605
        }
606
        return mpaths;
607
    }
608
    
535
    // XXX: This is currently called from editor/settings/storage (SettingsProvider)
609
    // XXX: This is currently called from editor/settings/storage (SettingsProvider)
536
    // and editor/mimelookup/impl via reflection.
610
    // and editor/mimelookup/impl via reflection.
537
    // We will eventually make it friend API. In the meantime just
611
    // We will eventually make it friend API. In the meantime just
(-)a/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/MimePathLookup.java (-15 / +3 lines)
Lines 138-157 Link Here
138
        // See also http://www.netbeans.org/issues/show_bug.cgi?id=118099
138
        // See also http://www.netbeans.org/issues/show_bug.cgi?id=118099
139
139
140
        // Add lookups from deprecated MimeLookupInitializers
140
        // Add lookups from deprecated MimeLookupInitializers
141
        List<String> paths;
141
        List<MimePath> paths = mimePath.getIncludedPaths();
142
        try {
142
        for(MimePath mp : paths) {
143
            Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
144
            m.setAccessible(true);
145
            @SuppressWarnings("unchecked")
146
            List<String> ret = (List<String>) m.invoke(mimePath, null, null);
147
            paths = ret;
148
        } catch (Exception e) {
149
            LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
150
            paths = Collections.singletonList(mimePath.getPath());
151
        }
152
153
        for(String path : paths) {
154
            MimePath mp = MimePath.parse(path);
155
            Collection<? extends MimeLookupInitializer> initializers = mimeInitializers.allInstances();
143
            Collection<? extends MimeLookupInitializer> initializers = mimeInitializers.allInstances();
156
144
157
            for(int i = 0; i < mp.size(); i++) {
145
            for(int i = 0; i < mp.size(); i++) {
Lines 166-172 Link Here
166
154
167
            for(MimeLookupInitializer mli : initializers) {
155
            for(MimeLookupInitializer mli : initializers) {
168
                if (LOG.isLoggable(Level.FINE)) {
156
                if (LOG.isLoggable(Level.FINE)) {
169
                    LOG.fine("- Querying MimeLookupInitializer(" + path + "): " + mli); //NOI18N
157
                    LOG.fine("- Querying MimeLookupInitializer(" + mp.getPath() + "): " + mli); //NOI18N
170
                }
158
                }
171
                Lookup mimePathLookup = mli.lookup();
159
                Lookup mimePathLookup = mli.lookup();
172
                if (mimePathLookup != null) {
160
                if (mimePathLookup != null) {
(-)a/editor.mimelookup/test/unit/src/org/netbeans/api/editor/mimelookup/MimePathTest.java (+2 lines)
Lines 84-89 Link Here
84
        MimePath mpPrefix = mp.getPrefix(2);
84
        MimePath mpPrefix = mp.getPrefix(2);
85
        assertTrue("text/x-java/text/x-ant+xml".equals(mpPrefix.getPath()));
85
        assertTrue("text/x-java/text/x-ant+xml".equals(mpPrefix.getPath()));
86
        
86
        
87
        List<String> ll = mp.getInheritedPaths(null, null);
88
        
87
        // Force exceed size of the internal LRU cache and release the created and cached MPs
89
        // Force exceed size of the internal LRU cache and release the created and cached MPs
88
        for (int op = 0; op < 2; op++) {
90
        for (int op = 0; op < 2; op++) {
89
            for (int i = 0; i < 10; i++) {
91
            for (int i = 0; i < 10; i++) {
(-)a/editor.settings.storage/manifest.mf (-1 / +1 lines)
Lines 2-7 Link Here
2
OpenIDE-Module: org.netbeans.modules.editor.settings.storage/1
2
OpenIDE-Module: org.netbeans.modules.editor.settings.storage/1
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/settings/storage/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/settings/storage/Bundle.properties
4
OpenIDE-Module-Provides: org.netbeans.api.editor.settings.implementation
4
OpenIDE-Module-Provides: org.netbeans.api.editor.settings.implementation
5
OpenIDE-Module-Specification-Version: 1.37
5
OpenIDE-Module-Specification-Version: 1.38
6
OpenIDE-Module-Layer: org/netbeans/modules/editor/settings/storage/layer.xml
6
OpenIDE-Module-Layer: org/netbeans/modules/editor/settings/storage/layer.xml
7
AutoUpdate-Show-In-Client: false
7
AutoUpdate-Show-In-Client: false
(-)a/editor.settings.storage/nbproject/project.xml (-1 / +2 lines)
Lines 55-61 Link Here
55
                    <compile-dependency/>
55
                    <compile-dependency/>
56
                    <run-dependency>
56
                    <run-dependency>
57
                        <release-version>1</release-version>
57
                        <release-version>1</release-version>
58
                        <specification-version>1.0</specification-version>
58
                        <specification-version>1.31</specification-version>
59
                    </run-dependency>
59
                    </run-dependency>
60
                </dependency>
60
                </dependency>
61
                <dependency>
61
                <dependency>
Lines 176-181 Link Here
176
                <friend>org.netbeans.modules.jvi</friend>
176
                <friend>org.netbeans.modules.jvi</friend>
177
                <friend>org.netbeans.modules.languages</friend>
177
                <friend>org.netbeans.modules.languages</friend>
178
                <friend>org.netbeans.modules.options.editor</friend>
178
                <friend>org.netbeans.modules.options.editor</friend>
179
                <friend>org.netbeans.modules.editor.fold</friend>
179
                <friend>org.netbeans.modules.parsing.api</friend>
180
                <friend>org.netbeans.modules.parsing.api</friend>
180
                <package>org.netbeans.modules.editor.settings.storage.api</package>
181
                <package>org.netbeans.modules.editor.settings.storage.api</package>
181
                <package>org.netbeans.modules.editor.settings.storage.spi</package>
182
                <package>org.netbeans.modules.editor.settings.storage.spi</package>
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/EditorSettings.java (+19 lines)
Lines 48-53 Link Here
48
import java.util.Collection;
48
import java.util.Collection;
49
import java.util.Map;
49
import java.util.Map;
50
import java.util.Set;
50
import java.util.Set;
51
import java.util.prefs.Preferences;
51
import javax.swing.text.AttributeSet;
52
import javax.swing.text.AttributeSet;
52
import org.netbeans.modules.editor.settings.storage.EditorSettingsImpl;
53
import org.netbeans.modules.editor.settings.storage.EditorSettingsImpl;
53
import org.netbeans.modules.editor.settings.storage.MimeTypesTracker;
54
import org.netbeans.modules.editor.settings.storage.MimeTypesTracker;
Lines 321-324 Link Here
321
        String propertyName,
322
        String propertyName,
322
        PropertyChangeListener l
323
        PropertyChangeListener l
323
    );
324
    );
325
326
    /**
327
     * Provides a temporary storage for Preferences, which can be
328
     * flushed to the delegate.
329
     * The returned object collects changes made through the {@link Preferences}
330
     * interface, but does not write them to the 'delegate' instance immediately.
331
     * It writes the changes (adds new keys, changes existing ones, removes the
332
     * obsolete keys) during {@link Preferences#flush}.
333
     * <p/>
334
     * The token controls the lifecycle of the Preferences tree. If left unattended,
335
     * it will be destroyed when the 'token' object becomes GCed. Each token instance 
336
     * has its own full Preferences tree.
337
     * 
338
     * @param token token to control lifecycle and separate instances
339
     * @param delegate the backing Preferences
340
     * @return Preferences with temporary storage.
341
     */
342
    // public abstract MemoryPreferences getProxyPreferences(Object token, Preferences delegate);
324
}
343
}
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/MemoryPreferences.java (+171 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.settings.storage.api;
43
44
import java.util.prefs.Preferences;
45
import org.netbeans.modules.editor.settings.storage.preferences.InheritedPreferences;
46
import org.netbeans.modules.editor.settings.storage.preferences.ProxyPreferencesImpl;
47
48
/**
49
 * Preferences with a temporary storage, backed by another Preferences
50
 * object. The instance tracks modifications done through the
51
 * {@link Preferences} interface, but do not change the backing store
52
 * until {@link Preferences#flush} is called.
53
 * <p/>
54
 * The MemoryPreferences object serves as an accessor, and offers some
55
 * additional control for the Preferences tree. It should not be handed
56
 * away, only the creator who manages the lifecycle should possess
57
 * the MemoryPreferences instance. Other clients should be given just the
58
 * Preferences object from {@link #getPreferences}.
59
 * <p/>
60
 * The returned Preferences object implements {@link LocalPreferences} extension
61
 * interface.
62
 * <p/>
63
 * This implementation <b>does not support</b> sub-nodes.
64
 * 
65
 * @author sdedic
66
 * @author Vita Stejskal
67
 */
68
public final class MemoryPreferences  {
69
    /**
70
     * Returns an instance of Preferences backed by the delegate.
71
     * A token is used to identify the desired Preferences set. As long as {@link #destroy} is not called,
72
     * calls which use the same token & delegate will receive the same Preferences objects (though their
73
     * MemoryPreferences may differ). The returned object implements {@link LocalPreferences}
74
     * interface.
75
     * 
76
     * @param token token that determines the tree of Preferences.
77
     * @param delegate
78
     * @return MemoryPreferences accessor instance
79
     */
80
    public static MemoryPreferences get(Object token, Preferences delegate) {
81
        return new MemoryPreferences(ProxyPreferencesImpl.getProxyPreferences(token, delegate));
82
    }
83
84
    /**
85
     * Creates Preferences, which delegates to both persistent storage and parent (inherited) preferences.
86
     * The persistent storage takes precedence over the parent. The {@link Preferences#remove} call is redefined
87
     * for this case to just remove the key-value from the 'delegate', so that 'parent' value (if any) can become
88
     * effective. Before {@link Preferences#flush}, the returned Preferences object delegates to both 'parent'
89
     * and 'delegate' so that effective values can be seen. The returned object implements {@link LocalPreferences}
90
     * interface.
91
     * 
92
     * @param token 
93
     * @param parent
94
     * @param delegate
95
     * @return 
96
     */
97
    public static MemoryPreferences getWithInherited(Object token, Preferences parent, Preferences delegate) {
98
        if (parent == null) {
99
            return get(token, delegate);
100
        }
101
        InheritedPreferences inh = new InheritedPreferences(parent, delegate);
102
        return new MemoryPreferences(ProxyPreferencesImpl.getProxyPreferences(token, inh));
103
    }
104
    
105
    /**
106
     * Provides the Preferences instance.
107
     * The instance will collect writes in memory, as described in {@link MemoryPreferences} doc.
108
     * 
109
     * @return instance of Preferences
110
     */
111
    public Preferences  getPreferences() {
112
        return prefInstance;
113
    }
114
    
115
    /**
116
     * Destroys the whole tree this Preferences belongs to.
117
     * Individual Preferences node will not be flushed or cleared, but will become
118
     * inaccessible from their token
119
     * 
120
     * @see {@link EditorSettings#getProxyPreferences}
121
     */
122
    public void destroy() {
123
        prefInstance.destroy();
124
    }
125
    
126
    /**
127
     * Suppresses events from this Preferences node.
128
     * During the Runnable execution, the Preferences node will not
129
     * fire any events.
130
     * 
131
     * @param r runnable to execute
132
     */
133
    public void runWithoutEvents(Runnable r) {
134
        try {
135
            prefInstance.silence();
136
            r.run();
137
        } finally {
138
            prefInstance.noise();
139
        }
140
    }
141
    
142
    /**
143
     * Checks whether the Preferences node is changed. 
144
     * Only value provided by the {@link #getPreferences} and values derived by call to {@link Preferences#node}
145
     * on that instance are supported. In other words, Preferences object from the tree managed by this
146
     * MemoryPreferences object. IllegalArgumentException can be thrown when an incompatible Preferences object
147
     * is used.
148
     * <p/>
149
     * True will be returned, if the Preferences object is dirty and not flushed.
150
     * 
151
     * @param pref preferences node to check
152
     * @return true, if the preferences was modified, and not flushed
153
     * @throws IllegalArgumentException if the pref node is not from the managed tree.
154
     */
155
    public boolean isDirty(Preferences pref) {
156
        if (!(pref instanceof ProxyPreferencesImpl)) {
157
            throw new IllegalArgumentException("Incompatible PreferencesImpl");
158
        }
159
        ProxyPreferencesImpl impl = (ProxyPreferencesImpl)pref;
160
        if (impl.node(prefInstance.absolutePath()) != prefInstance) {
161
            throw new IllegalArgumentException("The preferences tree root is not reachable");
162
        }
163
        return impl.isDirty();
164
    }
165
166
    private ProxyPreferencesImpl prefInstance;
167
    
168
    private MemoryPreferences(ProxyPreferencesImpl delegate) {
169
        this.prefInstance = delegate;
170
    }
171
}
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/api/OverridePreferences.java (+64 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.settings.storage.api;
43
44
/**
45
 * Mixin interface to detect if a value is inherited (defaulted) or not.
46
 * The interface is to be implemented on Preferences objects (e.g. Mime Preferences),
47
 * which support some sort of fallback, inheritance or default. It allows
48
 * clients to determine whether a preference key is defined at the level represented
49
 * by the Preferences object, or whether the value produced by {@link java.util.prefs.Preferences#get}
50
 * originates in some form of default or inherited values.
51
 *
52
 * @author sdedic
53
 */
54
public interface OverridePreferences {
55
    /**
56
     * Determines whether the value is defined locally.
57
     * If the value comes from an inherited or default set of values,
58
     * the method returns {@code false}.
59
     * 
60
     * @param key key to check
61
     * @return true, if the value is defined locally, false if inherited.
62
     */
63
    public boolean      isOverriden(String key);
64
}
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/fontscolors/CompositeFCS.java (-30 / +5 lines)
Lines 113-124 Link Here
113
            mimePath = mimePath.getPrefix(mimePath.size() - 1);
113
            mimePath = mimePath.getPrefix(mimePath.size() - 1);
114
        }
114
        }
115
115
116
        MimePath[] allPaths = computeInheritedMimePaths(mimePath);
116
        List<MimePath> allPaths = mimePath.getIncludedPaths();
117
        assert allPaths.length > 0 : "allPaths should always contain at least MimePath.EMPTY"; //NOI18N
117
        assert allPaths.size() > 0 : "allPaths should always contain at least MimePath.EMPTY"; //NOI18N
118
118
119
        this.allFcsi = new FontColorSettingsImpl[allPaths.length];
119
        this.allFcsi = new FontColorSettingsImpl[allPaths.size()];
120
        for (int i = 0; i < allPaths.length; i++) {
120
        for (int i = 0; i < allPaths.size(); i++) {
121
            allFcsi[i] = FontColorSettingsImpl.get(allPaths[i]);
121
            allFcsi[i] = FontColorSettingsImpl.get(allPaths.get(i));
122
        }
122
        }
123
123
124
        this.profile = profile;
124
        this.profile = profile;
Lines 275-305 Link Here
275
        System.out.println(sb.toString());
275
        System.out.println(sb.toString());
276
    }
276
    }
277
277
278
    private static MimePath[] computeInheritedMimePaths(MimePath mimePath) {
279
        List<String> paths = null;
280
        try {
281
            Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
282
            m.setAccessible(true);
283
            @SuppressWarnings("unchecked")
284
            List<String> ret = (List<String>) m.invoke(mimePath, null, null);
285
            paths = ret;
286
        } catch (Exception e) {
287
            LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
288
        }
289
290
        if (paths != null) {
291
            ArrayList<MimePath> mimePaths = new ArrayList<MimePath>(paths.size());
292
293
            for (String path : paths) {
294
                mimePaths.add(MimePath.parse(path));
295
            }
296
297
            return mimePaths.toArray(new MimePath[mimePaths.size()]);
298
        } else {
299
            return new MimePath[]{mimePath, MimePath.EMPTY};
300
        }
301
    }
302
303
    private Map<?, ?> getRenderingHints() {
278
    private Map<?, ?> getRenderingHints() {
304
        // This property was introduced in JDK1.6, see http://java.sun.com/javase/6/docs/api/java/awt/doc-files/DesktopProperties.html
279
        // This property was introduced in JDK1.6, see http://java.sun.com/javase/6/docs/api/java/awt/doc-files/DesktopProperties.html
305
        // We should probably also listen on the default toolkit for changes in this
280
        // We should probably also listen on the default toolkit for changes in this
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/InheritedPreferences.java (+258 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.settings.storage.preferences;
43
44
import java.util.Arrays;
45
import java.util.Collection;
46
import java.util.Collections;
47
import java.util.HashSet;
48
import java.util.Set;
49
import java.util.prefs.AbstractPreferences;
50
import java.util.prefs.BackingStoreException;
51
import java.util.prefs.PreferenceChangeEvent;
52
import java.util.prefs.PreferenceChangeListener;
53
import java.util.prefs.Preferences;
54
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
55
56
/**
57
 * Support for inheriting Preferences, while still working with stored ones.
58
 * 
59
 * This class solves the 'diamond inheritance', which is present during editing:
60
 * a MIME-type preferences derive from BOTH its persistent values (preferred) and
61
 * from the parent, whose actual values are potentially transient, and also persistent.
62
 * <p/>
63
 * Let us assume the following assignment:
64
 * <ul>
65
 * <li>TC (this current) = currently added/changed/removed values
66
 * <li>TP (this persistent) = persistent values, the getLocal() part of the Mime PreferencesImpl object
67
 * <li>PC (parent current) = currently added/changed/removed values of the parent
68
 * <li>PP (parent persistent) = persistent values, the getLocal() part of the parent MimePreferences
69
 * </ul>
70
 * The desired priority to find a value is: TC, TP, PC, PP. Because of {@link MemoryPreferences}, the
71
 * PC, PP (and potentially fallback to a grandparent) we already have, if we use the parent's {@link MemoryPreferences}
72
 * preferences as 'inherited'. The "TC" is handled by ProxyPreferences for this Mime node. In InheritedPreferences,
73
 * we must only inject the TP in between TC and the parent's preferences (PC, PP, ...)
74
 * <p/>
75
 * The object is intended to act as a ProxyPreferences delegate, all writes go directly to the stored
76
 * Mime preferences.
77
 * 
78
 * @author sdedic
79
 */
80
public final class InheritedPreferences extends AbstractPreferences implements PreferenceChangeListener, OverridePreferences  {
81
    /**
82
     * Preferences inherited, ie from a parent Mime type
83
     */
84
    private Preferences inherited;
85
    
86
    /**
87
     * Stored preferences, 
88
     */
89
    private Preferences stored;
90
    
91
    public InheritedPreferences(Preferences inherited, Preferences stored) {
92
        super(null, ""); // NOI18N
93
        this.inherited = inherited;
94
        if (!(stored instanceof OverridePreferences)) {
95
            throw new IllegalArgumentException();
96
        }
97
        this.stored = stored;
98
        
99
        inherited.addPreferenceChangeListener(this);
100
    }
101
102
    /* package */ Preferences getParent() {
103
        return inherited;
104
    }
105
106
    @Override
107
    protected void putSpi(String key, String value) {
108
        // do nothing, the AbstractPref then just fires an event
109
    }
110
111
    @Override
112
    public void put(String key, String value) {
113
        if (Boolean.TRUE != ignorePut.get()) {
114
            stored.put(key, value);
115
        }
116
        super.put(key, value);
117
    }
118
119
    @Override
120
    public void putInt(String key, int value) {
121
        if (Boolean.TRUE != ignorePut.get()) {
122
            stored.putInt(key, value);
123
        }
124
        super.putInt(key, value); 
125
    }
126
127
    @Override
128
    public void putLong(String key, long value) {
129
        if (Boolean.TRUE != ignorePut.get()) {
130
            stored.putLong(key, value);
131
        }
132
        super.putLong(key, value);
133
    }
134
135
    @Override
136
    public void putBoolean(String key, boolean value) {
137
        if (Boolean.TRUE != ignorePut.get()) {
138
            stored.putBoolean(key, value);
139
        }
140
        super.putBoolean(key, value);
141
    }
142
143
    @Override
144
    public void putFloat(String key, float value) {
145
        if (Boolean.TRUE != ignorePut.get()) {
146
            stored.putFloat(key, value);
147
        }
148
        super.putFloat(key, value); 
149
    }
150
151
    @Override
152
    public void putDouble(String key, double value) {
153
        if (Boolean.TRUE != ignorePut.get()) {
154
            stored.putDouble(key, value);
155
        }
156
        super.putDouble(key, value); 
157
    }
158
159
    @Override
160
    public void putByteArray(String key, byte[] value) {
161
        if (Boolean.TRUE != ignorePut.get()) {
162
            stored.putByteArray(key, value);
163
        }
164
        super.putByteArray(key, value);
165
    }
166
    
167
    private ThreadLocal<Boolean> ignorePut = new ThreadLocal<Boolean>();
168
169
    @Override
170
    public void preferenceChange(PreferenceChangeEvent evt) {
171
        if (!isOverriden(evt.getKey())) {
172
            // jusr refires an event
173
            ignorePut.set(true);
174
            try {
175
                put(evt.getKey(), evt.getNewValue());
176
            } finally {
177
                ignorePut.set(false);
178
            }
179
        }
180
    }
181
    
182
    /**
183
     * The value is defined locally, if the stored prefs define the value
184
     * locally. The parent definitions do not count. It is expected, that the
185
     * ProxyPreferences will report its local overrides as local in front of this
186
     * InheritedPreferences.
187
     * 
188
     * @param k
189
     * @return 
190
     */
191
    public @Override boolean isOverriden(String k) {
192
        if (stored instanceof OverridePreferences) {
193
            return ((OverridePreferences)stored).isOverriden(k);
194
        } else {
195
            return true;
196
        }
197
    }
198
    
199
    @Override
200
    protected String getSpi(String key) {
201
        // check the stored values
202
        OverridePreferences localStored = (OverridePreferences)stored;
203
        if (localStored.isOverriden(key)) {
204
            return stored.get(key, null);
205
        }
206
        // fall back to the inherited prefs, potentially its stored values etc.
207
        return inherited.get(key, null);
208
    }
209
210
    @Override
211
    protected void removeSpi(String key) {
212
        stored.remove(key);
213
    }
214
215
    @Override
216
    protected void removeNodeSpi() throws BackingStoreException {
217
        stored.removeNode();
218
    }
219
220
    @Override
221
    protected String[] keysSpi() throws BackingStoreException {
222
        Collection<String> names = new HashSet<String>();
223
        names.addAll(Arrays.asList(stored.keys()));
224
        names.addAll(Arrays.asList(inherited.keys()));
225
        return names.toArray(new String[names.size()]);
226
    }
227
228
    @Override
229
    protected String[] childrenNamesSpi() throws BackingStoreException {
230
        if (stored != null) {
231
            return stored.childrenNames();
232
        } else {
233
            return new String[0];
234
        }
235
    }
236
237
    @Override
238
    protected AbstractPreferences childSpi(String name) {
239
        Preferences storedNode = stored != null ? stored.node(name) : null;
240
        if (storedNode != null) {
241
            return new InheritedPreferences(null, storedNode);
242
        } else {
243
            return null;
244
        }
245
    }
246
247
    @Override
248
    protected void syncSpi() throws BackingStoreException {
249
        stored.sync();
250
    }
251
252
    @Override
253
    protected void flushSpi() throws BackingStoreException {
254
        stored.flush();
255
    }
256
    
257
    
258
}
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/PreferencesImpl.java (-15 / +17 lines)
Lines 63-68 Link Here
63
import java.util.prefs.Preferences;
63
import java.util.prefs.Preferences;
64
import org.netbeans.api.editor.mimelookup.MimePath;
64
import org.netbeans.api.editor.mimelookup.MimePath;
65
import org.netbeans.modules.editor.settings.storage.api.EditorSettingsStorage;
65
import org.netbeans.modules.editor.settings.storage.api.EditorSettingsStorage;
66
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
66
import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
67
import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
67
import org.openide.util.RequestProcessor;
68
import org.openide.util.RequestProcessor;
68
import org.openide.util.WeakListeners;
69
import org.openide.util.WeakListeners;
Lines 71-77 Link Here
71
 *
72
 *
72
 * @author vita
73
 * @author vita
73
 */
74
 */
74
public final class PreferencesImpl extends AbstractPreferences implements PreferenceChangeListener {
75
public final class PreferencesImpl extends AbstractPreferences implements PreferenceChangeListener, OverridePreferences {
75
76
76
    // the constant bellow is used in o.n.e.Settings!!
77
    // the constant bellow is used in o.n.e.Settings!!
77
    private static final String JAVATYPE_KEY_PREFIX = "nbeditor-javaType-for-legacy-setting_"; //NOI18N
78
    private static final String JAVATYPE_KEY_PREFIX = "nbeditor-javaType-for-legacy-setting_"; //NOI18N
Lines 154-159 Link Here
154
            }
155
            }
155
        }
156
        }
156
    }
157
    }
158
    
159
    public @Override boolean isOverriden(String key) {
160
        synchronized (lock) {
161
            String bareKey;
162
            if (key.startsWith(JAVATYPE_KEY_PREFIX)) {
163
                bareKey = key.substring(JAVATYPE_KEY_PREFIX.length());
164
            } else {
165
                bareKey = key;
166
            }
167
            return getLocal().containsKey(bareKey);
168
        }
169
    }
157
170
158
    public @Override void remove(String key) {
171
    public @Override void remove(String key) {
159
        synchronized(lock) {
172
        synchronized(lock) {
Lines 473-492 Link Here
473
    
486
    
474
    private Preferences getInherited() {
487
    private Preferences getInherited() {
475
        if (inherited == null && mimePath.length() > 0) {
488
        if (inherited == null && mimePath.length() > 0) {
476
            List<String> paths = null;
489
            String type = MimePath.parse(mimePath).getInheritedType();
477
            try {
490
            if (type != null) {
478
                Method m = MimePath.class.getDeclaredMethod("getInheritedPaths", String.class, String.class); //NOI18N
491
                inherited = get(MimePath.parse(type));
479
                m.setAccessible(true);
480
                @SuppressWarnings("unchecked")
481
                List<String> ret = (List<String>) m.invoke(MimePath.parse(mimePath), null, null);
482
                paths = ret;
483
            } catch (Exception e) {
484
                LOG.log(Level.WARNING, "Can't call org.netbeans.api.editor.mimelookup.MimePath.getInheritedPaths method.", e); //NOI18N
485
            }
486
            
487
            if (paths != null) {
488
                assert paths.size() > 1 : "Wrong getInheritedPaths result size: " + paths.size(); //NOI18N
489
                inherited = get(MimePath.parse(paths.get(1)));
490
            } else {
492
            } else {
491
                inherited = get(MimePath.EMPTY);
493
                inherited = get(MimePath.EMPTY);
492
            }
494
            }
(-)a/editor.settings.storage/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImpl.java (+1124 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 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
 * 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 2008 Sun Microsystems, Inc.
41
 */
42
43
package org.netbeans.modules.editor.settings.storage.preferences;
44
45
import java.io.IOException;
46
import java.io.OutputStream;
47
import java.lang.ref.Reference;
48
import java.lang.ref.WeakReference;
49
import java.util.ArrayList;
50
import java.util.Arrays;
51
import java.util.Collection;
52
import java.util.Collections;
53
import java.util.EventObject;
54
import java.util.HashMap;
55
import java.util.HashSet;
56
import java.util.LinkedList;
57
import java.util.List;
58
import java.util.Map;
59
import java.util.Set;
60
import java.util.WeakHashMap;
61
import java.util.logging.Level;
62
import java.util.logging.Logger;
63
import java.util.prefs.BackingStoreException;
64
import java.util.prefs.NodeChangeEvent;
65
import java.util.prefs.NodeChangeListener;
66
import java.util.prefs.PreferenceChangeEvent;
67
import java.util.prefs.PreferenceChangeListener;
68
import java.util.prefs.Preferences;
69
import javax.xml.bind.DatatypeConverter;
70
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
71
import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
72
import org.openide.util.WeakListeners;
73
74
/**
75
 * Preferences impl that stores changes locally, and propagates them upon flush().
76
 * The implementation is an adapted (former) implementation from org.netbeans.modules.options.indentation.ProxyPreferences.
77
 * The original was moved here, and adapted to work with 'diamond' double defaulting: 1st default is
78
 * the persistent Preferences object, where the changes will be finally propagated. The 2nd default
79
 * is the Preferences object for the MIMEtype parent (if it exists). Keys that do not exist
80
 * in the stored Preferences should delegate to the MIME parent. During editing, the MIME parent Preferences
81
 * may get also changed, so we cannot rely on delegation between stored Mime Preferences, but must
82
 * inject an additional path - see {@link InheritedPreferences}.
83
 *
84
 * @author sdedic
85
 * @author vita
86
 */
87
public final class ProxyPreferencesImpl extends Preferences implements PreferenceChangeListener, NodeChangeListener, 
88
        OverridePreferences {
89
90
    /**
91
     * Inherited preferences, for the case that key does not exist at our Node.
92
     * Special handling for diamond inheritance. 
93
     */
94
    private Preferences inheritedPrefs;
95
    
96
    public static ProxyPreferencesImpl getProxyPreferences(Object token, Preferences delegate) {
97
        return Tree.getTree(token, delegate).get(null, delegate.name(), delegate); //NOI18N
98
    }
99
    
100
    public boolean isDirty() {
101
        synchronized (tree.treeLock()) {
102
            return !(data.isEmpty() && removedKeys.isEmpty() && children.isEmpty() && removedChildren.isEmpty()) || removed;
103
        }
104
    }
105
106
    @Override
107
    public void put(String key, String value) {
108
        _put(key, value, String.class.getName());
109
    }
110
111
    @Override
112
    public String get(String key, String def) {
113
        synchronized (tree.treeLock()) {
114
            checkNotNull(key, "key"); //NOI18N
115
            checkRemoved();
116
            
117
            if (removedKeys.contains(key)) {
118
                if (LOG.isLoggable(Level.FINE)) {
119
                    LOG.fine("Key '" + key + "' removed, using default '" + def + "'"); //NOI18N
120
                }
121
                // removes will be flushed to the preferences, but now we need to see the defaults
122
                // that WILL become effective after flush of this object.
123
                if (inheritedPrefs != null) {
124
                    return inheritedPrefs.get(key, def);
125
                } else {
126
                    return def;
127
                }
128
            } else {
129
                TypedValue typedValue = data.get(key);
130
                if (typedValue != null) {
131
                    if (LOG.isLoggable(Level.FINE)) {
132
                        LOG.fine("Key '" + key + "' modified, local value '" + typedValue.getValue() + "'"); //NOI18N
133
                    }
134
                    return typedValue.getValue();
135
                } else if (delegate != null) {
136
                    String value = delegate.get(key, def);
137
                    if (LOG.isLoggable(Level.FINE)) {
138
                        LOG.fine("Key '" + key + "' undefined, original value '" + value + "'"); //NOI18N
139
                    }
140
                    return value;
141
                } else {
142
                    if (LOG.isLoggable(Level.FINE)) {
143
                        LOG.fine("Key '" + key + "' undefined, '" + name + "' is a new node, using default '" + def + "'"); //NOI18N
144
                    }
145
                    return def;
146
                }
147
            }
148
        }
149
    }
150
151
    @Override
152
    public void remove(String key) {
153
        EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag = null;
154
        
155
        synchronized (tree.treeLock()) {
156
            checkNotNull(key, "key"); //NOI18N
157
            checkRemoved();
158
            
159
            if (removedKeys.add(key)) {
160
                data.remove(key);
161
                bag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
162
                bag.addListeners(prefListeners);
163
                if (inheritedPrefs != null) {
164
                    bag.addEvent(new PreferenceChangeEvent(this, key, 
165
                            inheritedPrefs.get(key, null)));
166
                } else {
167
                    bag.addEvent(new PreferenceChangeEvent(this, key, null));
168
                }
169
            }
170
        }
171
172
        if (bag != null) {
173
            firePrefEvents(Collections.singletonList(bag));
174
        }
175
    }
176
177
    @Override
178
    public void clear() throws BackingStoreException {
179
        EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
180
        
181
        synchronized (tree.treeLock()) {
182
            checkRemoved();
183
184
            // Determine modified or added keys
185
            Set<String> keys = new HashSet<String>();
186
            keys.addAll(data.keySet());
187
            keys.removeAll(removedKeys);
188
            if (!keys.isEmpty()) {
189
                for(String key : keys) {
190
                    String value = delegate == null ? null : delegate.get(key, null);
191
                    bag.addEvent(new PreferenceChangeEvent(this, key, value));
192
                }
193
            }
194
195
            // Determine removed keys
196
            if (delegate != null) {
197
                for(String key : removedKeys) {
198
                    String value = delegate.get(key, null);
199
                    if (value != null) {
200
                        bag.addEvent(new PreferenceChangeEvent(this, key, value));
201
                    }
202
                }
203
            }
204
205
            // Initialize bag's listeners
206
            bag.addListeners(prefListeners);
207
208
            // Finally, remove the data
209
            data.clear();
210
            removedKeys.clear();
211
        }
212
        
213
        firePrefEvents(Collections.singletonList(bag));
214
    }
215
216
    @Override
217
    public void putInt(String key, int value) {
218
        _put(key, Integer.toString(value), Integer.class.getName());
219
    }
220
221
    @Override
222
    public int getInt(String key, int def) {
223
        String value = get(key, null);
224
        if (value != null) {
225
            try {
226
                return Integer.parseInt(value);
227
            } catch (NumberFormatException nfe) {
228
                // ignore
229
            }
230
        }
231
        return def;
232
    }
233
234
    @Override
235
    public void putLong(String key, long value) {
236
        _put(key, Long.toString(value), Long.class.getName());
237
    }
238
239
    @Override
240
    public long getLong(String key, long def) {
241
        String value = get(key, null);
242
        if (value != null) {
243
            try {
244
                return Long.parseLong(value);
245
            } catch (NumberFormatException nfe) {
246
                // ignore
247
            }
248
        }
249
        return def;
250
    }
251
252
    @Override
253
    public void putBoolean(String key, boolean value) {
254
        _put(key, Boolean.toString(value), Boolean.class.getName());
255
    }
256
257
    @Override
258
    public boolean getBoolean(String key, boolean def) {
259
        String value = get(key, null);
260
        if (value != null) {
261
            return Boolean.parseBoolean(value);
262
        } else {
263
            return def;
264
        }
265
    }
266
267
    @Override
268
    public void putFloat(String key, float value) {
269
        _put(key, Float.toString(value), Float.class.getName());
270
    }
271
272
    @Override
273
    public float getFloat(String key, float def) {
274
        String value = get(key, null);
275
        if (value != null) {
276
            try {
277
                return Float.parseFloat(value);
278
            } catch (NumberFormatException nfe) {
279
                // ignore
280
            }
281
        }
282
        return def;
283
    }
284
285
    @Override
286
    public void putDouble(String key, double value) {
287
        _put(key, Double.toString(value), Double.class.getName());
288
    }
289
290
    @Override
291
    public double getDouble(String key, double def) {
292
        String value = get(key, null);
293
        if (value != null) {
294
            try {
295
                return Double.parseDouble(value);
296
            } catch (NumberFormatException nfe) {
297
                // ignore
298
            }
299
        }
300
        return def;
301
    }
302
303
    @Override
304
    public void putByteArray(String key, byte[] value) {
305
        _put(key, DatatypeConverter.printBase64Binary(value), value.getClass().getName());
306
    }
307
308
    @Override
309
    public byte[] getByteArray(String key, byte[] def) {
310
        String value = get(key, null);
311
        if (value != null) {
312
            byte [] decoded = DatatypeConverter.parseBase64Binary(value);
313
            if (decoded != null) {
314
                return decoded;
315
            }
316
        }
317
        return def;
318
    }
319
320
    @Override
321
    public String[] keys() throws BackingStoreException {
322
        synchronized (tree.treeLock()) {
323
            checkRemoved();
324
            HashSet<String> keys = new HashSet<String>();
325
            if (delegate != null) {
326
                keys.addAll(Arrays.asList(delegate.keys()));
327
            }
328
            keys.addAll(data.keySet());
329
            keys.removeAll(removedKeys);
330
            return keys.toArray(new String [keys.size()]);
331
        }
332
    }
333
334
    @Override
335
    public String[] childrenNames() throws BackingStoreException {
336
        synchronized (tree.treeLock()) {
337
            checkRemoved();
338
            HashSet<String> names = new HashSet<String>();
339
            if (delegate != null) {
340
                names.addAll(Arrays.asList(delegate.childrenNames()));
341
            }
342
            names.addAll(children.keySet());
343
            names.removeAll(removedChildren);
344
            return names.toArray(new String [names.size()]);
345
        }
346
    }
347
348
    @Override
349
    public Preferences parent() {
350
        synchronized (tree.treeLock()) {
351
            checkRemoved();
352
            return parent;
353
        }
354
    }
355
356
    @Override
357
    public Preferences node(String pathName) {
358
        Preferences node;
359
        LinkedList<EventBag<NodeChangeListener, NodeChangeEvent>> events = new LinkedList<EventBag<NodeChangeListener, NodeChangeEvent>>();
360
361
        synchronized (tree.treeLock()) {
362
            checkNotNull(pathName, "pathName"); //NOI18N
363
            checkRemoved();
364
            node = node(pathName, true, events);
365
        }
366
367
        fireNodeEvents(events);
368
        return node;
369
    }
370
371
    @Override
372
    public boolean nodeExists(String pathName) throws BackingStoreException {
373
        synchronized (tree.treeLock()) {
374
            if (pathName.length() == 0) {
375
                return !removed;
376
            } else {
377
                checkRemoved();
378
                return node(pathName, false, null) != null;
379
            }
380
        }
381
    }
382
383
    @Override
384
    public void removeNode() throws BackingStoreException {
385
        synchronized (tree.treeLock()) {
386
            checkRemoved();
387
            ProxyPreferencesImpl p = parent;
388
            if (p != null) {
389
                p.removeChild(this);
390
            } else {
391
                throw new UnsupportedOperationException("Can't remove the root."); //NOI18N
392
            }
393
        }
394
    }
395
396
    @Override
397
    public String name() {
398
        return name;
399
    }
400
401
    @Override
402
    public String absolutePath() {
403
        synchronized (tree.treeLock()) {
404
            ProxyPreferencesImpl pp = parent;
405
            if (pp != null) {
406
                if (pp.parent == null) {
407
                    // pp is the root, we don't want two consecutive slashes in the path
408
                    return "/" + name(); //NOI18N
409
                } else {
410
                    return pp.absolutePath() + "/" + name(); //NOI18N
411
                }
412
            } else {
413
                return "/"; //NOI18N
414
            }
415
        }
416
    }
417
418
    @Override
419
    public boolean isUserNode() {
420
        synchronized (tree.treeLock()) {
421
            if (delegate != null) {
422
                return delegate.isUserNode();
423
            } else {
424
                ProxyPreferencesImpl pp = parent;
425
                if (pp != null) {
426
                    return pp.isUserNode();
427
                } else {
428
                    return true;
429
                }
430
            }
431
        }
432
    }
433
434
    @Override
435
    public String toString() {
436
        return (isUserNode() ? "User" : "System") + " Preference Node: " + absolutePath(); //NOI18N
437
    }
438
439
    @Override
440
    public void flush() throws BackingStoreException {
441
        synchronized (tree.treeLock()) {
442
            if (LOG.isLoggable(Level.FINE)) {
443
                LOG.fine("Flushing " + absolutePath());
444
            }
445
446
            checkRemoved();
447
            for(ProxyPreferencesImpl pp : children.values()) {
448
                pp.flush();
449
            }
450
451
            if (delegate == null) {
452
                ProxyPreferencesImpl proxyRoot = parent.node("/", false, null); //NOI18N
453
                assert proxyRoot != null : "Root must always exist"; //NOI18N
454
455
                Preferences delegateRoot = proxyRoot.delegate;
456
                assert delegateRoot != null : "Root must always have its corresponding delegate"; //NOI18N
457
458
                Preferences nueDelegate = delegateRoot.node(absolutePath());
459
                changeDelegate(nueDelegate);
460
            }
461
462
            delegate.removeNodeChangeListener(weakNodeListener);
463
            delegate.removePreferenceChangeListener(weakPrefListener);
464
            try {
465
                // remove all removed children
466
                for(String childName : removedChildren) {
467
                    if (delegate.nodeExists(childName)) {
468
                        delegate.node(childName).removeNode();
469
                    }
470
                }
471
472
                // write all valid key-value pairs
473
                for(String key : data.keySet()) {
474
                    if (!removedKeys.contains(key)) {
475
                        if (LOG.isLoggable(Level.FINE)) {
476
                            LOG.fine("Writing " + absolutePath() + "/" + key + "=" + data.get(key));
477
                        }
478
                        
479
                        TypedValue typedValue = data.get(key);
480
                        if (String.class.getName().equals(typedValue.getJavaType())) {
481
                            delegate.put(key, typedValue.getValue());
482
483
                        } else if (Integer.class.getName().equals(typedValue.getJavaType())) {
484
                            delegate.putInt(key, Integer.parseInt(typedValue.getValue()));
485
486
                        } else if (Long.class.getName().equals(typedValue.getJavaType())) {
487
                            delegate.putLong(key, Long.parseLong(typedValue.getValue()));
488
489
                        } else if (Boolean.class.getName().equals(typedValue.getJavaType())) {
490
                            delegate.putBoolean(key, Boolean.parseBoolean(typedValue.getValue()));
491
492
                        } else if (Float.class.getName().equals(typedValue.getJavaType())) {
493
                            delegate.putFloat(key, Float.parseFloat(typedValue.getValue()));
494
495
                        } else if (Double.class.getName().equals(typedValue.getJavaType())) {
496
                            delegate.putDouble(key, Double.parseDouble(typedValue.getValue()));
497
498
                        } else {
499
                            delegate.putByteArray(key, DatatypeConverter.parseBase64Binary(typedValue.getValue()));
500
                        }
501
                    }
502
                }
503
                data.clear();
504
505
                // remove all removed keys
506
                for(String key : removedKeys) {
507
                    if (LOG.isLoggable(Level.FINE)) {
508
                        LOG.fine("Removing " + absolutePath() + "/" + key);
509
                    }
510
                    delegate.remove(key);
511
                }
512
                removedKeys.clear();
513
            } finally {
514
                delegate.addNodeChangeListener(weakNodeListener);
515
                delegate.addPreferenceChangeListener(weakPrefListener);
516
            }
517
        }        
518
    }
519
520
    @Override
521
    public void sync() throws BackingStoreException {
522
        ArrayList<EventBag<PreferenceChangeListener, PreferenceChangeEvent>> prefEvents = new ArrayList<EventBag<PreferenceChangeListener, PreferenceChangeEvent>>();
523
        ArrayList<EventBag<NodeChangeListener, NodeChangeEvent>> nodeEvents = new ArrayList<EventBag<NodeChangeListener, NodeChangeEvent>>();
524
525
        synchronized (tree.treeLock()) {
526
            _sync(prefEvents, nodeEvents);
527
        }
528
529
        fireNodeEvents(nodeEvents);
530
        firePrefEvents(prefEvents);
531
    }
532
533
    @Override
534
    public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
535
        synchronized (tree.treeLock()) {
536
            prefListeners.add(pcl);
537
        }
538
    }
539
540
    @Override
541
    public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
542
        synchronized (tree.treeLock()) {
543
            prefListeners.remove(pcl);
544
        }
545
    }
546
547
    @Override
548
    public void addNodeChangeListener(NodeChangeListener ncl) {
549
        synchronized (tree.treeLock()) {
550
            nodeListeners.add(ncl);
551
        }
552
    }
553
554
    @Override
555
    public void removeNodeChangeListener(NodeChangeListener ncl) {
556
        synchronized (tree.treeLock()) {
557
            nodeListeners.remove(ncl);
558
        }
559
    }
560
561
    @Override
562
    public void exportNode(OutputStream os) throws IOException, BackingStoreException {
563
        throw new UnsupportedOperationException("exportNode not supported");
564
    }
565
566
    @Override
567
    public void exportSubtree(OutputStream os) throws IOException, BackingStoreException {
568
        throw new UnsupportedOperationException("exportSubtree not supported");
569
    }
570
571
    // ------------------------------------------------------------------------
572
    // PreferenceChangeListener implementation
573
    // ------------------------------------------------------------------------
574
575
    public void preferenceChange(PreferenceChangeEvent evt) {
576
        PreferenceChangeListener [] listeners;
577
        String nValue = evt.getNewValue();
578
        String k = evt.getKey();
579
        synchronized (tree.treeLock()) {
580
            if (removed || data.containsKey(k)) {
581
                return;
582
            }
583
            if (removedKeys.contains(k)) {
584
                if (inheritedPrefs == null) {
585
                    return;
586
                } else {
587
                    // if removed && there are inherited preferences, we must report the 'new value'
588
                    // from the inherited prefs, as the override in our preferences is not in effect now.
589
                    nValue = inheritedPrefs.get(k, null);
590
                }
591
            }
592
            listeners = prefListeners.toArray(new PreferenceChangeListener[prefListeners.size()]);
593
        }
594
595
        PreferenceChangeEvent myEvt = null;
596
        for(PreferenceChangeListener l : listeners) {
597
            if (myEvt == null) {
598
                myEvt = new PreferenceChangeEvent(this, k, nValue);
599
            }
600
            l.preferenceChange(myEvt);
601
        }
602
    }
603
604
    // ------------------------------------------------------------------------
605
    // NodeChangeListener implementation
606
    // ------------------------------------------------------------------------
607
608
    public void childAdded(NodeChangeEvent evt) {
609
        NodeChangeListener [] listeners;
610
        Preferences childNode;
611
612
        synchronized (tree.treeLock()) {
613
            String childName = evt.getChild().name();
614
            if (removed || removedChildren.contains(childName)) {
615
                return;
616
            }
617
618
            childNode = children.get(childName);
619
            if (childNode != null) {
620
                // swap delegates
621
                ((ProxyPreferencesImpl) childNode).changeDelegate(evt.getChild());
622
            } else {
623
                childNode = node(evt.getChild().name());
624
            }
625
            
626
            listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
627
        }
628
629
        NodeChangeEvent myEvt = null;
630
        for(NodeChangeListener l : listeners) {
631
            if (myEvt == null) {
632
                myEvt = new NodeChangeEvent(this, childNode);
633
            }
634
            l.childAdded(evt);
635
        }
636
    }
637
638
    public void childRemoved(NodeChangeEvent evt) {
639
        NodeChangeListener [] listeners;
640
        Preferences childNode;
641
642
        synchronized (tree.treeLock()) {
643
            String childName = evt.getChild().name();
644
            if (removed || removedChildren.contains(childName)) {
645
                return;
646
            }
647
648
            childNode = children.get(childName);
649
            if (childNode != null) {
650
                // swap delegates
651
                ((ProxyPreferencesImpl) childNode).changeDelegate(null);
652
            } else {
653
                // nobody has accessed the child yet
654
                return;
655
            }
656
            
657
            listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
658
        }
659
660
        NodeChangeEvent myEvt = null;
661
        for(NodeChangeListener l : listeners) {
662
            if (myEvt == null) {
663
                myEvt = new NodeChangeEvent(this, childNode);
664
            }
665
            l.childAdded(evt);
666
        }
667
    }
668
    
669
    // ------------------------------------------------------------------------
670
    // Other public implementation
671
    // ------------------------------------------------------------------------
672
673
    /**
674
     * Destroys whole preferences tree as if called on the root.
675
     */
676
    public void destroy() {
677
        synchronized (tree.treeLock()) {
678
            tree.destroy();
679
        }
680
    }
681
    
682
    public void silence() {
683
        synchronized (tree.treeLock()) {
684
            noEvents = true;
685
        }
686
    }
687
    
688
    public void noise() {
689
        synchronized (tree.treeLock()) {
690
            noEvents = false;
691
        }
692
    }
693
694
    @Override
695
    public boolean isOverriden(String key) {
696
        return data.containsKey(key);
697
    }
698
    
699
    // ------------------------------------------------------------------------
700
    // private implementation
701
    // ------------------------------------------------------------------------
702
703
    private static final Logger LOG = Logger.getLogger(ProxyPreferencesImpl.class.getName());
704
    
705
    private final ProxyPreferencesImpl parent;
706
    private final String name;
707
    private Preferences delegate;
708
    private final Tree tree;
709
    private boolean removed;
710
    
711
    private final Map<String, TypedValue> data = new HashMap<String, TypedValue>();
712
    private final Set<String> removedKeys = new HashSet<String>();
713
    private final Map<String, ProxyPreferencesImpl> children = new HashMap<String, ProxyPreferencesImpl>();
714
    private final Set<String> removedChildren = new HashSet<String>();
715
716
    private boolean noEvents = false;
717
    private PreferenceChangeListener weakPrefListener;
718
    private final Set<PreferenceChangeListener> prefListeners = new HashSet<PreferenceChangeListener>();
719
    private NodeChangeListener weakNodeListener;
720
    private final Set<NodeChangeListener> nodeListeners = new HashSet<NodeChangeListener>();
721
722
    private ProxyPreferencesImpl(ProxyPreferencesImpl parent, String name, Preferences delegate, Tree tree) {
723
        assert name != null;
724
        
725
        this.parent = parent;
726
        this.name = name;
727
        this.delegate = delegate;
728
        if (delegate instanceof InheritedPreferences) {
729
            this.inheritedPrefs = ((InheritedPreferences)delegate).getParent();
730
        }
731
        if (delegate != null) {
732
            assert name.equals(delegate.name());
733
734
            weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
735
            delegate.addPreferenceChangeListener(weakPrefListener);
736
            
737
            weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
738
            delegate.addNodeChangeListener(weakNodeListener);
739
        }
740
        this.tree = tree;
741
    }
742
743
    private void _put(String key, String value, String javaType) {
744
        EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag = null;
745
746
        synchronized (tree.treeLock()) {
747
            checkNotNull(key, "key"); //NOI18N
748
            checkNotNull(value, "value"); //NOI18N
749
            checkRemoved();
750
            
751
            String orig = get(key, null);
752
            if (orig == null || !orig.equals(value)) {
753
                if (LOG.isLoggable(Level.FINE)) {
754
                    LOG.fine("Overwriting '" + key + "' = '" + value + "'"); //NOI18N
755
                }
756
                
757
                data.put(key, new TypedValue(value, javaType));
758
                removedKeys.remove(key);
759
                
760
                bag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
761
                bag.addListeners(prefListeners);
762
                bag.addEvent(new PreferenceChangeEvent(this, key, value));
763
            }
764
        }
765
766
        if (bag != null) {
767
            firePrefEvents(Collections.singletonList(bag));
768
        }
769
    }
770
771
    private ProxyPreferencesImpl node(String pathName, boolean create, List<EventBag<NodeChangeListener, NodeChangeEvent>> events) {
772
        if (pathName.length() > 0 && pathName.charAt(0) == '/') { //NOI18N
773
            // absolute path, if this is not the root then find the root
774
            // and pass the call to it
775
            if (parent != null) {
776
                Preferences root = this;
777
                while (root.parent() != null) {
778
                    root = root.parent();
779
                }
780
                return ((ProxyPreferencesImpl) root).node(pathName, create, events);
781
            } else {
782
                // this is the root, change the pathName to a relative path and proceed
783
                pathName = pathName.substring(1);
784
            }
785
        }
786
787
        if (pathName.length() > 0) {
788
            String childName;
789
            String pathFromChild;
790
791
            int idx = pathName.indexOf('/'); //NOI18N
792
            if (idx != -1) {
793
                childName = pathName.substring(0, idx);
794
                pathFromChild = pathName.substring(idx + 1);
795
            } else {
796
                childName = pathName;
797
                pathFromChild = null;
798
            }
799
800
            ProxyPreferencesImpl child = children.get(childName);
801
            if (child == null) {
802
                if (removedChildren.contains(childName) && !create) {
803
                    // this child has been removed
804
                    return null;
805
                }
806
                
807
                Preferences childDelegate = null;
808
                try {
809
                    if (delegate != null && delegate.nodeExists(childName)) {
810
                        childDelegate = delegate.node(childName);
811
                    }
812
                } catch (BackingStoreException bse) {
813
                    // ignore
814
                }
815
816
                if (childDelegate != null || create) {
817
                    child = tree.get(this, childName, childDelegate);
818
                    children.put(childName, child);
819
                    removedChildren.remove(childName);
820
821
                    // fire event if we really created the new child node
822
                    if (childDelegate == null) {
823
                        EventBag<NodeChangeListener, NodeChangeEvent> bag = new EventBag<NodeChangeListener, NodeChangeEvent>();
824
                        bag.addListeners(nodeListeners);
825
                        bag.addEvent(new NodeChangeEventExt(this, child, false));
826
                        events.add(bag);
827
                    }
828
                } else {
829
                    // childDelegate == null && !create
830
                    return null;
831
                }
832
            } else {
833
                assert !child.removed;
834
            }
835
836
            return pathFromChild != null ? child.node(pathFromChild, create, events) : child;
837
        } else {
838
            return this;
839
        }
840
    }
841
842
    private void addChild(ProxyPreferencesImpl child) {
843
        ProxyPreferencesImpl pp = children.get(child.name());
844
        if (pp == null) {
845
            children.put(child.name(), child);
846
        } else {
847
            assert pp == child;
848
        }
849
    }
850
    
851
    private void removeChild(ProxyPreferencesImpl child) {
852
        assert child != null;
853
        assert children.get(child.name()) == child;
854
855
        child.nodeRemoved();
856
        children.remove(child.name());
857
        removedChildren.add(child.name());
858
    }
859
    
860
    private void nodeRemoved() {
861
        for(ProxyPreferencesImpl pp : children.values()) {
862
            pp.nodeRemoved();
863
        }
864
865
        data.clear();
866
        removedKeys.clear();
867
        children.clear();
868
        removedChildren.clear();
869
        tree.removeNode(this);
870
        
871
        removed = true;
872
    }
873
    
874
    private void checkNotNull(Object paramValue, String paramName) {
875
        if (paramValue == null) {
876
            throw new NullPointerException("The " + paramName + " must not be null");
877
        }
878
    }
879
880
    private void checkRemoved() {
881
        if (removed) {
882
            throw new IllegalStateException("The node '" + this + " has already been removed."); //NOI18N
883
        }
884
    }
885
886
    private void changeDelegate(Preferences nueDelegate) {
887
        if (delegate != null) {
888
            try {
889
                if (delegate.nodeExists("")) { //NOI18N
890
                    assert weakPrefListener != null;
891
                    assert weakNodeListener != null;
892
                    delegate.removePreferenceChangeListener(weakPrefListener);
893
                    delegate.removeNodeChangeListener(weakNodeListener);
894
                }
895
            } catch (BackingStoreException bse) {
896
                LOG.log(Level.WARNING, null, bse);
897
            }
898
        }
899
900
        delegate = nueDelegate;
901
        weakPrefListener = null;
902
        weakNodeListener = null;
903
        
904
        if (delegate != null) {
905
            weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
906
            delegate.addPreferenceChangeListener(weakPrefListener);
907
            
908
            weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
909
            delegate.addNodeChangeListener(weakNodeListener);
910
        }
911
    }
912
913
    private void _sync(
914
        List<EventBag<PreferenceChangeListener, PreferenceChangeEvent>> prefEvents, 
915
        List<EventBag<NodeChangeListener, NodeChangeEvent>> nodeEvents
916
    ) {
917
        // synchronize all children firts
918
        for(ProxyPreferencesImpl pp : children.values()) {
919
            pp._sync(prefEvents, nodeEvents);
920
        }
921
922
        // report all new children as removed
923
        EventBag<NodeChangeListener, NodeChangeEvent> nodeBag = new EventBag<NodeChangeListener, NodeChangeEvent>();
924
        nodeBag.addListeners(nodeListeners);
925
926
        for(ProxyPreferencesImpl pp : children.values()) {
927
            if (pp.delegate == null) {
928
                // new node that does not have corresponding node in the original hierarchy
929
                nodeBag.addEvent(new NodeChangeEventExt(this, pp, true));
930
            }
931
        }
932
933
        if (!nodeBag.getEvents().isEmpty()) {
934
            nodeEvents.add(nodeBag);
935
        }
936
937
        // report all modified keys
938
        if (delegate != null) {
939
            EventBag<PreferenceChangeListener, PreferenceChangeEvent> prefBag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
940
            prefBag.addListeners(prefListeners);
941
            prefEvents.add(prefBag);
942
943
            for(String key : data.keySet()) {
944
                prefBag.addEvent(new PreferenceChangeEvent(this, key, delegate.get(key, data.get(key).getValue())));
945
            }
946
        } // else there is no corresponding node in the orig hierarchy and this node
947
          // will be reported as removed
948
949
        // erase modified data
950
        for(NodeChangeEvent nce : nodeBag.getEvents()) {
951
            children.remove(nce.getChild().name());
952
        }
953
        data.clear();
954
    }
955
956
    private void firePrefEvents(List<EventBag<PreferenceChangeListener, PreferenceChangeEvent>> events) {
957
        if (noEvents) {
958
            return;
959
        }
960
        
961
        for(EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag : events) {
962
            for(PreferenceChangeEvent event : bag.getEvents()) {
963
                for(PreferenceChangeListener l : bag.getListeners()) {
964
                    try {
965
                        l.preferenceChange(event);
966
                    } catch (Throwable t) {
967
                        LOG.log(Level.WARNING, null, t);
968
                    }
969
                }
970
            }
971
        }
972
    }
973
974
    private void fireNodeEvents(List<EventBag<NodeChangeListener, NodeChangeEvent>> events) {
975
        if (noEvents) {
976
            return;
977
        }
978
        
979
        for(EventBag<NodeChangeListener, NodeChangeEvent> bag : events) {
980
            for(NodeChangeEvent event : bag.getEvents()) {
981
                for(NodeChangeListener l : bag.getListeners()) {
982
                    try {
983
                        if ((event instanceof NodeChangeEventExt) && ((NodeChangeEventExt) event).isRemovalEvent()) {
984
                            l.childRemoved(event);
985
                        } else {
986
                            l.childAdded(event);
987
                        }
988
                    } catch (Throwable t) {
989
                        LOG.log(Level.WARNING, null, t);
990
                    }
991
                }
992
            }
993
        }
994
    }
995
996
    /* test */ static final class Tree {
997
998
        public static Tree getTree(Object token, Preferences prefs) {
999
            synchronized (trees) {
1000
                // find all trees for the token
1001
                Map<Preferences, Tree> forest = trees.get(token);
1002
                if (forest == null) {
1003
                    forest = new HashMap<Preferences, Tree>();
1004
                    trees.put(token, forest);
1005
                }
1006
1007
                // find the tree for the prefs' root
1008
                Preferences root = prefs.node("/"); //NOI18N
1009
                Tree tree = forest.get(root);
1010
                if (tree == null) {
1011
                    tree = new Tree(token, root);
1012
                    forest.put(root, tree);
1013
                }
1014
1015
                return tree;
1016
            }
1017
        }
1018
1019
        /* test */ static final Map<Object, Map<Preferences, Tree>> trees = new WeakHashMap<Object, Map<Preferences, Tree>>();
1020
1021
        private final Preferences root;
1022
        private final Reference<?> tokenRef;
1023
        private final Map<String, ProxyPreferencesImpl> nodes = new HashMap<String, ProxyPreferencesImpl>();
1024
        
1025
        private Tree(Object token, Preferences root) {
1026
            this.root = root;
1027
            this.tokenRef = new WeakReference<Object>(token);
1028
        }
1029
1030
        public Object treeLock() {
1031
            return this;
1032
        }
1033
1034
        public ProxyPreferencesImpl get(ProxyPreferencesImpl parent, String name, Preferences delegate) {
1035
            if (delegate != null) {
1036
                assert name.equals(delegate.name());
1037
1038
                if (parent == null) {
1039
                    Preferences parentDelegate = delegate.parent();
1040
                    if (parentDelegate != null) {
1041
                        parent = get(null, parentDelegate.name(), parentDelegate);
1042
                    } // else delegate is the root
1043
                } else {
1044
                    // sanity check
1045
                    assert parent.delegate == delegate.parent();
1046
                }
1047
            }
1048
1049
            String absolutePath;
1050
            if (parent == null) {
1051
                absolutePath = "/"; //NOI18N
1052
            } else if (parent.parent() == null) {
1053
                absolutePath = "/" + name; //NOI18N
1054
            } else {
1055
                absolutePath = parent.absolutePath() + "/" + name; //NOI18N
1056
            }
1057
1058
            ProxyPreferencesImpl node = nodes.get(absolutePath);
1059
            if (node == null) {
1060
                node = new ProxyPreferencesImpl(parent, name, delegate, this);
1061
                nodes.put(absolutePath, node);
1062
1063
                if (parent != null) {
1064
                    parent.addChild(node);
1065
                }
1066
            } else {
1067
                assert !node.removed;
1068
            }
1069
1070
            return node;
1071
        }
1072
1073
        public void removeNode(ProxyPreferencesImpl node) {
1074
            String path = node.absolutePath();
1075
            assert nodes.containsKey(path);
1076
            ProxyPreferencesImpl pp = nodes.remove(path);
1077
        }
1078
1079
        public void destroy() {
1080
            synchronized (trees) {
1081
                Object token = tokenRef.get();
1082
                if (token != null) {
1083
                    trees.remove(token);
1084
                } // else the token has been GCed and therefore is not even in the trees map
1085
            }
1086
        }
1087
    } // End of Tree class
1088
1089
    private static final class EventBag<L, E extends EventObject> {
1090
        private final Set<L> listeners = new HashSet<L>();
1091
        private final Set<E> events = new HashSet<E>();
1092
1093
        public EventBag() {
1094
        }
1095
1096
        public Set<? extends L> getListeners() {
1097
            return listeners;
1098
        }
1099
1100
        public Set<? extends E> getEvents() {
1101
            return events;
1102
        }
1103
1104
        public void addListeners(Collection<? extends L> l) {
1105
            listeners.addAll(l);
1106
        }
1107
1108
        public void addEvent(E event) {
1109
            events.add(event);
1110
        }
1111
    } // End of EventBag class
1112
1113
    private static final class NodeChangeEventExt extends NodeChangeEvent {
1114
        private final boolean removal;
1115
        public NodeChangeEventExt(Preferences parent, Preferences child, boolean removal) {
1116
            super(parent, child);
1117
            this.removal = removal;
1118
        }
1119
1120
        public boolean isRemovalEvent() {
1121
            return removal;
1122
        }
1123
    } // End of NodeChangeEventExt class
1124
}
(-)a/editor.settings.storage/test/unit/src/org/netbeans/modules/editor/settings/storage/preferences/ProxyPreferencesImplTest.java (+544 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 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
 * 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 2008 Sun Microsystems, Inc.
41
 */
42
43
package org.netbeans.modules.editor.settings.storage.preferences;
44
45
import java.lang.ref.Reference;
46
import java.lang.ref.WeakReference;
47
import java.util.Arrays;
48
import java.util.Collections;
49
import java.util.HashMap;
50
import java.util.Map;
51
import java.util.prefs.AbstractPreferences;
52
import java.util.prefs.BackingStoreException;
53
import java.util.prefs.Preferences;
54
import static junit.framework.Assert.assertEquals;
55
import org.netbeans.junit.NbTestCase;
56
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;
57
import org.netbeans.modules.editor.settings.storage.api.MemoryPreferences;
58
59
/**
60
 *
61
 * @author vita
62
 */
63
public class ProxyPreferencesImplTest extends NbTestCase {
64
65
    public ProxyPreferencesImplTest(String name) {
66
        super(name);
67
    }
68
69
    public void testSimpleRead() {
70
        Preferences orig = Preferences.userRoot().node(getName());
71
        orig.put("key-1", "value-1");
72
73
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
74
        assertEquals("Wrong value", "value-1", test.get("key-1", null));
75
    }
76
    
77
    public void testSimpleWrite() {
78
        Preferences orig = Preferences.userRoot().node(getName());
79
        assertNull("Original contains value", orig.get("key-1", null));
80
81
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
82
        test.put("key-1", "xyz");
83
        assertEquals("Wrong value", "xyz", test.get("key-1", null));
84
    }
85
86
    public void testBase64() {
87
        Preferences orig = Preferences.userRoot().node(getName());
88
        assertNull("Original contains value", orig.get("key-1", null));
89
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
90
        test.putByteArray("key-1", "however you like it".getBytes());
91
        assertEquals("Wrong value", "however you like it", new String(test.getByteArray("key-1", null)));
92
    }
93
    
94
    public void testSimpleSync() throws BackingStoreException {
95
        Preferences orig = Preferences.userRoot().node(getName());
96
        assertNull("Original contains value", orig.get("key-1", null));
97
98
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
99
        assertNull("Test should not contains pair", orig.get("key-1", null));
100
101
        test.put("key-1", "xyz");
102
        assertEquals("Test doesn't contain new pair", "xyz", test.get("key-1", null));
103
104
        test.sync();
105
        assertNull("Test didn't rollback pair", test.get("key-1", null));
106
    }
107
108
    public void testSimpleFlush() throws BackingStoreException {
109
        Preferences orig = Preferences.userRoot().node(getName());
110
        assertNull("Original contains value", orig.get("key-1", null));
111
112
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
113
        assertNull("Test should not contains pair", orig.get("key-1", null));
114
115
        test.put("key-1", "xyz");
116
        assertEquals("Test doesn't contain new pair", "xyz", test.get("key-1", null));
117
118
        test.flush();
119
        assertEquals("Test should still contain the pair", "xyz", test.get("key-1", null));
120
        assertEquals("Test didn't flush the pair", "xyz", orig.get("key-1", null));
121
    }
122
    
123
    public void testSyncTree1() throws BackingStoreException {
124
        String [] origTree = new String [] {
125
            "CodeStyle/profile=GLOBAL",
126
        };
127
        String [] newTree = new String [] {
128
            "CodeStyle/text/x-java/tab-size=2",
129
            "CodeStyle/text/x-java/override-global-settings=true",
130
            "CodeStyle/text/x-java/expand-tabs=true",
131
            "CodeStyle/profile=PROJECT",
132
        };
133
134
        Preferences orig = Preferences.userRoot().node(getName());
135
        write(orig, origTree);
136
        checkContains(orig, origTree, "Orig");
137
        checkNotContains(orig, newTree, "Orig");
138
        
139
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
140
        checkEquals("Test should be the same as Orig", orig, test);
141
        
142
        write(test, newTree);
143
        checkContains(test, newTree, "Test");
144
145
        test.sync();
146
        checkContains(orig, origTree, "Orig");
147
        checkNotContains(orig, newTree, "Orig");
148
        checkContains(test, origTree, "Test");
149
        checkNotContains(test, newTree, "Test");
150
    }
151
152
    public void testFlushTree1() throws BackingStoreException {
153
        String [] origTree = new String [] {
154
            "CodeStyle/profile=GLOBAL",
155
        };
156
        String [] newTree = new String [] {
157
            "CodeStyle/text/x-java/tab-size=2",
158
            "CodeStyle/text/x-java/override-global-settings=true",
159
            "CodeStyle/text/x-java/expand-tabs=true",
160
            "CodeStyle/profile=PROJECT",
161
        };
162
163
        Preferences orig = Preferences.userRoot().node(getName());
164
        write(orig, origTree);
165
        checkContains(orig, origTree, "Orig");
166
        checkNotContains(orig, newTree, "Orig");
167
        
168
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
169
        checkEquals("Test should be the same as Orig", orig, test);
170
        
171
        write(test, newTree);
172
        checkContains(test, newTree, "Test");
173
174
        test.flush();
175
        checkEquals("Test didn't flush to Orig", test, orig);
176
    }
177
178
    public void testRemoveKey() throws BackingStoreException {
179
        Preferences orig = Preferences.userRoot().node(getName());
180
        orig.put("key-2", "value-2");
181
        assertNull("Original contains value", orig.get("key-1", null));
182
        assertEquals("Original doesn't contain value", "value-2", orig.get("key-2", null));
183
184
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
185
        test.put("key-1", "xyz");
186
        assertEquals("Wrong value", "xyz", test.get("key-1", null));
187
        
188
        test.remove("key-1");
189
        assertNull("Test contains removed key-1", test.get("key-1", null));
190
        
191
        test.remove("key-2");
192
        assertNull("Test contains removed key-2", test.get("key-2", null));
193
194
        test.flush();
195
        assertNull("Test flushed removed key-1", orig.get("key-1", null));
196
        assertNull("Test.flush did not remove removed key-2", orig.get("key-2", null));
197
    }
198
199
    public void testRemoveNode() throws BackingStoreException {
200
        Preferences orig = Preferences.userRoot().node(getName());
201
        Preferences origChild = orig.node("child");
202
203
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
204
        assertTrue("Test child shoculd exist", test.nodeExists("child"));
205
        Preferences testChild = test.node("child");
206
207
        testChild.removeNode();
208
        assertFalse("Removed test child should not exist", testChild.nodeExists(""));
209
        assertFalse("Removed test child should not exist in parent", test.nodeExists("child"));
210
211
        test.flush();
212
        assertFalse("Test.flush did not remove orig child", origChild.nodeExists(""));
213
        assertFalse("Test.flush did not remove orig child from parent", orig.nodeExists("child"));
214
    }
215
216
    public void testRemoveNodeCreateItAgain() throws BackingStoreException {
217
        Preferences orig = Preferences.userRoot().node(getName());
218
219
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
220
        Preferences testChild = test.node("child");
221
222
        testChild.removeNode();
223
        assertFalse("Removed test child should not exist", testChild.nodeExists(""));
224
        assertFalse("Removed test child should not exist in parent", test.nodeExists("child"));
225
226
        Preferences testChild2 = test.node("child");
227
        assertTrue("Recreated test child should exist", testChild2.nodeExists(""));
228
        assertTrue("Recreated test child should exist in parent", test.nodeExists("child"));
229
        assertNotSame("Recreated child must not be the same as the removed one", testChild2, testChild);
230
        assertEquals("Wrong childrenNames list", Arrays.asList(new String [] { "child" }), Arrays.asList(test.childrenNames()));
231
232
        try {
233
            testChild.get("key", null);
234
            fail("Removed test node should not be accessible");
235
        } catch (Exception e) {
236
        }
237
238
        try {
239
            testChild2.get("key", null);
240
        } catch (Exception e) {
241
            fail("Recreated test node should be accessible");
242
        }
243
244
    }
245
246
    public void testRemoveHierarchy() throws BackingStoreException {
247
        String [] origTree = new String [] {
248
            "R.CodeStyle.project.expand-tabs=true",
249
            "R.CodeStyle.project.indent-shift-width=6",
250
            "R.CodeStyle.project.spaces-per-tab=6",
251
            "R.CodeStyle.project.tab-size=7",
252
            "R.CodeStyle.project.text-limit-width=88",
253
            "R.CodeStyle.usedProfile=project",
254
            "R.text.x-ruby.CodeStyle.project.indent-shift-width=2",
255
            "R.text.x-ruby.CodeStyle.project.spaces-per-tab=2",
256
            "R.text.x-ruby.CodeStyle.project.tab-size=2",
257
        };
258
        String [] newTree = new String [] {
259
            "R.CodeStyle.project.expand-tabs=true",
260
            "R.CodeStyle.project.indent-shift-width=3",
261
            "R.CodeStyle.project.spaces-per-tab=3",
262
            "R.CodeStyle.project.tab-size=5",
263
            "R.CodeStyle.project.text-limit-width=77",
264
            "R.CodeStyle.usedProfile=project",
265
            "R.text.x-ruby.CodeStyle.project.indent-shift-width=2",
266
            "R.text.x-ruby.CodeStyle.project.spaces-per-tab=2",
267
            "R.text.x-ruby.CodeStyle.project.tab-size=2",
268
        };
269
270
        Preferences orig = Preferences.userRoot().node(getName());
271
        write(orig, origTree);
272
273
        checkContains(orig, origTree, "Orig");
274
275
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(this, orig);
276
        checkEquals("Test should be the same as Orig", orig, test);
277
278
        Preferences testRoot = test.node("R");
279
        removeAllKidsAndKeys(testRoot);
280
        
281
        write(test, newTree);
282
        checkContains(test, newTree, "Test");
283
284
        test.flush();
285
        checkEquals("Test didn't flush to Orig", test, orig);
286
    }
287
288
    public void testTreeGCed() throws BackingStoreException {
289
        String [] newTree = new String [] {
290
            "R.CodeStyle.project.expand-tabs=true",
291
            "R.CodeStyle.project.indent-shift-width=3",
292
            "R.CodeStyle.project.spaces-per-tab=3",
293
            "R.CodeStyle.project.tab-size=5",
294
            "R.CodeStyle.project.text-limit-width=77",
295
            "R.CodeStyle.usedProfile=project",
296
            "R.text.x-ruby.CodeStyle.project.indent-shift-width=2",
297
            "R.text.x-ruby.CodeStyle.project.spaces-per-tab=2",
298
            "R.text.x-ruby.CodeStyle.project.tab-size=2",
299
        };
300
301
        Preferences orig = Preferences.userRoot().node(getName());
302
303
        Object treeToken = new Object();
304
        Preferences test = ProxyPreferencesImpl.getProxyPreferences(treeToken, orig);
305
        write(test, newTree);
306
        checkContains(test, newTree, "Test");
307
308
        Reference<Object> treeTokenRef = new WeakReference<Object>(treeToken);
309
        Reference<Preferences> testRef = new WeakReference<Preferences>(test);
310
        treeToken = null;
311
        test = null;
312
        assertGC("Tree token was not GCed", treeTokenRef, Collections.singleton(this));
313
        // touch the WeakHashMap to expungeStaleEntries
314
        Object dummyToken = new Object();
315
        ProxyPreferencesImpl dummyPrefs = ProxyPreferencesImpl.getProxyPreferences(dummyToken, orig);
316
        assertGC("Test preferences were not GCed", testRef, Collections.singleton(this));
317
        
318
    }
319
    
320
    /**
321
     * Checks that a value not defined in delegate can be read from the parent prefs.
322
     * Checks that if the parent prefs also do not define the value, the
323
     * default from parameter is used.
324
     * 
325
     * @throws Exception 
326
     */
327
    public void testInheritedRead() throws Exception {
328
        Preferences stored = new MapPreferences();
329
        Preferences inherited = new MapPreferences();
330
        
331
        stored.put("key-1", "value-1");
332
        stored.put("key-3", "override");
333
        inherited.put("key-2", "value-2");
334
        inherited.put("key-3", "base");
335
336
        MemoryPreferences mem = MemoryPreferences.getWithInherited(this, inherited, stored);
337
        Preferences test = mem.getPreferences();
338
339
        assertEquals("Wrong value 1", "value-1", test.get("key-1", null));
340
        assertEquals("Wrong value 2", "value-2", test.get("key-2", "a"));
341
        assertEquals("Wrong value 3", "override", test.get("key-3", "a"));
342
        assertEquals("Wrong value 4", "value-4", test.get("key-4", "value-4"));
343
    }
344
    
345
    /**
346
     * Asserts that if a value is remove()d during editing, the inherited value
347
     * will be seen through. Also checks that the Preferences key is actually
348
     * deleted on flush() and the inherited preferences is not altered.
349
     */
350
    public void testSeeInheritedThroughRemoves() throws Exception {
351
        Preferences stored = new MapPreferences();
352
        Preferences inherited = new MapPreferences();
353
354
        stored.put("key", "value");
355
        inherited.put("key", "parentValue");
356
        
357
        MemoryPreferences mem = MemoryPreferences.getWithInherited(this, inherited, stored);
358
        Preferences test = mem.getPreferences();
359
360
        assertEquals("Does not see local value", "value", test.get("key", null));
361
        test.remove("key");
362
        
363
        assertEquals("Stored value changed prematurely", "value", stored.get("key", null));
364
        assertEquals("Inherited not seen", "parentValue", test.get("key", null));
365
        
366
        test.flush();
367
        assertNull("Stored value not erased", stored.get("key", null));
368
        assertEquals("Inherited changed", "parentValue", test.get("key", null));
369
    }
370
371
    // -----------------------------------------------------------------------
372
    // private implementation
373
    // -----------------------------------------------------------------------
374
    
375
    private static class MapPreferences extends AbstractPreferences implements OverridePreferences {
376
        
377
        private Map<String,Object> map = new HashMap<String, Object>();
378
379
        public MapPreferences() {
380
            super(null, ""); // NOI18N
381
        }
382
383
        @Override
384
        public boolean isOverriden(String key) {
385
            return map.containsKey(key);
386
        }
387
        
388
        protected void putSpi(String key, String value) {
389
            map.put(key, value);            
390
        }
391
392
        protected String getSpi(String key) {
393
            return (String)map.get(key);                    
394
        }
395
396
        protected void removeSpi(String key) {
397
            map.remove(key);
398
        }
399
400
        protected void removeNodeSpi() throws BackingStoreException {
401
            throw new UnsupportedOperationException("Not supported yet.");
402
        }
403
404
        protected String[] keysSpi() throws BackingStoreException {
405
            String array[] = new String[map.keySet().size()];
406
            return map.keySet().toArray( array );
407
        }
408
409
        protected String[] childrenNamesSpi() throws BackingStoreException {
410
            throw new UnsupportedOperationException("Not supported yet.");
411
        }
412
413
        protected AbstractPreferences childSpi(String name) {
414
            throw new UnsupportedOperationException("Not supported yet.");
415
        }
416
417
        protected void syncSpi() throws BackingStoreException {
418
            throw new UnsupportedOperationException("Not supported yet.");
419
        }
420
421
        protected void flushSpi() throws BackingStoreException {
422
            throw new UnsupportedOperationException("Not supported yet.");
423
        }
424
    }
425
426
    private void write(Preferences prefs, String[] tree) {
427
        for(String s : tree) {
428
            int equalIdx = s.lastIndexOf('=');
429
            assertTrue(equalIdx != -1);
430
            String value = s.substring(equalIdx + 1);
431
432
            String key;
433
            String nodePath;
434
            int slashIdx = s.lastIndexOf('/', equalIdx);
435
            if (slashIdx != -1) {
436
                key = s.substring(slashIdx + 1, equalIdx);
437
                nodePath = s.substring(0, slashIdx);
438
            } else {
439
                key = s.substring(0, equalIdx);
440
                nodePath = "";
441
            }
442
443
            Preferences node = prefs.node(nodePath);
444
            node.put(key, value);
445
        }
446
    }
447
448
    private void checkContains(Preferences prefs, String[] tree, String prefsId) throws BackingStoreException {
449
        for(String s : tree) {
450
            int equalIdx = s.lastIndexOf('=');
451
            assertTrue(equalIdx != -1);
452
            String value = s.substring(equalIdx + 1);
453
454
            String key;
455
            String nodePath;
456
            int slashIdx = s.lastIndexOf('/', equalIdx);
457
            if (slashIdx != -1) {
458
                key = s.substring(slashIdx + 1, equalIdx);
459
                nodePath = s.substring(0, slashIdx);
460
            } else {
461
                key = s.substring(0, equalIdx);
462
                nodePath = "";
463
            }
464
465
            assertTrue(prefsId + " doesn't contain node '" + nodePath + "'", prefs.nodeExists(nodePath));
466
            Preferences node = prefs.node(nodePath);
467
468
            String realValue = node.get(key, null);
469
            assertNotNull(prefsId + ", '" + nodePath + "' node doesn't contain key '" + key + "'", realValue);
470
            assertEquals(prefsId + ", '" + nodePath + "' node, '" + key + "' contains wrong value", value, realValue);
471
        }
472
    }
473
474
    private void checkNotContains(Preferences prefs, String[] tree, String prefsId) throws BackingStoreException {
475
        for(String s : tree) {
476
            int equalIdx = s.lastIndexOf('=');
477
            assertTrue(equalIdx != -1);
478
            String value = s.substring(equalIdx + 1);
479
480
            String key;
481
            String nodePath;
482
            int slashIdx = s.lastIndexOf('/', equalIdx);
483
            if (slashIdx != -1) {
484
                key = s.substring(slashIdx + 1, equalIdx);
485
                nodePath = s.substring(0, slashIdx);
486
            } else {
487
                key = s.substring(0, equalIdx);
488
                nodePath = "";
489
            }
490
491
            if (prefs.nodeExists(nodePath)) {
492
                Preferences node = prefs.node(nodePath);
493
                String realValue = node.get(key, null);
494
                if (realValue != null && realValue.equals(value)) {
495
                    fail(prefsId + ", '" + nodePath + "' node contains key '" + key + "' = '" + realValue + "'");
496
                }
497
            }
498
        }
499
    }
500
501
    private void dump(Preferences prefs, String prefsId) throws BackingStoreException {
502
        for(String key : prefs.keys()) {
503
            System.out.println(prefsId + ", " + prefs.absolutePath() + "/" + key + "=" + prefs.get(key, null));
504
        }
505
        for(String child : prefs.childrenNames()) {
506
            dump(prefs.node(child), prefsId);
507
        }
508
    }
509
510
    private void checkEquals(String msg, Preferences expected, Preferences test) throws BackingStoreException {
511
        assertEquals("Won't compare two Preferences with different absolutePath", expected.absolutePath(), test.absolutePath());
512
        
513
        // check the keys and their values
514
        for(String key : expected.keys()) {
515
            String expectedValue = expected.get(key, null);
516
            assertNotNull(msg + "; Expected:" + expected.absolutePath() + " has no '" + key + "'", expectedValue);
517
            
518
            String value = test.get(key, null);
519
            assertNotNull(msg + "; Test:" + test.absolutePath() + " has no '" + key + "'", value);
520
            assertEquals(msg + "; Test:" + test.absolutePath() + "/" + key + " has wrong value", expectedValue, value);
521
        }
522
523
        // check the children
524
        for(String child : expected.childrenNames()) {
525
            assertTrue(msg + "; Expected:" + expected.absolutePath() + " has no '" + child + "' subnode", expected.nodeExists(child));
526
            Preferences expectedChild = expected.node(child);
527
528
            assertTrue(msg + "; Test:" + test.absolutePath() + " has no '" + child + "' subnode", test.nodeExists(child));
529
            Preferences testChild = test.node(child);
530
531
            checkEquals(msg, expectedChild, testChild);
532
        }
533
    }
534
535
    private void removeAllKidsAndKeys(Preferences prefs) throws BackingStoreException {
536
        for(String kid : prefs.childrenNames()) {
537
            prefs.node(kid).removeNode();
538
        }
539
        for(String key : prefs.keys()) {
540
            prefs.remove(key);
541
        }
542
    }
543
544
}
(-)a/editor/src/org/netbeans/modules/editor/resources/layer.xml (+7 lines)
Lines 251-256 Link Here
251
                <attr name="position" intvalue="6000"/>
251
                <attr name="position" intvalue="6000"/>
252
                <attr name="scrollable" boolvalue="false"/>
252
                <attr name="scrollable" boolvalue="false"/>
253
            </file>
253
            </file>
254
            <file name="org-netbeans-modules-editor-fold-CodeFoldingSidebar.instance">
255
                <attr name="location" stringvalue="west"/>
256
                <!-- should be positioned after the current registrations, so they prevail -->
257
                <attr name="position" intvalue="1600"/>
258
                <attr name="instanceof" stringvalue="org.netbeans.editor.SideBarFactory"/>
259
                <attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldingSupport.foldingSidebarFactory"/>
260
            </file>
254
        </folder>
261
        </folder>
255
        
262
        
256
        <folder name="Toolbars">
263
        <folder name="Toolbars">
(-)a/html.editor/nbproject/project.xml (+4 lines)
Lines 123-128 Link Here
123
                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
123
                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
124
                    <build-prerequisite/>
124
                    <build-prerequisite/>
125
                    <compile-dependency/>
125
                    <compile-dependency/>
126
                    <run-dependency>
127
                        <release-version>1</release-version>
128
                        <specification-version>1.34</specification-version>
129
                    </run-dependency>
126
                </dependency>
130
                </dependency>
127
                <dependency>
131
                <dependency>
128
                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
132
                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
(-)a/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlFoldTypeProvider.java (+74 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.html.editor.gsf;
43
44
import java.util.Arrays;
45
import java.util.Collection;
46
import org.netbeans.api.editor.fold.FoldType;
47
import org.netbeans.api.editor.mimelookup.MimeRegistration;
48
import org.netbeans.api.editor.mimelookup.MimeRegistrations;
49
import org.netbeans.spi.editor.fold.FoldTypeProvider;
50
51
/**
52
 * @author sdedic
53
 */
54
@MimeRegistrations({
55
        @MimeRegistration(mimeType = "text/html", service = FoldTypeProvider.class),
56
        @MimeRegistration(mimeType = "text/xhtml", service = FoldTypeProvider.class)
57
})
58
public class HtmlFoldTypeProvider  implements FoldTypeProvider {
59
    private Collection<FoldType> TYPES = Arrays.asList(new FoldType[] {
60
        HtmlStructureScanner.TYPE_TAG,
61
        HtmlStructureScanner.TYPE_COMMENT
62
    });
63
64
    @Override
65
    public Collection getValues(Class type) {
66
        return type == FoldType.class ? TYPES : null;
67
    }
68
69
    @Override
70
    public boolean inheritable() {
71
        return false;
72
    }
73
    
74
}
(-)a/html.editor/src/org/netbeans/modules/html/editor/gsf/HtmlStructureScanner.java (-2 / +18 lines)
Lines 47-52 Link Here
47
import java.util.logging.Level;
47
import java.util.logging.Level;
48
import java.util.logging.Logger;
48
import java.util.logging.Logger;
49
import javax.swing.text.BadLocationException;
49
import javax.swing.text.BadLocationException;
50
import org.netbeans.api.editor.fold.FoldType;
50
import org.netbeans.editor.BaseDocument;
51
import org.netbeans.editor.BaseDocument;
51
import org.netbeans.editor.Utilities;
52
import org.netbeans.editor.Utilities;
52
import org.netbeans.modules.csl.api.OffsetRange;
53
import org.netbeans.modules.csl.api.OffsetRange;
Lines 58-69 Link Here
58
import org.netbeans.modules.parsing.api.Snapshot;
59
import org.netbeans.modules.parsing.api.Snapshot;
59
import org.netbeans.modules.web.common.api.Pair;
60
import org.netbeans.modules.web.common.api.Pair;
60
import org.openide.filesystems.FileObject;
61
import org.openide.filesystems.FileObject;
62
import org.openide.util.Exceptions;
63
import org.openide.util.NbBundle;
64
import static org.netbeans.modules.html.editor.gsf.Bundle.*;
61
65
62
/**
66
/**
63
 *
67
 *
64
 * @author mfukala@netbeans.org
68
 * @author mfukala@netbeans.org
65
 */
69
 */
66
public class HtmlStructureScanner implements StructureScanner {
70
public class HtmlStructureScanner implements StructureScanner {
71
    
72
    /**
73
     * Tag fold type. Overrides the default label.
74
     */
75
    @NbBundle.Messages("FT_Tag=Tags")
76
    public static final FoldType TYPE_TAG = FoldType.TAG.override(
77
            FT_Tag(), FoldType.TAG.getTemplate());
78
    
79
    /**
80
     * HTML comments
81
     */
82
    public static final FoldType TYPE_COMMENT = FoldType.COMMENT;
67
83
68
    private static final Logger LOGGER = Logger.getLogger(HtmlStructureScanner.class.getName());
84
    private static final Logger LOGGER = Logger.getLogger(HtmlStructureScanner.class.getName());
69
    private static final boolean LOG = LOGGER.isLoggable(Level.FINE);
85
    private static final boolean LOG = LOGGER.isLoggable(Level.FINE);
Lines 187-194 Link Here
187
        } finally {
203
        } finally {
188
            doc.readUnlock();
204
            doc.readUnlock();
189
        }
205
        }
190
        folds.put("tags", tags);
206
        folds.put(TYPE_TAG.code(), tags);
191
        folds.put("comments", comments);
207
        folds.put(TYPE_COMMENT.code(), comments);
192
208
193
        return folds;
209
        return folds;
194
    }
210
    }
(-)a/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml (+16 lines)
Lines 133-138 Link Here
133
133
134
                <folder name="FoldManager">
134
                <folder name="FoldManager">
135
                    <file name="org-netbeans-modules-csl-editor-fold-GsfFoldManagerFactory.instance"/>
135
                    <file name="org-netbeans-modules-csl-editor-fold-GsfFoldManagerFactory.instance"/>
136
                    <file name="HTMLDocReader.instance">
137
                        <attr name="instanceOf" stringvalue="org.netbeans.spi.editor.fold.ContentReader$Factory"/>
138
                        <attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldingSupport.contentReaderFactory"/>
139
                        <attr name="start" stringvalue="&lt;"/>
140
                        <attr name="terminator" stringvalue="[\s/>]"/>
141
                        <attr name="type" stringvalue="tag"/>
142
                        <attr name="prefix" stringvalue=""/>
143
                    </file>
136
                </folder>
144
                </folder>
137
                <folder name="SideBar">
145
                <folder name="SideBar">
138
                    <file name="org-netbeans-modules-csl-editor-GsfCodeFoldingSideBarFactory.instance">
146
                    <file name="org-netbeans-modules-csl-editor-GsfCodeFoldingSideBarFactory.instance">
Lines 268-273 Link Here
268
276
269
                <folder name="FoldManager">
277
                <folder name="FoldManager">
270
                    <file name="org-netbeans-modules-csl-editor-fold-GsfFoldManagerFactory.instance"/>
278
                    <file name="org-netbeans-modules-csl-editor-fold-GsfFoldManagerFactory.instance"/>
279
                    <file name="HTMLDocReader.instance">
280
                        <attr name="instanceOf" stringvalue="org.netbeans.spi.editor.fold.ContentReader$Factory"/>
281
                        <attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldingSupport.contentReaderFactory"/>
282
                        <attr name="start" stringvalue="&lt;"/>
283
                        <attr name="terminator" stringvalue="[\s/>]"/>
284
                        <attr name="type" stringvalue="tag"/>
285
                        <attr name="prefix" stringvalue=""/>
286
                    </file>
271
                </folder>
287
                </folder>
272
                <folder name="SideBar">
288
                <folder name="SideBar">
273
                    <file name="org-netbeans-modules-csl-editor-GsfCodeFoldingSideBarFactory.instance">
289
                    <file name="org-netbeans-modules-csl-editor-GsfCodeFoldingSideBarFactory.instance">
(-)a/java.editor.lib/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.java.editor.lib/1
2
OpenIDE-Module: org.netbeans.modules.java.editor.lib/1
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/java/editor/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/lib/java/editor/Bundle.properties
4
OpenIDE-Module-Specification-Version: 1.28
4
OpenIDE-Module-Specification-Version: 1.29
5
AutoUpdate-Show-In-Client: false
5
AutoUpdate-Show-In-Client: false
(-)a/java.editor.lib/nbproject/project.properties (+2 lines)
Lines 40-45 Link Here
40
# Version 2 license, then the option applies only if the new code is
40
# Version 2 license, then the option applies only if the new code is
41
# made subject to such option by the copyright holder.
41
# made subject to such option by the copyright holder.
42
42
43
javac.compilerargs=-Xlint -Xlint:-serial
44
javac.source=1.5
43
#javadoc.arch=${basedir}/arch/arch-java-editor-lib.xml
45
#javadoc.arch=${basedir}/arch/arch-java-editor-lib.xml
44
javadoc.title=Java Editor Library
46
javadoc.title=Java Editor Library
45
javadoc.apichanges=${basedir}/apichanges.xml
47
javadoc.apichanges=${basedir}/apichanges.xml
(-)a/java.editor.lib/nbproject/project.xml (-9 / +19 lines)
Lines 50-60 Link Here
50
            <code-name-base>org.netbeans.modules.java.editor.lib</code-name-base>
50
            <code-name-base>org.netbeans.modules.java.editor.lib</code-name-base>
51
            <module-dependencies>
51
            <module-dependencies>
52
                <dependency>
52
                <dependency>
53
                    <code-name-base>org.netbeans.modules.editor.deprecated.pre65formatting</code-name-base>
54
                    <build-prerequisite/>
55
                    <compile-dependency/>
56
                    <run-dependency>
57
                        <release-version>0-1</release-version>
58
                        <specification-version>1.0</specification-version>
59
                    </run-dependency>
60
                </dependency>
61
                <dependency>
53
                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
62
                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
54
                    <build-prerequisite/>
63
                    <build-prerequisite/>
55
                    <compile-dependency/>
64
                    <compile-dependency/>
56
                    <run-dependency>
65
                    <run-dependency>
57
                        <release-version>1</release-version>
66
                        <release-version>1</release-version>
67
                        <specification-version>1.34</specification-version>
58
                    </run-dependency>
68
                    </run-dependency>
59
                </dependency>
69
                </dependency>
60
                <dependency>
70
                <dependency>
Lines 76-81 Link Here
76
                    </run-dependency>
86
                    </run-dependency>
77
                </dependency>
87
                </dependency>
78
                <dependency>
88
                <dependency>
89
                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
90
                    <build-prerequisite/>
91
                    <compile-dependency/>
92
                    <run-dependency>
93
                        <release-version>1</release-version>
94
                        <specification-version>1.71</specification-version>
95
                    </run-dependency>
96
                </dependency>
97
                <dependency>
79
                    <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
98
                    <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
80
                    <build-prerequisite/>
99
                    <build-prerequisite/>
81
                    <compile-dependency/>
100
                    <compile-dependency/>
Lines 108-122 Link Here
108
                        <specification-version>8.0</specification-version>
127
                        <specification-version>8.0</specification-version>
109
                    </run-dependency>
128
                    </run-dependency>
110
                </dependency>
129
                </dependency>
111
                <dependency>
112
                    <code-name-base>org.netbeans.modules.editor.deprecated.pre65formatting</code-name-base>
113
                    <build-prerequisite/>
114
                    <compile-dependency/>
115
                    <run-dependency>
116
                        <release-version>0-1</release-version>
117
                        <specification-version>1.0</specification-version>
118
                    </run-dependency>
119
                </dependency>
120
            </module-dependencies>
130
            </module-dependencies>
121
            <public-packages>
131
            <public-packages>
122
                <package>org.netbeans.editor.ext.java</package>
132
                <package>org.netbeans.editor.ext.java</package>
(-)a/java.editor.lib/src/org/netbeans/editor/ext/java/JavaFoldManager.java (-7 / +26 lines)
Lines 44-54 Link Here
44
44
45
package org.netbeans.editor.ext.java;
45
package org.netbeans.editor.ext.java;
46
46
47
import org.netbeans.api.editor.fold.Fold;
48
import org.netbeans.api.editor.fold.FoldHierarchy;
49
import org.netbeans.api.editor.fold.FoldType;
47
import org.netbeans.api.editor.fold.FoldType;
50
import org.netbeans.spi.editor.fold.FoldManager;
48
import org.netbeans.spi.editor.fold.FoldManager;
51
49
50
import static org.netbeans.editor.ext.java.Bundle.*;
51
import org.openide.util.NbBundle;
52
52
/**
53
/**
53
 * Java fold maintainer creates and updates folds for java sources.
54
 * Java fold maintainer creates and updates folds for java sources.
54
 *
55
 *
Lines 58-70 Link Here
58
59
59
public abstract class JavaFoldManager implements FoldManager {
60
public abstract class JavaFoldManager implements FoldManager {
60
61
61
    public static final FoldType INITIAL_COMMENT_FOLD_TYPE = new FoldType("initial-comment"); // NOI18N
62
    public static final FoldType INITIAL_COMMENT_FOLD_TYPE = FoldType.INITIAL_COMMENT; // NOI18N
62
63
63
    public static final FoldType IMPORTS_FOLD_TYPE = new FoldType("imports"); // NOI18N
64
    @NbBundle.Messages("FoldType_Imports=Imports")
65
    public static final FoldType IMPORTS_FOLD_TYPE = FoldType.create("import", FoldType_Imports(), 
66
	     new org.netbeans.api.editor.fold.FoldTemplate(0, 0, "...")); // NOI18N
64
    
67
    
65
    public static final FoldType JAVADOC_FOLD_TYPE = new FoldType("javadoc"); // NOI18N
68
    @NbBundle.Messages("FoldType_Javadoc=Javadoc Comments")
69
    public static final FoldType JAVADOC_FOLD_TYPE = FoldType.DOCUMENTATION.derive("javadoc", FoldType_Javadoc(), 
70
	     new org.netbeans.api.editor.fold.FoldTemplate(3, 2, "/**...*/")); // NOI18N
66
71
67
    public static final FoldType CODE_BLOCK_FOLD_TYPE = new FoldType("code-block"); // NOI18N
72
    @NbBundle.Messages("FoldType_Methods=Methods")
73
    public static final FoldType CODE_BLOCK_FOLD_TYPE = FoldType.MEMBER.derive("method", FoldType_Methods(), 
74
	     new org.netbeans.api.editor.fold.FoldTemplate(1, 1, "{...}")); // NOI18N
75
    
76
    @NbBundle.Messages("FoldType_InnerClasses=Inner Classes")
77
    public static final FoldType INNERCLASS_TYPE = FoldType.NESTED.derive("innerclass", "Inner Classes", 
78
	     new org.netbeans.api.editor.fold.FoldTemplate(1, 1, "{...}")); // NOI18N
68
    
79
    
69
    private static final String IMPORTS_FOLD_DESCRIPTION = "..."; // NOI18N
80
    private static final String IMPORTS_FOLD_DESCRIPTION = "..."; // NOI18N
70
81
Lines 73-91 Link Here
73
    private static final String JAVADOC_FOLD_DESCRIPTION = "/**...*/"; // NOI18N
84
    private static final String JAVADOC_FOLD_DESCRIPTION = "/**...*/"; // NOI18N
74
    
85
    
75
    private static final String CODE_BLOCK_FOLD_DESCRIPTION = "{...}"; // NOI18N
86
    private static final String CODE_BLOCK_FOLD_DESCRIPTION = "{...}"; // NOI18N
76
    
87
88
    @Deprecated
77
    public static final FoldTemplate INITIAL_COMMENT_FOLD_TEMPLATE
89
    public static final FoldTemplate INITIAL_COMMENT_FOLD_TEMPLATE
78
        = new FoldTemplate(INITIAL_COMMENT_FOLD_TYPE, COMMENT_FOLD_DESCRIPTION, 2, 2);
90
        = new FoldTemplate(INITIAL_COMMENT_FOLD_TYPE, COMMENT_FOLD_DESCRIPTION, 2, 2);
79
91
92
    @Deprecated
80
    public static final FoldTemplate IMPORTS_FOLD_TEMPLATE
93
    public static final FoldTemplate IMPORTS_FOLD_TEMPLATE
81
        = new FoldTemplate(IMPORTS_FOLD_TYPE, IMPORTS_FOLD_DESCRIPTION, 0, 0);
94
        = new FoldTemplate(IMPORTS_FOLD_TYPE, IMPORTS_FOLD_DESCRIPTION, 0, 0);
82
95
96
    @Deprecated
83
    public static final FoldTemplate JAVADOC_FOLD_TEMPLATE
97
    public static final FoldTemplate JAVADOC_FOLD_TEMPLATE
84
        = new FoldTemplate(JAVADOC_FOLD_TYPE, JAVADOC_FOLD_DESCRIPTION, 3, 2);
98
        = new FoldTemplate(JAVADOC_FOLD_TYPE, JAVADOC_FOLD_DESCRIPTION, 3, 2);
85
99
100
    @Deprecated
86
    public static final FoldTemplate CODE_BLOCK_FOLD_TEMPLATE
101
    public static final FoldTemplate CODE_BLOCK_FOLD_TEMPLATE
87
        = new FoldTemplate(CODE_BLOCK_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
102
        = new FoldTemplate(CODE_BLOCK_FOLD_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
88
103
104
    @Deprecated
105
    public static final FoldTemplate INNER_CLASS_FOLD_TEMPLATE
106
        = new FoldTemplate(INNERCLASS_TYPE, CODE_BLOCK_FOLD_DESCRIPTION, 1, 1);
107
89
    
108
    
90
    protected static final class FoldTemplate {
109
    protected static final class FoldTemplate {
91
        
110
        
(-)a/java.editor/nbproject/project.properties (-1 / +1 lines)
Lines 42-48 Link Here
42
42
43
javadoc.title=Java Editor
43
javadoc.title=Java Editor
44
44
45
spec.version.base=2.51.0
45
spec.version.base=2.52.0
46
test.qa-functional.cp.extra=${editor.dir}/modules/org-netbeans-modules-editor-fold.jar
46
test.qa-functional.cp.extra=${editor.dir}/modules/org-netbeans-modules-editor-fold.jar
47
javac.source=1.6
47
javac.source=1.6
48
#test.unit.cp.extra=
48
#test.unit.cp.extra=
(-)a/java.editor/nbproject/project.xml (-1 / +2 lines)
Lines 143-148 Link Here
143
                    <compile-dependency/>
143
                    <compile-dependency/>
144
                    <run-dependency>
144
                    <run-dependency>
145
                        <release-version>1</release-version>
145
                        <release-version>1</release-version>
146
                        <specification-version>1.34</specification-version>
146
                    </run-dependency>
147
                    </run-dependency>
147
                </dependency>
148
                </dependency>
148
                <dependency>
149
                <dependency>
Lines 205-211 Link Here
205
                    <compile-dependency/>
206
                    <compile-dependency/>
206
                    <run-dependency>
207
                    <run-dependency>
207
                        <release-version>1</release-version>
208
                        <release-version>1</release-version>
208
                        <specification-version>1.9</specification-version>
209
                        <specification-version>1.29</specification-version>
209
                    </run-dependency>
210
                    </run-dependency>
210
                </dependency>
211
                </dependency>
211
                <dependency>
212
                <dependency>
(-)a/java.editor/src/org/netbeans/modules/editor/java/NbJavaCodeFoldingSideBarFactory.java (+4 lines)
Lines 50-57 Link Here
50
/**
50
/**
51
 *  Java Code Folding Side Bar Factory, responsible for creating CodeFoldingSideBar
51
 *  Java Code Folding Side Bar Factory, responsible for creating CodeFoldingSideBar
52
 *  Plugged via layer.xml
52
 *  Plugged via layer.xml
53
 * <p/>
54
 * As this factory does not provide any extra functionality over the standard factory,
55
 * it should be probably removed.
53
 *
56
 *
54
 *  @author  Martin Roskanin
57
 *  @author  Martin Roskanin
58
 *  @Deprecated
55
 */
59
 */
56
public class NbJavaCodeFoldingSideBarFactory implements SideBarFactory{
60
public class NbJavaCodeFoldingSideBarFactory implements SideBarFactory{
57
61
(-)a/java.editor/src/org/netbeans/modules/java/editor/fold/JavaElementFoldManager.java (-258 / +51 lines)
Lines 71-76 Link Here
71
import javax.swing.text.Document;
71
import javax.swing.text.Document;
72
import javax.swing.text.Position;
72
import javax.swing.text.Position;
73
import org.netbeans.api.editor.fold.Fold;
73
import org.netbeans.api.editor.fold.Fold;
74
import org.netbeans.spi.editor.fold.FoldInfo;
74
import org.netbeans.api.editor.mimelookup.MimeLookup;
75
import org.netbeans.api.editor.mimelookup.MimeLookup;
75
import org.netbeans.api.editor.settings.SimpleValueNames;
76
import org.netbeans.api.editor.settings.SimpleValueNames;
76
import org.netbeans.api.java.lexer.JavaTokenId;
77
import org.netbeans.api.java.lexer.JavaTokenId;
Lines 101-145 Link Here
101
    private FileObject    file;
102
    private FileObject    file;
102
    private JavaElementFoldTask task;
103
    private JavaElementFoldTask task;
103
    
104
    
104
    /**
105
     * Default folding of individual fold types. New instance is created
106
     * when a new FoldManager opens (= editor appears).
107
     */
108
    private static class Presets {
109
        // Folding presets
110
        private boolean foldImportsPreset = false;
111
        private boolean foldInnerClassesPreset = false;
112
        private boolean foldJavadocsPreset = false;
113
        private boolean foldCodeBlocksPreset = false;
114
        private boolean foldInitialCommentsPreset = false;
115
116
        public Presets() {
117
            Preferences prefs = MimeLookup.getLookup(JavaKit.JAVA_MIME_TYPE).lookup(Preferences.class);
118
            foldInitialCommentsPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_INITIAL_COMMENT, foldInitialCommentsPreset);
119
            foldImportsPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_IMPORT, foldImportsPreset);
120
            foldCodeBlocksPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_METHOD, foldCodeBlocksPreset);
121
            foldInnerClassesPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_INNERCLASS, foldInnerClassesPreset);
122
            foldJavadocsPreset = prefs.getBoolean(SimpleValueNames.CODE_FOLDING_COLLAPSE_JAVADOC, foldJavadocsPreset);
123
        }
124
125
        private static volatile Presets CURRENT;
126
127
        static void refresh() {
128
            CURRENT = null;
129
        }
130
131
        static Presets get() {
132
            Presets p = CURRENT;
133
            if (p != null) {
134
                return p;
135
            }
136
            return CURRENT = new Presets();
137
        }
138
    }
139
140
    public void init(FoldOperation operation) {
105
    public void init(FoldOperation operation) {
141
        this.operation = operation;
106
        this.operation = operation;
142
        Presets.refresh();
143
    }
107
    }
144
108
145
    public synchronized void initFolds(FoldHierarchyTransaction transaction) {
109
    public synchronized void initFolds(FoldHierarchyTransaction transaction) {
Lines 149-155 Link Here
149
        if (od instanceof DataObject) {
113
        if (od instanceof DataObject) {
150
            FileObject file = ((DataObject)od).getPrimaryFile();
114
            FileObject file = ((DataObject)od).getPrimaryFile();
151
115
152
            currentFolds = new ArrayList<Fold>();
153
            task = JavaElementFoldTask.getTask(file);
116
            task = JavaElementFoldTask.getTask(file);
154
            task.setJavaElementFoldManager(JavaElementFoldManager.this, file);
117
            task.setJavaElementFoldManager(JavaElementFoldManager.this, file);
155
        }
118
        }
Lines 169-175 Link Here
169
    }
132
    }
170
133
171
    public void removeDamagedNotify(Fold damagedFold) {
134
    public void removeDamagedNotify(Fold damagedFold) {
172
        currentFolds.remove(operation.getExtraInfo(damagedFold));
173
        if (importsFold == damagedFold) {
135
        if (importsFold == damagedFold) {
174
            importsFold = null;//not sure if this is correct...
136
            importsFold = null;//not sure if this is correct...
175
        }
137
        }
Lines 187-193 Link Here
187
        
149
        
188
        task         = null;
150
        task         = null;
189
        file         = null;
151
        file         = null;
190
        currentFolds = null;
191
        importsFold  = null;
152
        importsFold  = null;
192
        initialCommentFold = null;
153
        initialCommentFold = null;
193
    }
154
    }
Lines 278-284 Link Here
278
            }
239
            }
279
            
240
            
280
            final JavaElementFoldVisitor v = new JavaElementFoldVisitor(info, 
241
            final JavaElementFoldVisitor v = new JavaElementFoldVisitor(info, 
281
                    cu, info.getTrees().getSourcePositions(), doc, Presets.get());
242
                    cu, info.getTrees().getSourcePositions(), doc);
282
            
243
            
283
            scan(v, cu, null);
244
            scan(v, cu, null);
284
            
245
            
Lines 291-305 Link Here
291
            if (v.stopped || isCancelled())
252
            if (v.stopped || isCancelled())
292
                return ;
253
                return ;
293
254
294
            Collections.sort(v.folds);
295
            if (mgrs instanceof JavaElementFoldManager) {
255
            if (mgrs instanceof JavaElementFoldManager) {
296
                SwingUtilities.invokeLater(((JavaElementFoldManager)mgrs).new CommitFolds(doc, v.folds));
256
                SwingUtilities.invokeLater(((JavaElementFoldManager)mgrs).createCommit(doc, v));
297
            } else {
257
            } else {
298
                SwingUtilities.invokeLater(new Runnable() {
258
                SwingUtilities.invokeLater(new Runnable() {
299
                    Collection<JavaElementFoldManager> jefms = (Collection<JavaElementFoldManager>)mgrs;
259
                    Collection<JavaElementFoldManager> jefms = (Collection<JavaElementFoldManager>)mgrs;
300
                    public void run() {
260
                    public void run() {
301
                        for (JavaElementFoldManager jefm : jefms) {
261
                        for (JavaElementFoldManager jefm : jefms) {
302
                            jefm.new CommitFolds(doc, v.folds).run();
262
                            jefm.createCommit(doc, v).run();
303
                        }
263
                        }
304
                }});
264
                }});
305
            }
265
            }
Lines 312-327 Link Here
312
        
272
        
313
    }
273
    }
314
    
274
    
275
    private CommitFolds createCommit(Document doc, JavaElementFoldVisitor v) {
276
        return new CommitFolds(doc, v.folds, v.initialCommentInfo, v.importsInfo);
277
    }
278
    
315
    private class CommitFolds implements Runnable {
279
    private class CommitFolds implements Runnable {
316
        
280
        
317
        private boolean insideRender;
281
        private boolean insideRender;
318
        private Document doc;
282
        private Document doc;
319
        private List<FoldInfo> infos;
283
        private List<FoldInfo> infos;
320
        private long startTime;
284
        private long startTime;
285
        private FoldInfo initComment;
286
        private FoldInfo imports;
321
        
287
        
322
        public CommitFolds(Document doc, List<FoldInfo> infos) {
288
        public CommitFolds(Document doc, List<FoldInfo> infos, FoldInfo initComment, FoldInfo imports) {
323
            this.doc = doc;
289
            this.doc = doc;
324
            this.infos = infos;
290
            this.infos = infos;
291
            this.imports = imports;
292
            this.initComment = initComment;
325
        }
293
        }
326
        
294
        
327
        /**
295
        /**
Lines 329-354 Link Here
329
         * ignores the default state, and takes it from the actual state of
297
         * ignores the default state, and takes it from the actual state of
330
         * existing fold.
298
         * existing fold.
331
         */
299
         */
332
        private boolean mergeSpecialFoldState(FoldInfo fi) {
300
        private void setSpecialFoldState(FoldInfo info, Fold f) {
333
            if (fi.template == IMPORTS_FOLD_TEMPLATE) {
301
            if (info == null || f == null) {
334
                if (importsFold != null) {
302
                return;
335
                    return importsFold.isCollapsed();
336
                }
337
            } else if (fi.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
338
                if (initialCommentFold != null) {
339
                    return initialCommentFold.isCollapsed();
340
                }
341
            }
342
            return fi.collapseByDefault;
343
        }
344
        
345
        private int flip(int order) {
346
            if (order > 0) {
347
                return -1;
348
            } else if (order < 0) {
349
                return 1;
350
            } else {
303
            } else {
351
                return 0;
304
                info.collapsed(f.isCollapsed());
352
            }
305
            }
353
        }
306
        }
354
        
307
        
Lines 356-361 Link Here
356
            if (!insideRender) {
309
            if (!insideRender) {
357
                startTime = System.currentTimeMillis();
310
                startTime = System.currentTimeMillis();
358
                insideRender = true;
311
                insideRender = true;
312
                
313
                // retain import & initial comment states
314
                setSpecialFoldState(imports, importsFold);
315
                setSpecialFoldState(initComment, initialCommentFold);
359
                Document d = operation.getHierarchy().getComponent().getDocument();
316
                Document d = operation.getHierarchy().getComponent().getDocument();
360
                if (d != doc) {
317
                if (d != doc) {
361
                    return;
318
                    return;
Lines 366-457 Link Here
366
            }
323
            }
367
            
324
            
368
            operation.getHierarchy().lock();
325
            operation.getHierarchy().lock();
369
            
370
            try {
326
            try {
371
                FoldHierarchyTransaction tr = operation.openTransaction();
327
                Map<FoldInfo, Fold> folds = operation.update(infos, null, null);
372
                
328
                if (initComment != null) {
373
                try {
329
                    initialCommentFold = folds.get(initComment);
374
                    if (currentFolds == null)
330
                } else {
375
                        return ;
331
                    initialCommentFold = null;
376
377
                    List<Fold> updatedFolds = new ArrayList<Fold>(infos.size());
378
                    Iterator<Fold> itExisting = currentFolds.iterator();
379
                    Iterator<FoldInfo> itNew = infos.iterator();
380
                    Fold currentExisting = itExisting.hasNext() ? itExisting.next() : null;
381
                    FoldInfo currentNew = itNew.hasNext() ? itNew.next() : null;
382
383
                    while (currentExisting != null || currentNew != null) {
384
                        int order = currentExisting != null && currentNew != null ? currentNew.compareTo(currentExisting) : currentExisting != null ? 1 : -1;
385
386
                        if (order > 0) {
387
                            //fold removed:
388
                            operation.removeFromHierarchy(currentExisting, tr);
389
390
                            if (importsFold == currentExisting) {
391
                                importsFold = null;
392
                            }
393
394
                            if (initialCommentFold == currentExisting) {
395
                                initialCommentFold = null;
396
                            }
397
                            
398
                            currentExisting = itExisting.hasNext() ? itExisting.next() : null;
399
                        } else {
400
                            //added or remains:
401
                            if (order < 0) {
402
                                //added:
403
                                int start = currentNew.start.getOffset();
404
                                int end   = currentNew.end.getOffset();
405
406
                                if (end > start &&
407
                                        (end - start) >= (currentNew.template.getStartGuardedLength() + currentNew.template.getEndGuardedLength())) {
408
                                    Fold f = operation.addToHierarchy(currentNew.template.getType(),
409
                                            currentNew.template.getDescription(),
410
                                            mergeSpecialFoldState(currentNew),
411
                                            start,
412
                                            end,
413
                                            currentNew.template.getStartGuardedLength(),
414
                                            currentNew.template.getEndGuardedLength(),
415
                                            currentNew,
416
                                            tr);
417
                                    
418
                                    if (currentNew.template == IMPORTS_FOLD_TEMPLATE) {
419
                                        importsFold = f;
420
                                    }
421
                                    
422
                                    if (currentNew.template == INITIAL_COMMENT_FOLD_TEMPLATE) {
423
                                        initialCommentFold = f;
424
                                    }
425
426
                                    updatedFolds.add(f);
427
                                }
428
                            } else {
429
                                updatedFolds.add(currentExisting);
430
                                currentExisting = itExisting.hasNext() ? itExisting.next() : null;
431
                            }
432
433
                            FoldInfo newNew = itNew.hasNext() ? itNew.next() : null;
434
435
                            // XXX: In some situations infos contains duplicate folds and we don't
436
                            // want to add the same multiple times. The situation that I came across
437
                            // was with having an empty enum subclass with javadoc. The javadoc fold
438
                            // was added twice - once from visitClass and second time from visitMethod
439
                            // for the <init> node, which for some reason has the same offset as the
440
                            // the enum inner class.
441
                            while (newNew != null && currentNew.compareTo(newNew) == 0) {
442
                                newNew = itNew.hasNext() ? itNew.next() : null;
443
                            }
444
445
                            currentNew = newNew;
446
                        }
447
                    }
448
                    
449
                    currentFolds = updatedFolds;
450
                } catch (BadLocationException e) {
451
                    Exceptions.printStackTrace(e);
452
                } finally {
453
                    tr.commit();
454
                }
332
                }
333
                if (imports != null) {
334
                    importsFold = folds.get(imports);
335
                } else {
336
                    importsFold = null;
337
                }
338
            } catch (BadLocationException e) {
339
                Exceptions.printStackTrace(e);
455
            } finally {
340
            } finally {
456
                operation.getHierarchy().unlock();
341
                operation.getHierarchy().unlock();
457
            }
342
            }
Lines 464-490 Link Here
464
    }
349
    }
465
350
466
    //@GuardedBy(FoldOperation.openTransaction())
351
    //@GuardedBy(FoldOperation.openTransaction())
467
    private List<Fold> currentFolds; //in natural order
468
    private Fold initialCommentFold;
352
    private Fold initialCommentFold;
469
    private Fold importsFold;
353
    private Fold importsFold;
470
    
354
    
471
    private static final class JavaElementFoldVisitor extends CancellableTreePathScanner<Object, Object> {
355
    private static final class JavaElementFoldVisitor extends CancellableTreePathScanner<Object, Object> {
472
        
356
        
473
        private List<FoldInfo> folds = new ArrayList<JavaElementFoldManager.FoldInfo>();
357
        private List<FoldInfo> folds = new ArrayList<FoldInfo>();
474
        private CompilationInfo info;
358
        private CompilationInfo info;
475
        private CompilationUnitTree cu;
359
        private CompilationUnitTree cu;
476
        private SourcePositions sp;
360
        private SourcePositions sp;
477
        private boolean stopped;
361
        private boolean stopped;
478
        private int initialCommentStopPos = Integer.MAX_VALUE;
362
        private int initialCommentStopPos = Integer.MAX_VALUE;
479
        private Document doc;
363
        private Document doc;
480
        private Presets presets;
364
        private FoldInfo    initialCommentInfo;
365
        private FoldInfo    importsInfo;
481
        
366
        
482
        public JavaElementFoldVisitor(CompilationInfo info, CompilationUnitTree cu, SourcePositions sp, Document doc, Presets presets) {
367
        public JavaElementFoldVisitor(CompilationInfo info, CompilationUnitTree cu, SourcePositions sp, Document doc) {
483
            this.info = info;
368
            this.info = info;
484
            this.cu = cu;
369
            this.cu = cu;
485
            this.sp = sp;
370
            this.sp = sp;
486
            this.doc = doc;
371
            this.doc = doc;
487
            this.presets = presets;
488
        }
372
        }
489
        
373
        
490
        public void checkInitialFold() {
374
        public void checkInitialFold() {
Lines 500-520 Link Here
500
                    
384
                    
501
                    if (token.id() == JavaTokenId.BLOCK_COMMENT || token.id() == JavaTokenId.JAVADOC_COMMENT) {
385
                    if (token.id() == JavaTokenId.BLOCK_COMMENT || token.id() == JavaTokenId.JAVADOC_COMMENT) {
502
                        int startOffset = ts.offset();
386
                        int startOffset = ts.offset();
503
                        boolean collapsed = presets.foldInitialCommentsPreset;
387
                        folds.add(initialCommentInfo = FoldInfo.range(startOffset, startOffset + token.length(), INITIAL_COMMENT_FOLD_TYPE));
504
505
                        /*
506
                        if (initialCommentFold != null) {
507
                            collapsed = initialCommentFold.isCollapsed();
508
                        }
509
                        */
510
                        
511
                        folds.add(new FoldInfo(doc, startOffset, startOffset + token.length(), INITIAL_COMMENT_FOLD_TEMPLATE, collapsed));
512
                        break;
388
                        break;
513
                    }
389
                    }
514
                }
390
                }
515
            } catch (BadLocationException e) {
516
                //the document probably changed, stop
517
                stopped = true;
518
            } catch (ConcurrentModificationException e) {
391
            } catch (ConcurrentModificationException e) {
519
                //from TokenSequence, document probably changed, stop
392
                //from TokenSequence, document probably changed, stop
520
                stopped = true;
393
                stopped = true;
Lines 542-548 Link Here
542
                
415
                
543
                if (token.id() == JavaTokenId.JAVADOC_COMMENT) {
416
                if (token.id() == JavaTokenId.JAVADOC_COMMENT) {
544
                    int startOffset = ts.offset();
417
                    int startOffset = ts.offset();
545
                    folds.add(new FoldInfo(doc, startOffset, startOffset + token.length(), JAVADOC_FOLD_TEMPLATE, presets.foldJavadocsPreset));
418
                    folds.add(FoldInfo.range(startOffset, startOffset + token.length(), JAVADOC_FOLD_TYPE));
546
                    if (startOffset < initialCommentStopPos)
419
                    if (startOffset < initialCommentStopPos)
547
                        initialCommentStopPos = startOffset;
420
                        initialCommentStopPos = startOffset;
548
                }
421
                }
Lines 559-566 Link Here
559
                    int start = (int)sp.getStartPosition(cu, node);
432
                    int start = (int)sp.getStartPosition(cu, node);
560
                    int end   = (int)sp.getEndPosition(cu, node);
433
                    int end   = (int)sp.getEndPosition(cu, node);
561
                    
434
                    
562
                    if (start != (-1) && end != (-1))
435
                    if (start != (-1) && end != (-1)) {
563
                        folds.add(new FoldInfo(doc, start, end, CODE_BLOCK_FOLD_TEMPLATE, presets.foldCodeBlocksPreset));
436
                        folds.add(FoldInfo.range(start, end, CODE_BLOCK_FOLD_TYPE));
437
                    }
564
                }
438
                }
565
                
439
                
566
                handleJavadoc(javadocTree != null ? javadocTree : node);
440
                handleJavadoc(javadocTree != null ? javadocTree : node);
Lines 588-595 Link Here
588
                    int start = Utilities.findBodyStart(node, cu, sp, doc);
462
                    int start = Utilities.findBodyStart(node, cu, sp, doc);
589
                    int end   = (int)sp.getEndPosition(cu, node);
463
                    int end   = (int)sp.getEndPosition(cu, node);
590
                    
464
                    
591
                    if (start != (-1) && end != (-1))
465
                    if (start != (-1) && end != (-1)) {
592
                        folds.add(new FoldInfo(doc, start, end, CODE_BLOCK_FOLD_TEMPLATE, presets.foldInnerClassesPreset));
466
                        folds.add(FoldInfo.range(start, end, INNERCLASS_TYPE));
467
		      }
593
                }
468
                }
594
                
469
                
595
                handleJavadoc(node);
470
                handleJavadoc(node);
Lines 642-740 Link Here
642
            }
517
            }
643
            
518
            
644
            if (importsEnd != (-1) && importsStart != (-1)) {
519
            if (importsEnd != (-1) && importsStart != (-1)) {
645
                if (importsStart < initialCommentStopPos)
520
                if (importsStart < initialCommentStopPos) {
646
                    initialCommentStopPos = importsStart;
521
                    initialCommentStopPos = importsStart;
647
                
522
                }
648
                try {
523
                importsStart += 7/*"import ".length()*/;
649
                    boolean collapsed = presets.foldImportsPreset;
524
650
                    
525
                if (importsStart < importsEnd) {
651
                    /*
526
                    folds.add(importsInfo = FoldInfo.range(importsStart , importsEnd, IMPORTS_FOLD_TYPE));
652
                    if (importsFold != null) {
653
                        collapsed = importsFold.isCollapsed();
654
                    }
655
                    */
656
                    
657
                    importsStart += 7/*"import ".length()*/;
658
                    
659
                    if (importsStart < importsEnd) {
660
                        folds.add(new FoldInfo(doc, importsStart , importsEnd, IMPORTS_FOLD_TEMPLATE, collapsed));
661
                    }
662
                } catch (BadLocationException e) {
663
                    //the document probably changed, stop
664
                    stopped = true;
665
                }
527
                }
666
            }
528
            }
667
            return super.visitCompilationUnit(node, p);
529
            return super.visitCompilationUnit(node, p);
668
        }
530
        }
669
670
    }
671
    
672
    protected static final class FoldInfo implements Comparable {
673
        
674
        private final Position start;
675
        private final Position end;
676
        private final FoldTemplate template;
677
        private final boolean collapseByDefault;
678
        //@GUardedBy(FoldOperation.openTransaction())
679
        
680
        public FoldInfo(Document doc, int start, int end, FoldTemplate template, boolean collapseByDefault) throws BadLocationException {
681
            this.start = doc.createPosition(start);
682
            this.end   = doc.createPosition(end);
683
            this.template = template;
684
            this.collapseByDefault = collapseByDefault;
685
        }
686
        
687
        public int compareTo(Fold remote) {
688
            if (start.getOffset() < remote.getStartOffset()) {
689
                return -1;
690
            }
691
            
692
            if (start.getOffset() > remote.getStartOffset()) {
693
                return 1;
694
            }
695
            
696
            if (end.getOffset() < remote.getEndOffset()) {
697
                return -1;
698
            }
699
            
700
            if (end.getOffset() > remote.getEndOffset()) {
701
                return 1;
702
            }
703
            
704
            //XXX: abusing the length of the fold description to implement ordering (the exact order does not matter in this case):
705
            return template.getDescription().length() - remote.getDescription().length();
706
        }
707
        
708
        public int compareTo(Object o) {
709
            if (o instanceof Fold) {
710
                return compareTo((Fold)o);
711
            }
712
            FoldInfo remote = (FoldInfo) o;
713
            
714
            if (start.getOffset() < remote.start.getOffset()) {
715
                return -1;
716
            }
717
            
718
            if (start.getOffset() > remote.start.getOffset()) {
719
                return 1;
720
            }
721
            
722
            if (end.getOffset() < remote.end.getOffset()) {
723
                return -1;
724
            }
725
            
726
            if (end.getOffset() > remote.end.getOffset()) {
727
                return 1;
728
            }
729
            
730
            //XXX: abusing the length of the fold description to implement ordering (the exact order does not matter in this case):
731
            return template.getDescription().length() - remote.template.getDescription().length();
732
        }
733
734
        @Override
735
        public String toString() {
736
            return "FoldInfo[" + start.getOffset() + ", " + end.getOffset() + ", " + template.getDescription() + "]";
737
        }
738
    }
531
    }
739
    
532
    
740
}
533
}
(-)a/java.editor/src/org/netbeans/modules/java/editor/fold/JavaFoldTypeProvider.java (+78 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.java.editor.fold;
43
44
import java.util.ArrayList;
45
import java.util.Collection;
46
import org.netbeans.api.editor.fold.FoldType;
47
import org.netbeans.api.editor.mimelookup.MimeRegistration;
48
import org.netbeans.editor.ext.java.JavaFoldManager;
49
import org.netbeans.spi.editor.fold.FoldTypeProvider;
50
51
/**
52
 *
53
 * @author sdedic
54
 */
55
@MimeRegistration(mimeType = "text/x-java", service = FoldTypeProvider.class)
56
public class JavaFoldTypeProvider implements FoldTypeProvider {
57
    private Collection<FoldType>   types = new ArrayList<FoldType>(5);
58
59
    public JavaFoldTypeProvider() {
60
        types.add(JavaFoldManager.CODE_BLOCK_FOLD_TYPE);
61
        types.add(JavaFoldManager.INNERCLASS_TYPE);
62
        types.add(JavaFoldManager.IMPORTS_FOLD_TYPE);
63
        types.add(JavaFoldManager.JAVADOC_FOLD_TYPE);
64
        types.add(JavaFoldManager.INITIAL_COMMENT_FOLD_TYPE);
65
    }
66
    
67
    
68
    @Override
69
    public Collection getValues(Class type) {
70
        return types;
71
    }
72
73
    @Override
74
    public boolean inheritable() {
75
        return false;
76
    }
77
    
78
}
(-)a/java.editor/src/org/netbeans/modules/java/editor/fold/JavadocReaderFactory.java (+64 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.java.editor.fold;
43
44
import org.netbeans.api.editor.fold.FoldType;
45
import org.netbeans.api.editor.fold.FoldUtilities;
46
import org.netbeans.api.editor.fold.FoldingSupport;
47
import org.netbeans.api.editor.mimelookup.MimeRegistration;
48
import org.netbeans.spi.editor.fold.ContentReader;
49
50
/**
51
 *
52
 * @author sdedic
53
 */
54
@MimeRegistration(mimeType = "text/x-java", service = ContentReader.Factory.class)
55
public class JavadocReaderFactory implements ContentReader.Factory {
56
    @Override
57
    public ContentReader createReader(FoldType ft) {
58
        if (ft == JavaElementFoldManager.JAVADOC_FOLD_TYPE) {
59
            return FoldingSupport.contentReader("*", "\\.", "@", null);
60
        } else {
61
            return null;
62
        }
63
    }
64
}
(-)a/java.editor/src/org/netbeans/modules/java/editor/resources/layer.xml (-5 / +5 lines)
Lines 141-153 Link Here
141
                </folder>
141
                </folder>
142
                
142
                
143
                <folder name="SideBar">
143
                <folder name="SideBar">
144
                    <file name="org-netbeans-modules-editor-java-NbJavaCodeFoldingSideBarFactory.instance">
144
                </folder>
145
                        <attr name="position" intvalue="1500"/>
146
                    </file>
147
                    </folder>
148
                
145
                
149
                <folder name="FoldManager">
146
                <folder name="FoldManager">
150
                    <file name="org-netbeans-editor-CustomFoldManager$Factory.instance"/>
147
                    <file name="CustomFoldManager.instance">
148
                        <attr name="instanceOf" stringvalue="org.netbeans.spi.editor.fold.FoldManagerFactory"/>
149
                        <attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldingSupport.userFoldManagerFactory"/>
150
                    </file>
151
                    <file name="org-netbeans-modules-java-editor-fold-JavaElementFoldManagerFactory.instance"/>
151
                    <file name="org-netbeans-modules-java-editor-fold-JavaElementFoldManagerFactory.instance"/>
152
                </folder>
152
                </folder>
153
                
153
                
(-)a/options.editor/manifest.mf (-1 / +1 lines)
Lines 2-7 Link Here
2
OpenIDE-Module: org.netbeans.modules.options.editor/1
2
OpenIDE-Module: org.netbeans.modules.options.editor/1
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/options/editor/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/options/editor/Bundle.properties
4
OpenIDE-Module-Layer: org/netbeans/modules/options/editor/mf-layer.xml
4
OpenIDE-Module-Layer: org/netbeans/modules/options/editor/mf-layer.xml
5
OpenIDE-Module-Specification-Version: 1.39
5
OpenIDE-Module-Specification-Version: 1.40
6
AutoUpdate-Show-In-Client: false
6
AutoUpdate-Show-In-Client: false
7
7
(-)a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form (-105 / +5 lines)
Lines 29-52 Link Here
29
          <Group type="102" attributes="0">
29
          <Group type="102" attributes="0">
30
              <Group type="103" groupAlignment="0" attributes="0">
30
              <Group type="103" groupAlignment="0" attributes="0">
31
                  <Group type="102" alignment="0" attributes="0">
31
                  <Group type="102" alignment="0" attributes="0">
32
                      <EmptySpace max="-2" attributes="0"/>
32
                      <EmptySpace min="-2" pref="163" max="-2" attributes="0"/>
33
                      <Component id="lCollapseByDefault" min="-2" max="-2" attributes="0"/>
34
                      <EmptySpace max="-2" attributes="0"/>
35
                      <Group type="103" groupAlignment="0" attributes="0">
33
                      <Group type="103" groupAlignment="0" attributes="0">
36
                          <Component id="cbUseCodeFolding" alignment="0" min="-2" max="-2" attributes="0"/>
37
                          <Group type="102" alignment="0" attributes="0">
34
                          <Group type="102" alignment="0" attributes="0">
38
                              <Group type="103" groupAlignment="0" attributes="0">
35
                              <EmptySpace min="-2" pref="175" max="-2" attributes="0"/>
39
                                  <Component id="cbFoldMethods" linkSize="1" alignment="0" min="-2" max="-2" attributes="0"/>
36
                              <Component id="cbBraceTooltip" min="-2" max="-2" attributes="0"/>
40
                                  <Component id="cbFoldInnerClasses" linkSize="1" alignment="0" min="-2" max="-2" attributes="0"/>
41
                                  <Component id="cbFoldImports" linkSize="1" alignment="0" min="-2" max="-2" attributes="0"/>
42
                              </Group>
43
                              <EmptySpace type="separate" max="-2" attributes="0"/>
44
                              <Group type="103" groupAlignment="0" attributes="0">
45
                                  <Component id="cbFoldJavadocComments" linkSize="1" alignment="0" min="-2" max="-2" attributes="0"/>
46
                                  <Component id="cbFoldInitialComments" linkSize="1" alignment="0" min="-2" max="-2" attributes="0"/>
47
                                  <Component id="cbFoldTags" alignment="0" min="-2" max="-2" attributes="0"/>
48
                                  <Component id="cbBraceTooltip" min="-2" max="-2" attributes="0"/>
49
                              </Group>
50
                          </Group>
37
                          </Group>
51
                          <Component id="cbCamelCaseBehavior" alignment="0" min="-2" max="-2" attributes="0"/>
38
                          <Component id="cbCamelCaseBehavior" alignment="0" min="-2" max="-2" attributes="0"/>
52
                          <Group type="102" attributes="0">
39
                          <Group type="102" attributes="0">
Lines 54-63 Link Here
54
                              <Component id="lCamelCaseBehaviorExample" min="-2" max="-2" attributes="0"/>
41
                              <Component id="lCamelCaseBehaviorExample" min="-2" max="-2" attributes="0"/>
55
                          </Group>
42
                          </Group>
56
                      </Group>
43
                      </Group>
57
                  </Group>
44
                      <EmptySpace min="-2" pref="26" max="-2" attributes="0"/>
58
                  <Group type="102" alignment="0" attributes="0">
59
                      <EmptySpace min="12" pref="12" max="-2" attributes="0"/>
60
                      <Component id="lUseCodeFolding" min="-2" max="-2" attributes="0"/>
61
                  </Group>
45
                  </Group>
62
                  <Group type="102" alignment="0" attributes="0">
46
                  <Group type="102" alignment="0" attributes="0">
63
                      <EmptySpace max="-2" attributes="0"/>
47
                      <EmptySpace max="-2" attributes="0"/>
Lines 72-82 Link Here
72
              <EmptySpace min="0" pref="12" max="32767" attributes="0"/>
56
              <EmptySpace min="0" pref="12" max="32767" attributes="0"/>
73
          </Group>
57
          </Group>
74
          <Group type="102" attributes="0">
58
          <Group type="102" attributes="0">
75
              <Component id="lCodeFolding" min="-2" max="-2" attributes="0"/>
76
              <EmptySpace max="-2" attributes="0"/>
77
              <Component id="jSeparator1" max="32767" attributes="0"/>
78
          </Group>
79
          <Group type="102" attributes="0">
80
              <Component id="lBracesMatching" min="-2" max="-2" attributes="0"/>
59
              <Component id="lBracesMatching" min="-2" max="-2" attributes="0"/>
81
              <EmptySpace max="-2" attributes="0"/>
60
              <EmptySpace max="-2" attributes="0"/>
82
              <Component id="jSeparator6" max="32767" attributes="0"/>
61
              <Component id="jSeparator6" max="32767" attributes="0"/>
Lines 97-129 Link Here
97
    <DimensionLayout dim="1">
76
    <DimensionLayout dim="1">
98
      <Group type="103" groupAlignment="0" attributes="0">
77
      <Group type="103" groupAlignment="0" attributes="0">
99
          <Group type="102" alignment="0" attributes="0">
78
          <Group type="102" alignment="0" attributes="0">
100
              <EmptySpace min="-2" max="-2" attributes="0"/>
101
              <Group type="103" groupAlignment="1" attributes="0">
102
                  <Component id="lCodeFolding" min="-2" max="-2" attributes="1"/>
103
                  <Component id="jSeparator1" min="-2" pref="10" max="-2" attributes="0"/>
104
              </Group>
105
              <EmptySpace max="-2" attributes="0"/>
106
              <Group type="103" groupAlignment="0" attributes="0">
107
                  <Component id="cbUseCodeFolding" alignment="0" min="-2" max="-2" attributes="0"/>
108
                  <Component id="lUseCodeFolding" min="-2" max="-2" attributes="0"/>
109
              </Group>
110
              <EmptySpace max="-2" attributes="0"/>
111
              <Group type="103" groupAlignment="3" attributes="0">
112
                  <Component id="lCollapseByDefault" alignment="3" min="-2" max="-2" attributes="0"/>
113
                  <Component id="cbFoldMethods" alignment="3" min="-2" max="-2" attributes="0"/>
114
                  <Component id="cbFoldJavadocComments" alignment="3" min="-2" max="-2" attributes="0"/>
115
              </Group>
116
              <EmptySpace max="-2" attributes="0"/>
117
              <Group type="103" groupAlignment="3" attributes="0">
118
                  <Component id="cbFoldInnerClasses" alignment="3" min="-2" max="-2" attributes="0"/>
119
                  <Component id="cbFoldInitialComments" alignment="3" min="-2" max="-2" attributes="0"/>
120
              </Group>
121
              <EmptySpace max="-2" attributes="0"/>
122
              <Group type="103" groupAlignment="3" attributes="0">
123
                  <Component id="cbFoldImports" alignment="3" min="-2" max="-2" attributes="0"/>
124
                  <Component id="cbFoldTags" alignment="3" min="-2" max="-2" attributes="0"/>
125
              </Group>
126
              <EmptySpace type="separate" max="-2" attributes="0"/>
127
              <Group type="103" groupAlignment="1" attributes="0">
79
              <Group type="103" groupAlignment="1" attributes="0">
128
                  <Component id="lBracesMatching" min="-2" max="-2" attributes="0"/>
80
                  <Component id="lBracesMatching" min="-2" max="-2" attributes="0"/>
129
                  <Component id="jSeparator6" min="-2" pref="10" max="-2" attributes="0"/>
81
                  <Component id="jSeparator6" min="-2" pref="10" max="-2" attributes="0"/>
Lines 154-217 Link Here
154
              </Group>
106
              </Group>
155
              <EmptySpace max="-2" attributes="0"/>
107
              <EmptySpace max="-2" attributes="0"/>
156
              <Component id="lSearchtypeTooltip" min="-2" max="-2" attributes="0"/>
108
              <Component id="lSearchtypeTooltip" min="-2" max="-2" attributes="0"/>
157
              <EmptySpace max="-2" attributes="0"/>
109
              <EmptySpace min="0" pref="12" max="32767" attributes="0"/>
158
          </Group>
110
          </Group>
159
      </Group>
111
      </Group>
160
    </DimensionLayout>
112
    </DimensionLayout>
161
  </Layout>
113
  </Layout>
162
  <SubComponents>
114
  <SubComponents>
163
    <Component class="javax.swing.JLabel" name="lCodeFolding">
164
      <Properties>
165
        <Property name="text" type="java.lang.String" value="Code Folding"/>
166
      </Properties>
167
    </Component>
168
    <Component class="javax.swing.JLabel" name="lUseCodeFolding">
169
      <Properties>
170
        <Property name="labelFor" type="java.awt.Component" editor="org.netbeans.modules.form.ComponentChooserEditor">
171
          <ComponentRef name="cbUseCodeFolding"/>
172
        </Property>
173
        <Property name="text" type="java.lang.String" value="Use Code Folding:"/>
174
      </Properties>
175
    </Component>
176
    <Component class="javax.swing.JLabel" name="lCollapseByDefault">
177
      <Properties>
178
        <Property name="text" type="java.lang.String" value="Collapse by Default:"/>
179
      </Properties>
180
    </Component>
181
    <Component class="javax.swing.JCheckBox" name="cbUseCodeFolding">
182
    </Component>
183
    <Component class="javax.swing.JCheckBox" name="cbFoldMethods">
184
      <Properties>
185
        <Property name="text" type="java.lang.String" value="Methods"/>
186
      </Properties>
187
    </Component>
188
    <Component class="javax.swing.JCheckBox" name="cbFoldInnerClasses">
189
      <Properties>
190
        <Property name="text" type="java.lang.String" value="Inner Classes"/>
191
      </Properties>
192
    </Component>
193
    <Component class="javax.swing.JCheckBox" name="cbFoldImports">
194
      <Properties>
195
        <Property name="text" type="java.lang.String" value="Imports"/>
196
      </Properties>
197
    </Component>
198
    <Component class="javax.swing.JCheckBox" name="cbFoldJavadocComments">
199
      <Properties>
200
        <Property name="text" type="java.lang.String" value="Javadoc Comments"/>
201
      </Properties>
202
    </Component>
203
    <Component class="javax.swing.JCheckBox" name="cbFoldInitialComments">
204
      <Properties>
205
        <Property name="text" type="java.lang.String" value="Initial Comments"/>
206
      </Properties>
207
    </Component>
208
    <Component class="javax.swing.JCheckBox" name="cbFoldTags">
209
      <Properties>
210
        <Property name="text" type="java.lang.String" value="Tags and Other Code Blocks"/>
211
      </Properties>
212
    </Component>
213
    <Component class="javax.swing.JSeparator" name="jSeparator1">
214
    </Component>
215
    <Component class="javax.swing.JLabel" name="lBracesMatching">
115
    <Component class="javax.swing.JLabel" name="lBracesMatching">
216
      <Properties>
116
      <Properties>
217
        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
117
        <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
(-)a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java (-144 / +6 lines)
Lines 76-93 Link Here
76
    public GeneralEditorPanel () {
76
    public GeneralEditorPanel () {
77
        initComponents ();
77
        initComponents ();
78
78
79
        loc (lCodeFolding, "Code_Folding");
80
        loc (lUseCodeFolding, "Code_Folding_Section");
81
        loc (lCollapseByDefault, "Fold_by_Default");
82
            
83
        loc (cbUseCodeFolding, "Use_Folding");
84
        loc (cbFoldMethods, "Fold_Methods");
85
        loc (cbFoldInnerClasses, "Fold_Classes");
86
        loc (cbFoldImports, "Fold_Imports");
87
        loc (cbFoldJavadocComments, "Fold_JavaDoc");
88
        loc (cbFoldInitialComments, "Fold_Licence");
89
        loc (cbFoldTags, "Fold_Tags");
90
91
        loc (lCamelCaseBehavior, "Camel_Case_Behavior");
79
        loc (lCamelCaseBehavior, "Camel_Case_Behavior");
92
        loc (cbCamelCaseBehavior, "Enable_Camel_Case_In_Java");
80
        loc (cbCamelCaseBehavior, "Enable_Camel_Case_In_Java");
93
        loc (lCamelCaseBehaviorExample, "Camel_Case_Behavior_Example");
81
        loc (lCamelCaseBehaviorExample, "Camel_Case_Behavior_Example");
Lines 99-105 Link Here
99
        loc (cbBraceTooltip, "Brace_First_Tooltip");
87
        loc (cbBraceTooltip, "Brace_First_Tooltip");
100
        loc (cbShowBraceOutline, "Brace_Show_Outline");
88
        loc (cbShowBraceOutline, "Brace_Show_Outline");
101
                
89
                
102
        cbUseCodeFolding.setMnemonic(NbBundle.getMessage (GeneralEditorPanel.class, "MNEMONIC_Use_Folding").charAt(0));
103
        cboEditorSearchType.setRenderer(new EditorSearchTypeRenderer(cboEditorSearchType.getRenderer()));
90
        cboEditorSearchType.setRenderer(new EditorSearchTypeRenderer(cboEditorSearchType.getRenderer()));
104
        cboEditorSearchType.setModel(new DefaultComboBoxModel(new Object [] { "default", "closing"})); //NOI18N
91
        cboEditorSearchType.setModel(new DefaultComboBoxModel(new Object [] { "default", "closing"})); //NOI18N
105
        cboEditorSearchType.addActionListener( new ActionListener() {
92
        cboEditorSearchType.addActionListener( new ActionListener() {
Lines 123-139 Link Here
123
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
110
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
124
    private void initComponents() {
111
    private void initComponents() {
125
112
126
        lCodeFolding = new javax.swing.JLabel();
127
        lUseCodeFolding = new javax.swing.JLabel();
128
        lCollapseByDefault = new javax.swing.JLabel();
129
        cbUseCodeFolding = new javax.swing.JCheckBox();
130
        cbFoldMethods = new javax.swing.JCheckBox();
131
        cbFoldInnerClasses = new javax.swing.JCheckBox();
132
        cbFoldImports = new javax.swing.JCheckBox();
133
        cbFoldJavadocComments = new javax.swing.JCheckBox();
134
        cbFoldInitialComments = new javax.swing.JCheckBox();
135
        cbFoldTags = new javax.swing.JCheckBox();
136
        jSeparator1 = new javax.swing.JSeparator();
137
        lBracesMatching = new javax.swing.JLabel();
113
        lBracesMatching = new javax.swing.JLabel();
138
        cbShowBraceOutline = new javax.swing.JCheckBox();
114
        cbShowBraceOutline = new javax.swing.JCheckBox();
139
        cbBraceTooltip = new javax.swing.JCheckBox();
115
        cbBraceTooltip = new javax.swing.JCheckBox();
Lines 150-174 Link Here
150
126
151
        setForeground(new java.awt.Color(99, 130, 191));
127
        setForeground(new java.awt.Color(99, 130, 191));
152
128
153
        lCodeFolding.setText("Code Folding");
154
155
        lUseCodeFolding.setLabelFor(cbUseCodeFolding);
156
        lUseCodeFolding.setText("Use Code Folding:");
157
158
        lCollapseByDefault.setText("Collapse by Default:");
159
160
        cbFoldMethods.setText("Methods");
161
162
        cbFoldInnerClasses.setText("Inner Classes");
163
164
        cbFoldImports.setText("Imports");
165
166
        cbFoldJavadocComments.setText("Javadoc Comments");
167
168
        cbFoldInitialComments.setText("Initial Comments");
169
170
        cbFoldTags.setText("Tags and Other Code Blocks");
171
172
        lBracesMatching.setText(org.openide.util.NbBundle.getMessage(GeneralEditorPanel.class, "BRACES_MATCHING")); // NOI18N
129
        lBracesMatching.setText(org.openide.util.NbBundle.getMessage(GeneralEditorPanel.class, "BRACES_MATCHING")); // NOI18N
173
130
174
        cbShowBraceOutline.setText("Show outline");
131
        cbShowBraceOutline.setText("Show outline");
Lines 200-228 Link Here
200
            .addGroup(layout.createSequentialGroup()
157
            .addGroup(layout.createSequentialGroup()
201
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
158
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
202
                    .addGroup(layout.createSequentialGroup()
159
                    .addGroup(layout.createSequentialGroup()
203
                        .addContainerGap()
160
                        .addGap(163, 163, 163)
204
                        .addComponent(lCollapseByDefault)
205
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
206
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
161
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
207
                            .addComponent(cbUseCodeFolding)
208
                            .addGroup(layout.createSequentialGroup()
162
                            .addGroup(layout.createSequentialGroup()
209
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
163
                                .addGap(175, 175, 175)
210
                                    .addComponent(cbFoldMethods)
164
                                .addComponent(cbBraceTooltip))
211
                                    .addComponent(cbFoldInnerClasses)
212
                                    .addComponent(cbFoldImports))
213
                                .addGap(18, 18, 18)
214
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
215
                                    .addComponent(cbFoldJavadocComments)
216
                                    .addComponent(cbFoldInitialComments)
217
                                    .addComponent(cbFoldTags)
218
                                    .addComponent(cbBraceTooltip)))
219
                            .addComponent(cbCamelCaseBehavior)
165
                            .addComponent(cbCamelCaseBehavior)
220
                            .addGroup(layout.createSequentialGroup()
166
                            .addGroup(layout.createSequentialGroup()
221
                                .addGap(21, 21, 21)
167
                                .addGap(21, 21, 21)
222
                                .addComponent(lCamelCaseBehaviorExample, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
168
                                .addComponent(lCamelCaseBehaviorExample, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
223
                    .addGroup(layout.createSequentialGroup()
169
                        .addGap(26, 26, 26))
224
                        .addGap(12, 12, 12)
225
                        .addComponent(lUseCodeFolding))
226
                    .addGroup(layout.createSequentialGroup()
170
                    .addGroup(layout.createSequentialGroup()
227
                        .addContainerGap()
171
                        .addContainerGap()
228
                        .addComponent(lEditorSearchType)
172
                        .addComponent(lEditorSearchType)
Lines 232-241 Link Here
232
                            .addComponent(cboEditorSearchType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
176
                            .addComponent(cboEditorSearchType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))))
233
                .addGap(0, 12, Short.MAX_VALUE))
177
                .addGap(0, 12, Short.MAX_VALUE))
234
            .addGroup(layout.createSequentialGroup()
178
            .addGroup(layout.createSequentialGroup()
235
                .addComponent(lCodeFolding)
236
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
237
                .addComponent(jSeparator1))
238
            .addGroup(layout.createSequentialGroup()
239
                .addComponent(lBracesMatching)
179
                .addComponent(lBracesMatching)
240
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
180
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
241
                .addComponent(jSeparator6))
181
                .addComponent(jSeparator6))
Lines 248-281 Link Here
248
                        .addGap(0, 0, Short.MAX_VALUE))
188
                        .addGap(0, 0, Short.MAX_VALUE))
249
                    .addComponent(jSeparator3)))
189
                    .addComponent(jSeparator3)))
250
        );
190
        );
251
252
        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cbFoldImports, cbFoldInitialComments, cbFoldInnerClasses, cbFoldJavadocComments, cbFoldMethods});
253
254
        layout.setVerticalGroup(
191
        layout.setVerticalGroup(
255
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
192
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
256
            .addGroup(layout.createSequentialGroup()
193
            .addGroup(layout.createSequentialGroup()
257
                .addContainerGap()
258
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
259
                    .addComponent(lCodeFolding)
260
                    .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
261
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
262
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
263
                    .addComponent(cbUseCodeFolding)
264
                    .addComponent(lUseCodeFolding))
265
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
266
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
267
                    .addComponent(lCollapseByDefault)
268
                    .addComponent(cbFoldMethods)
269
                    .addComponent(cbFoldJavadocComments))
270
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
271
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
272
                    .addComponent(cbFoldInnerClasses)
273
                    .addComponent(cbFoldInitialComments))
274
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
275
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
276
                    .addComponent(cbFoldImports)
277
                    .addComponent(cbFoldTags))
278
                .addGap(18, 18, 18)
279
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
194
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
280
                    .addComponent(lBracesMatching)
195
                    .addComponent(lBracesMatching)
281
                    .addComponent(jSeparator6, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
196
                    .addComponent(jSeparator6, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE))
Lines 301-307 Link Here
301
                    .addComponent(cboEditorSearchType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
216
                    .addComponent(cboEditorSearchType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
302
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
217
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
303
                .addComponent(lSearchtypeTooltip, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
218
                .addComponent(lSearchtypeTooltip, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
304
                .addContainerGap())
219
                .addGap(0, 12, Short.MAX_VALUE))
305
        );
220
        );
306
    }// </editor-fold>//GEN-END:initComponents
221
    }// </editor-fold>//GEN-END:initComponents
307
    
222
    
Lines 309-336 Link Here
309
    // Variables declaration - do not modify//GEN-BEGIN:variables
224
    // Variables declaration - do not modify//GEN-BEGIN:variables
310
    private javax.swing.JCheckBox cbBraceTooltip;
225
    private javax.swing.JCheckBox cbBraceTooltip;
311
    private javax.swing.JCheckBox cbCamelCaseBehavior;
226
    private javax.swing.JCheckBox cbCamelCaseBehavior;
312
    private javax.swing.JCheckBox cbFoldImports;
313
    private javax.swing.JCheckBox cbFoldInitialComments;
314
    private javax.swing.JCheckBox cbFoldInnerClasses;
315
    private javax.swing.JCheckBox cbFoldJavadocComments;
316
    private javax.swing.JCheckBox cbFoldMethods;
317
    private javax.swing.JCheckBox cbFoldTags;
318
    private javax.swing.JCheckBox cbShowBraceOutline;
227
    private javax.swing.JCheckBox cbShowBraceOutline;
319
    private javax.swing.JCheckBox cbUseCodeFolding;
320
    private javax.swing.JComboBox cboEditorSearchType;
228
    private javax.swing.JComboBox cboEditorSearchType;
321
    private javax.swing.JSeparator jSeparator1;
322
    private javax.swing.JSeparator jSeparator3;
229
    private javax.swing.JSeparator jSeparator3;
323
    private javax.swing.JSeparator jSeparator5;
230
    private javax.swing.JSeparator jSeparator5;
324
    private javax.swing.JSeparator jSeparator6;
231
    private javax.swing.JSeparator jSeparator6;
325
    private javax.swing.JLabel lBracesMatching;
232
    private javax.swing.JLabel lBracesMatching;
326
    private javax.swing.JLabel lCamelCaseBehavior;
233
    private javax.swing.JLabel lCamelCaseBehavior;
327
    private javax.swing.JLabel lCamelCaseBehaviorExample;
234
    private javax.swing.JLabel lCamelCaseBehaviorExample;
328
    private javax.swing.JLabel lCodeFolding;
329
    private javax.swing.JLabel lCollapseByDefault;
330
    private javax.swing.JLabel lEditorSearchType;
235
    private javax.swing.JLabel lEditorSearchType;
331
    private javax.swing.JLabel lSearch;
236
    private javax.swing.JLabel lSearch;
332
    private javax.swing.JLabel lSearchtypeTooltip;
237
    private javax.swing.JLabel lSearchtypeTooltip;
333
    private javax.swing.JLabel lUseCodeFolding;
334
    // End of variables declaration//GEN-END:variables
238
    // End of variables declaration//GEN-END:variables
335
    
239
    
336
    
240
    
Lines 362-389 Link Here
362
        listen = false;
266
        listen = false;
363
        if (model == null) {
267
        if (model == null) {
364
            model = new Model ();
268
            model = new Model ();
365
            cbUseCodeFolding.addActionListener (this);
366
            cbFoldMethods.addActionListener (this);
367
            cbFoldInnerClasses.addActionListener (this);
368
            cbFoldImports.addActionListener (this);
369
            cbFoldJavadocComments.addActionListener (this);
370
            cbFoldInitialComments.addActionListener (this);
371
            cbCamelCaseBehavior.addActionListener (this);
269
            cbCamelCaseBehavior.addActionListener (this);
372
            cbFoldTags.addActionListener (this);
373
            cboEditorSearchType.addActionListener(this);
270
            cboEditorSearchType.addActionListener(this);
374
            cbBraceTooltip.addActionListener(this);
271
            cbBraceTooltip.addActionListener(this);
375
            cbShowBraceOutline.addActionListener(this);
272
            cbShowBraceOutline.addActionListener(this);
376
        }
273
        }
377
        
274
        
378
        // init code folding
379
        cbUseCodeFolding.setSelected (model.isShowCodeFolding ());
380
        cbFoldImports.setSelected (model.isFoldImports ());
381
        cbFoldInitialComments.setSelected (model.isFoldInitialComment ());
382
        cbFoldInnerClasses.setSelected (model.isFoldInnerClasses ());
383
        cbFoldJavadocComments.setSelected (model.isFoldJavaDocComments ());
384
        cbFoldMethods.setSelected (model.isFoldMethods ());
385
        cbFoldTags.setSelected (model.isFoldTag());
386
387
        // Java Camel Case Navigation
275
        // Java Camel Case Navigation
388
        Boolean ccJava = model.isCamelCaseJavaNavigation();
276
        Boolean ccJava = model.isCamelCaseJavaNavigation();
389
        if ( ccJava == null ) {
277
        if ( ccJava == null ) {
Lines 400-407 Link Here
400
        cbBraceTooltip.setSelected(model.isBraceTooltip());
288
        cbBraceTooltip.setSelected(model.isBraceTooltip());
401
        cbShowBraceOutline.setSelected(model.isBraceOutline());
289
        cbShowBraceOutline.setSelected(model.isBraceOutline());
402
290
403
        updateEnabledState ();
404
        
405
        listen = true;
291
        listen = true;
406
    }
292
    }
407
    
293
    
Lines 409-425 Link Here
409
        
295
        
410
        if (model == null || !changed) return;
296
        if (model == null || !changed) return;
411
        
297
        
412
        // code folding options
413
        model.setFoldingOptions (
414
            cbUseCodeFolding.isSelected (),
415
            cbFoldImports.isSelected (),
416
            cbFoldInitialComments.isSelected (),
417
            cbFoldInnerClasses.isSelected (),
418
            cbFoldJavadocComments.isSelected (),
419
            cbFoldMethods.isSelected (),
420
            cbFoldTags.isSelected ()
421
        );
422
        
423
        // java camel case navigation
298
        // java camel case navigation
424
        model.setCamelCaseNavigation(cbCamelCaseBehavior.isSelected());
299
        model.setCamelCaseNavigation(cbCamelCaseBehavior.isSelected());
425
        
300
        
Lines 446-470 Link Here
446
    @Override
321
    @Override
447
    public void actionPerformed (ActionEvent e) {
322
    public void actionPerformed (ActionEvent e) {
448
        if (!listen) return;
323
        if (!listen) return;
449
        if (e.getSource () == cbUseCodeFolding) {
450
            updateEnabledState ();
451
        }
452
        changed = true;
324
        changed = true;
453
    }
325
    }
454
    
326
    
455
    
327
    
456
    // other methods ...........................................................
328
    // other methods ...........................................................
457
    
329
    
458
    private void updateEnabledState () {
459
        boolean useCodeFolding = cbUseCodeFolding.isSelected ();
460
        cbFoldImports.setEnabled (useCodeFolding);
461
        cbFoldInitialComments.setEnabled (useCodeFolding);
462
        cbFoldInnerClasses.setEnabled (useCodeFolding);
463
        cbFoldJavadocComments.setEnabled (useCodeFolding);
464
        cbFoldMethods.setEnabled (useCodeFolding);
465
        cbFoldTags.setEnabled(useCodeFolding);
466
    }
467
468
    private static final class EditorSearchTypeRenderer implements ListCellRenderer {
330
    private static final class EditorSearchTypeRenderer implements ListCellRenderer {
469
331
470
        private final ListCellRenderer defaultRenderer;
332
        private final ListCellRenderer defaultRenderer;
(-)a/options.editor/src/org/netbeans/modules/options/indentation/ProxyPreferences.java (-845 / +170 lines)
Lines 44-525 Link Here
44
44
45
import java.io.IOException;
45
import java.io.IOException;
46
import java.io.OutputStream;
46
import java.io.OutputStream;
47
import java.lang.ref.Reference;
48
import java.lang.ref.WeakReference;
49
import java.util.ArrayList;
50
import java.util.Arrays;
51
import java.util.Collection;
52
import java.util.Collections;
53
import java.util.EventObject;
54
import java.util.HashMap;
47
import java.util.HashMap;
55
import java.util.HashSet;
48
import java.util.HashSet;
56
import java.util.LinkedList;
57
import java.util.List;
58
import java.util.Map;
49
import java.util.Map;
59
import java.util.Set;
50
import java.util.Set;
60
import java.util.WeakHashMap;
61
import java.util.logging.Level;
62
import java.util.logging.Logger;
63
import java.util.prefs.BackingStoreException;
51
import java.util.prefs.BackingStoreException;
64
import java.util.prefs.NodeChangeEvent;
52
import java.util.prefs.NodeChangeEvent;
65
import java.util.prefs.NodeChangeListener;
53
import java.util.prefs.NodeChangeListener;
66
import java.util.prefs.PreferenceChangeEvent;
54
import java.util.prefs.PreferenceChangeEvent;
67
import java.util.prefs.PreferenceChangeListener;
55
import java.util.prefs.PreferenceChangeListener;
68
import java.util.prefs.Preferences;
56
import java.util.prefs.Preferences;
69
import javax.xml.bind.DatatypeConverter;
57
import org.netbeans.modules.editor.settings.storage.api.MemoryPreferences;
70
import org.netbeans.modules.editor.settings.storage.spi.TypedValue;
71
import org.openide.util.WeakListeners;
58
import org.openide.util.WeakListeners;
72
59
73
/**
60
/**
74
 *
61
 * This class has been obsoleted by {@link MemoryPreferences}
62
 * 
75
 * @author vita
63
 * @author vita
64
 * @deprecated Please use {@link MemoryPreferences} API instead
76
 */
65
 */
77
public final class ProxyPreferences extends Preferences implements PreferenceChangeListener, NodeChangeListener {
66
public final class ProxyPreferences extends Preferences implements PreferenceChangeListener, NodeChangeListener {
67
    
68
    private final MemoryPreferences delegateRoot;
69
    private Preferences       delegate;
70
    private boolean noEvents;
71
    
72
    public static ProxyPreferences getProxyPreferences(Object token, Preferences delegate) {
73
        return new ProxyPreferences(null, MemoryPreferences.get(token, delegate), null);
74
    }
78
75
79
    public static ProxyPreferences getProxyPreferences(Object token, Preferences delegate) {
76
    public void silence() {
80
        return Tree.getTree(token, delegate).get(null, delegate.name(), delegate); //NOI18N
77
        synchronized (delegate) {
78
            this.noEvents = true;
79
        }
80
    }
81
82
    public void destroy() {
83
        delegateRoot.destroy();
81
    }
84
    }
82
85
83
    @Override
86
    @Override
84
    public void put(String key, String value) {
87
    public void put(String key, String value) {
85
        _put(key, value, String.class.getName());
88
        delegate.put(key, value);
86
    }
89
    }
87
90
88
    @Override
91
    @Override
89
    public String get(String key, String def) {
92
    public String get(String key, String def) {
90
        synchronized (tree.treeLock()) {
93
        return delegate.get(key, def);
91
            checkNotNull(key, "key"); //NOI18N
94
    }
92
            checkRemoved();
95
93
            
96
    @Override
94
            if (removedKeys.contains(key)) {
97
    public void remove(String key) {
95
                if (LOG.isLoggable(Level.FINE)) {
98
        delegate.remove(key);
96
                    LOG.fine("Key '" + key + "' removed, using default '" + def + "'"); //NOI18N
99
    }
97
                }
100
98
                return def;
101
    @Override
99
            } else {
102
    public void clear() throws BackingStoreException {
100
                TypedValue typedValue = data.get(key);
103
        delegate.clear();
101
                if (typedValue != null) {
104
    }
102
                    if (LOG.isLoggable(Level.FINE)) {
105
103
                        LOG.fine("Key '" + key + "' modified, local value '" + typedValue.getValue() + "'"); //NOI18N
106
    @Override
104
                    }
107
    public void putInt(String key, int value) {
105
                    return typedValue.getValue();
108
        delegate.putInt(key, value);
106
                } else if (delegate != null) {
109
    }
107
                    String value = delegate.get(key, def);
110
108
                    if (LOG.isLoggable(Level.FINE)) {
111
    @Override
109
                        LOG.fine("Key '" + key + "' undefined, original value '" + value + "'"); //NOI18N
112
    public int getInt(String key, int def) {
110
                    }
113
        return delegate.getInt(key, def);
111
                    return value;
114
    }
112
                } else {
115
113
                    if (LOG.isLoggable(Level.FINE)) {
116
    @Override
114
                        LOG.fine("Key '" + key + "' undefined, '" + name + "' is a new node, using default '" + def + "'"); //NOI18N
117
    public void putLong(String key, long value) {
115
                    }
118
        delegate.putLong(key, value);
116
                    return def;
119
    }
117
                }
120
121
    @Override
122
    public long getLong(String key, long def) {
123
        return delegate.getLong(key, def);
124
    }
125
126
    @Override
127
    public void putBoolean(String key, boolean value) {
128
        delegate.putBoolean(key, value);
129
    }
130
131
    @Override
132
    public boolean getBoolean(String key, boolean def) {
133
        return delegate.getBoolean(key, def);
134
    }
135
136
    @Override
137
    public void putFloat(String key, float value) {
138
        delegate.putFloat(key, value);
139
    }
140
141
    @Override
142
    public float getFloat(String key, float def) {
143
        return delegate.getFloat(key, def);
144
    }
145
146
    @Override
147
    public void putDouble(String key, double value) {
148
        delegate.putDouble(key, value);
149
    }
150
151
    @Override
152
    public double getDouble(String key, double def) {
153
        return delegate.getDouble(key, def);
154
    }
155
156
    @Override
157
    public void putByteArray(String key, byte[] value) {
158
        delegate.putByteArray(key, value);
159
    }
160
161
    @Override
162
    public byte[] getByteArray(String key, byte[] def) {
163
        return delegate.getByteArray(key, def);
164
    }
165
166
    @Override
167
    public String[] keys() throws BackingStoreException {
168
        return delegate.keys();
169
    }
170
171
    @Override
172
    public String[] childrenNames() throws BackingStoreException {
173
        return delegate.childrenNames();
174
    }
175
176
    @Override
177
    public Preferences parent() {
178
        return parent;
179
    }
180
181
    @Override
182
    public Preferences node(String pathName) {
183
        synchronized (this) {
184
            Preferences pref = children.get(pathName);
185
            if (pref != null) {
186
                return pref;
118
            }
187
            }
119
        }
188
        }
189
        Preferences pref = delegate.node(pathName);
190
        ProxyPreferences result;
191
        
192
        synchronized (this) {
193
            result = children.get(pathName);
194
            if (result != null) {
195
                return result;
196
            }
197
            result = new ProxyPreferences(this, delegateRoot, pref);
198
            children.put(pathName, result);
199
            return result;
200
        }
120
    }
201
    }
121
202
122
    @Override
203
    @Override
123
    public void remove(String key) {
124
        EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag = null;
125
        
126
        synchronized (tree.treeLock()) {
127
            checkNotNull(key, "key"); //NOI18N
128
            checkRemoved();
129
            
130
            if (removedKeys.add(key)) {
131
                data.remove(key);
132
                bag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
133
                bag.addListeners(prefListeners);
134
                bag.addEvent(new PreferenceChangeEvent(this, key, null));
135
            }
136
        }
137
138
        if (bag != null) {
139
            firePrefEvents(Collections.singletonList(bag));
140
        }
141
    }
142
143
    @Override
144
    public void clear() throws BackingStoreException {
145
        EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
146
        
147
        synchronized (tree.treeLock()) {
148
            checkRemoved();
149
150
            // Determine modified or added keys
151
            Set<String> keys = new HashSet<String>();
152
            keys.addAll(data.keySet());
153
            keys.removeAll(removedKeys);
154
            if (!keys.isEmpty()) {
155
                for(String key : keys) {
156
                    String value = delegate == null ? null : delegate.get(key, null);
157
                    bag.addEvent(new PreferenceChangeEvent(this, key, value));
158
                }
159
            }
160
161
            // Determine removed keys
162
            if (delegate != null) {
163
                for(String key : removedKeys) {
164
                    String value = delegate.get(key, null);
165
                    if (value != null) {
166
                        bag.addEvent(new PreferenceChangeEvent(this, key, value));
167
                    }
168
                }
169
            }
170
171
            // Initialize bag's listeners
172
            bag.addListeners(prefListeners);
173
174
            // Finally, remove the data
175
            data.clear();
176
            removedKeys.clear();
177
        }
178
        
179
        firePrefEvents(Collections.singletonList(bag));
180
    }
181
182
    @Override
183
    public void putInt(String key, int value) {
184
        _put(key, Integer.toString(value), Integer.class.getName());
185
    }
186
187
    @Override
188
    public int getInt(String key, int def) {
189
        String value = get(key, null);
190
        if (value != null) {
191
            try {
192
                return Integer.parseInt(value);
193
            } catch (NumberFormatException nfe) {
194
                // ignore
195
            }
196
        }
197
        return def;
198
    }
199
200
    @Override
201
    public void putLong(String key, long value) {
202
        _put(key, Long.toString(value), Long.class.getName());
203
    }
204
205
    @Override
206
    public long getLong(String key, long def) {
207
        String value = get(key, null);
208
        if (value != null) {
209
            try {
210
                return Long.parseLong(value);
211
            } catch (NumberFormatException nfe) {
212
                // ignore
213
            }
214
        }
215
        return def;
216
    }
217
218
    @Override
219
    public void putBoolean(String key, boolean value) {
220
        _put(key, Boolean.toString(value), Boolean.class.getName());
221
    }
222
223
    @Override
224
    public boolean getBoolean(String key, boolean def) {
225
        String value = get(key, null);
226
        if (value != null) {
227
            return Boolean.parseBoolean(value);
228
        } else {
229
            return def;
230
        }
231
    }
232
233
    @Override
234
    public void putFloat(String key, float value) {
235
        _put(key, Float.toString(value), Float.class.getName());
236
    }
237
238
    @Override
239
    public float getFloat(String key, float def) {
240
        String value = get(key, null);
241
        if (value != null) {
242
            try {
243
                return Float.parseFloat(value);
244
            } catch (NumberFormatException nfe) {
245
                // ignore
246
            }
247
        }
248
        return def;
249
    }
250
251
    @Override
252
    public void putDouble(String key, double value) {
253
        _put(key, Double.toString(value), Double.class.getName());
254
    }
255
256
    @Override
257
    public double getDouble(String key, double def) {
258
        String value = get(key, null);
259
        if (value != null) {
260
            try {
261
                return Double.parseDouble(value);
262
            } catch (NumberFormatException nfe) {
263
                // ignore
264
            }
265
        }
266
        return def;
267
    }
268
269
    @Override
270
    public void putByteArray(String key, byte[] value) {
271
        _put(key, DatatypeConverter.printBase64Binary(value), value.getClass().getName());
272
    }
273
274
    @Override
275
    public byte[] getByteArray(String key, byte[] def) {
276
        String value = get(key, null);
277
        if (value != null) {
278
            byte [] decoded = DatatypeConverter.parseBase64Binary(value);
279
            if (decoded != null) {
280
                return decoded;
281
            }
282
        }
283
        return def;
284
    }
285
286
    @Override
287
    public String[] keys() throws BackingStoreException {
288
        synchronized (tree.treeLock()) {
289
            checkRemoved();
290
            HashSet<String> keys = new HashSet<String>();
291
            if (delegate != null) {
292
                keys.addAll(Arrays.asList(delegate.keys()));
293
            }
294
            keys.addAll(data.keySet());
295
            keys.removeAll(removedKeys);
296
            return keys.toArray(new String [keys.size()]);
297
        }
298
    }
299
300
    @Override
301
    public String[] childrenNames() throws BackingStoreException {
302
        synchronized (tree.treeLock()) {
303
            checkRemoved();
304
            HashSet<String> names = new HashSet<String>();
305
            if (delegate != null) {
306
                names.addAll(Arrays.asList(delegate.childrenNames()));
307
            }
308
            names.addAll(children.keySet());
309
            names.removeAll(removedChildren);
310
            return names.toArray(new String [names.size()]);
311
        }
312
    }
313
314
    @Override
315
    public Preferences parent() {
316
        synchronized (tree.treeLock()) {
317
            checkRemoved();
318
            return parent;
319
        }
320
    }
321
322
    @Override
323
    public Preferences node(String pathName) {
324
        Preferences node;
325
        LinkedList<EventBag<NodeChangeListener, NodeChangeEvent>> events = new LinkedList<EventBag<NodeChangeListener, NodeChangeEvent>>();
326
327
        synchronized (tree.treeLock()) {
328
            checkNotNull(pathName, "pathName"); //NOI18N
329
            checkRemoved();
330
            node = node(pathName, true, events);
331
        }
332
333
        fireNodeEvents(events);
334
        return node;
335
    }
336
337
    @Override
338
    public boolean nodeExists(String pathName) throws BackingStoreException {
204
    public boolean nodeExists(String pathName) throws BackingStoreException {
339
        synchronized (tree.treeLock()) {
205
        return delegate.nodeExists(pathName);
340
            if (pathName.length() == 0) {
341
                return !removed;
342
            } else {
343
                checkRemoved();
344
                return node(pathName, false, null) != null;
345
            }
346
        }
347
    }
206
    }
348
207
349
    @Override
208
    @Override
350
    public void removeNode() throws BackingStoreException {
209
    public void removeNode() throws BackingStoreException {
351
        synchronized (tree.treeLock()) {
210
        delegate.removeNode();
352
            checkRemoved();
211
        if (parent != null) {
353
            ProxyPreferences p = parent;
212
            parent.nodeRemoved(this);
354
            if (p != null) {
355
                p.removeChild(this);
356
            } else {
357
                throw new UnsupportedOperationException("Can't remove the root."); //NOI18N
358
            }
359
        }
213
        }
360
    }
214
    }
361
215
362
    @Override
216
    @Override
363
    public String name() {
217
    public String name() {
364
        return name;
218
        return delegate.name();
365
    }
219
    }
366
220
367
    @Override
221
    @Override
368
    public String absolutePath() {
222
    public String absolutePath() {
369
        synchronized (tree.treeLock()) {
223
        return delegate.absolutePath();
370
            ProxyPreferences pp = parent;
371
            if (pp != null) {
372
                if (pp.parent == null) {
373
                    // pp is the root, we don't want two consecutive slashes in the path
374
                    return "/" + name(); //NOI18N
375
                } else {
376
                    return pp.absolutePath() + "/" + name(); //NOI18N
377
                }
378
            } else {
379
                return "/"; //NOI18N
380
            }
381
        }
382
    }
224
    }
383
225
384
    @Override
226
    @Override
385
    public boolean isUserNode() {
227
    public boolean isUserNode() {
386
        synchronized (tree.treeLock()) {
228
        return delegate.isUserNode();
387
            if (delegate != null) {
388
                return delegate.isUserNode();
389
            } else {
390
                ProxyPreferences pp = parent;
391
                if (pp != null) {
392
                    return pp.isUserNode();
393
                } else {
394
                    return true;
395
                }
396
            }
397
        }
398
    }
229
    }
399
230
400
    @Override
231
    @Override
401
    public String toString() {
232
    public String toString() {
402
        return (isUserNode() ? "User" : "System") + " Preference Node: " + absolutePath(); //NOI18N
233
        return delegate.toString();
403
    }
234
    }
404
235
405
    @Override
236
    @Override
406
    public void flush() throws BackingStoreException {
237
    public void flush() throws BackingStoreException {
407
        synchronized (tree.treeLock()) {
238
        delegate.flush();
408
            if (LOG.isLoggable(Level.FINE)) {
409
                LOG.fine("Flushing " + absolutePath());
410
            }
411
412
            checkRemoved();
413
            for(ProxyPreferences pp : children.values()) {
414
                pp.flush();
415
            }
416
417
            if (delegate == null) {
418
                ProxyPreferences proxyRoot = parent.node("/", false, null); //NOI18N
419
                assert proxyRoot != null : "Root must always exist"; //NOI18N
420
421
                Preferences delegateRoot = proxyRoot.delegate;
422
                assert delegateRoot != null : "Root must always have its corresponding delegate"; //NOI18N
423
424
                Preferences nueDelegate = delegateRoot.node(absolutePath());
425
                changeDelegate(nueDelegate);
426
            }
427
428
            delegate.removeNodeChangeListener(weakNodeListener);
429
            delegate.removePreferenceChangeListener(weakPrefListener);
430
            try {
431
                // remove all removed children
432
                for(String childName : removedChildren) {
433
                    if (delegate.nodeExists(childName)) {
434
                        delegate.node(childName).removeNode();
435
                    }
436
                }
437
438
                // write all valid key-value pairs
439
                for(String key : data.keySet()) {
440
                    if (!removedKeys.contains(key)) {
441
                        if (LOG.isLoggable(Level.FINE)) {
442
                            LOG.fine("Writing " + absolutePath() + "/" + key + "=" + data.get(key));
443
                        }
444
                        
445
                        TypedValue typedValue = data.get(key);
446
                        if (String.class.getName().equals(typedValue.getJavaType())) {
447
                            delegate.put(key, typedValue.getValue());
448
449
                        } else if (Integer.class.getName().equals(typedValue.getJavaType())) {
450
                            delegate.putInt(key, Integer.parseInt(typedValue.getValue()));
451
452
                        } else if (Long.class.getName().equals(typedValue.getJavaType())) {
453
                            delegate.putLong(key, Long.parseLong(typedValue.getValue()));
454
455
                        } else if (Boolean.class.getName().equals(typedValue.getJavaType())) {
456
                            delegate.putBoolean(key, Boolean.parseBoolean(typedValue.getValue()));
457
458
                        } else if (Float.class.getName().equals(typedValue.getJavaType())) {
459
                            delegate.putFloat(key, Float.parseFloat(typedValue.getValue()));
460
461
                        } else if (Double.class.getName().equals(typedValue.getJavaType())) {
462
                            delegate.putDouble(key, Double.parseDouble(typedValue.getValue()));
463
464
                        } else {
465
                            delegate.putByteArray(key, DatatypeConverter.parseBase64Binary(typedValue.getValue()));
466
                        }
467
                    }
468
                }
469
                data.clear();
470
471
                // remove all removed keys
472
                for(String key : removedKeys) {
473
                    if (LOG.isLoggable(Level.FINE)) {
474
                        LOG.fine("Removing " + absolutePath() + "/" + key);
475
                    }
476
                    delegate.remove(key);
477
                }
478
                removedKeys.clear();
479
            } finally {
480
                delegate.addNodeChangeListener(weakNodeListener);
481
                delegate.addPreferenceChangeListener(weakPrefListener);
482
            }
483
        }        
484
    }
239
    }
485
240
486
    @Override
241
    @Override
487
    public void sync() throws BackingStoreException {
242
    public void sync() throws BackingStoreException {
488
        ArrayList<EventBag<PreferenceChangeListener, PreferenceChangeEvent>> prefEvents = new ArrayList<EventBag<PreferenceChangeListener, PreferenceChangeEvent>>();
243
        delegate.sync();
489
        ArrayList<EventBag<NodeChangeListener, NodeChangeEvent>> nodeEvents = new ArrayList<EventBag<NodeChangeListener, NodeChangeEvent>>();
490
491
        synchronized (tree.treeLock()) {
492
            _sync(prefEvents, nodeEvents);
493
        }
494
495
        fireNodeEvents(nodeEvents);
496
        firePrefEvents(prefEvents);
497
    }
244
    }
498
245
    
499
    @Override
246
    @Override
500
    public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
247
    public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
501
        synchronized (tree.treeLock()) {
248
        synchronized (this) {
502
            prefListeners.add(pcl);
249
            prefListeners.add(pcl);
503
        }
250
        }
504
    }
251
    }
505
252
506
    @Override
253
    @Override
507
    public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
254
    public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
508
        synchronized (tree.treeLock()) {
255
        synchronized (this) {
509
            prefListeners.remove(pcl);
256
            prefListeners.remove(pcl);
510
        }
257
        }
511
    }
258
    }
512
259
513
    @Override
260
    @Override
514
    public void addNodeChangeListener(NodeChangeListener ncl) {
261
    public void addNodeChangeListener(NodeChangeListener ncl) {
515
        synchronized (tree.treeLock()) {
262
        synchronized (this) {
516
            nodeListeners.add(ncl);
263
            nodeListeners.add(ncl);
517
        }
264
        }
518
    }
265
    }
519
266
520
    @Override
267
    @Override
521
    public void removeNodeChangeListener(NodeChangeListener ncl) {
268
    public void removeNodeChangeListener(NodeChangeListener ncl) {
522
        synchronized (tree.treeLock()) {
269
        synchronized (this) {
523
            nodeListeners.remove(ncl);
270
            nodeListeners.remove(ncl);
524
        }
271
        }
525
    }
272
    }
Lines 540-550 Link Here
540
287
541
    public void preferenceChange(PreferenceChangeEvent evt) {
288
    public void preferenceChange(PreferenceChangeEvent evt) {
542
        PreferenceChangeListener [] listeners;
289
        PreferenceChangeListener [] listeners;
543
        synchronized (tree.treeLock()) {
290
        synchronized (this) {
544
            if (removed || removedKeys.contains(evt.getKey()) || data.containsKey(evt.getKey())) {
291
            if (!noEvents) {
545
                return;
292
                return;
546
            }
293
            }
547
548
            listeners = prefListeners.toArray(new PreferenceChangeListener[prefListeners.size()]);
294
            listeners = prefListeners.toArray(new PreferenceChangeListener[prefListeners.size()]);
549
        }
295
        }
550
296
Lines 556-561 Link Here
556
            l.preferenceChange(myEvt);
302
            l.preferenceChange(myEvt);
557
        }
303
        }
558
    }
304
    }
305
    
306
    private synchronized void changeDelegate(Preferences del) {
307
        this.delegate = del;
308
    }
309
    
310
    private synchronized void nodeRemoved(ProxyPreferences child) {
311
        children.values().remove(child);
312
    }
559
313
560
    // ------------------------------------------------------------------------
314
    // ------------------------------------------------------------------------
561
    // NodeChangeListener implementation
315
    // NodeChangeListener implementation
Lines 565-576 Link Here
565
        NodeChangeListener [] listeners;
319
        NodeChangeListener [] listeners;
566
        Preferences childNode;
320
        Preferences childNode;
567
321
568
        synchronized (tree.treeLock()) {
322
        synchronized (this) {
569
            String childName = evt.getChild().name();
323
            String childName = evt.getChild().name();
570
            if (removed || removedChildren.contains(childName)) {
571
                return;
572
            }
573
574
            childNode = children.get(childName);
324
            childNode = children.get(childName);
575
            if (childNode != null) {
325
            if (childNode != null) {
576
                // swap delegates
326
                // swap delegates
Lines 578-584 Link Here
578
            } else {
328
            } else {
579
                childNode = node(evt.getChild().name());
329
                childNode = node(evt.getChild().name());
580
            }
330
            }
581
            
331
            if (!noEvents) {
332
                return;
333
            }
582
            listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
334
            listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
583
        }
335
        }
584
336
Lines 595-615 Link Here
595
        NodeChangeListener [] listeners;
347
        NodeChangeListener [] listeners;
596
        Preferences childNode;
348
        Preferences childNode;
597
349
598
        synchronized (tree.treeLock()) {
350
        synchronized (this) {
599
            String childName = evt.getChild().name();
351
            String childName = evt.getChild().name();
600
            if (removed || removedChildren.contains(childName)) {
601
                return;
602
            }
603
352
604
            childNode = children.get(childName);
353
            childNode = children.get(childName);
605
            if (childNode != null) {
354
            if (childNode == null) {
606
                // swap delegates
607
                ((ProxyPreferences) childNode).changeDelegate(null);
608
            } else {
609
                // nobody has accessed the child yet
355
                // nobody has accessed the child yet
610
                return;
356
                return;
611
            }
357
            }
612
            
358
            
359
            if (!noEvents) {
360
                return;
361
            }
613
            listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
362
            listeners = nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
614
        }
363
        }
615
364
Lines 622-1066 Link Here
622
        }
371
        }
623
    }
372
    }
624
    
373
    
625
    // ------------------------------------------------------------------------
374
    private final ProxyPreferences parent;
626
    // Other public implementation
375
    private final Map<String, ProxyPreferences> children = new HashMap<String, ProxyPreferences>();
627
    // ------------------------------------------------------------------------
628
376
629
    /**
630
     * Destroys whole preferences tree as if called on the root.
631
     */
632
    public void destroy() {
633
        synchronized (tree.treeLock()) {
634
            tree.destroy();
635
        }
636
    }
637
    
638
    public void silence() {
639
        synchronized (tree.treeLock()) {
640
            noEvents = true;
641
        }
642
    }
643
    
644
    // ------------------------------------------------------------------------
645
    // private implementation
646
    // ------------------------------------------------------------------------
647
648
    private static final Logger LOG = Logger.getLogger(ProxyPreferences.class.getName());
649
    
650
    private final ProxyPreferences parent;
651
    private final String name;
652
    private Preferences delegate;
653
    private final Tree tree;
654
    private boolean removed;
655
    
656
    private final Map<String, TypedValue> data = new HashMap<String, TypedValue>();
657
    private final Set<String> removedKeys = new HashSet<String>();
658
    private final Map<String, ProxyPreferences> children = new HashMap<String, ProxyPreferences>();
659
    private final Set<String> removedChildren = new HashSet<String>();
660
661
    private boolean noEvents = false;
662
    private PreferenceChangeListener weakPrefListener;
377
    private PreferenceChangeListener weakPrefListener;
663
    private final Set<PreferenceChangeListener> prefListeners = new HashSet<PreferenceChangeListener>();
378
    private final Set<PreferenceChangeListener> prefListeners = new HashSet<PreferenceChangeListener>();
664
    private NodeChangeListener weakNodeListener;
379
    private NodeChangeListener weakNodeListener;
665
    private final Set<NodeChangeListener> nodeListeners = new HashSet<NodeChangeListener>();
380
    private final Set<NodeChangeListener> nodeListeners = new HashSet<NodeChangeListener>();
666
381
667
    private ProxyPreferences(ProxyPreferences parent, String name, Preferences delegate, Tree tree) {
382
    private ProxyPreferences(ProxyPreferences parent, MemoryPreferences memoryPref, Preferences delegate) {
668
        assert name != null;
669
        
670
        this.parent = parent;
383
        this.parent = parent;
671
        this.name = name;
384
        this.delegateRoot = memoryPref;
672
        this.delegate = delegate;
385
        this.delegate = delegate == null ? memoryPref.getPreferences() : delegate;
673
        if (delegate != null) {
386
        weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
674
            assert name.equals(delegate.name());
387
        this.delegate.addPreferenceChangeListener(weakPrefListener);
675
388
        weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
676
            weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
389
        this.delegate.addNodeChangeListener(weakNodeListener);
677
            delegate.addPreferenceChangeListener(weakPrefListener);
678
            
679
            weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
680
            delegate.addNodeChangeListener(weakNodeListener);
681
        }
682
        this.tree = tree;
683
    }
390
    }
684
685
    private void _put(String key, String value, String javaType) {
686
        EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag = null;
687
688
        synchronized (tree.treeLock()) {
689
            checkNotNull(key, "key"); //NOI18N
690
            checkNotNull(value, "value"); //NOI18N
691
            checkRemoved();
692
            
693
            String orig = get(key, null);
694
            if (orig == null || !orig.equals(value)) {
695
                if (LOG.isLoggable(Level.FINE)) {
696
                    LOG.fine("Overwriting '" + key + "' = '" + value + "'"); //NOI18N
697
                }
698
                
699
                data.put(key, new TypedValue(value, javaType));
700
                removedKeys.remove(key);
701
                
702
                bag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
703
                bag.addListeners(prefListeners);
704
                bag.addEvent(new PreferenceChangeEvent(this, key, value));
705
            }
706
        }
707
708
        if (bag != null) {
709
            firePrefEvents(Collections.singletonList(bag));
710
        }
711
    }
712
713
    private ProxyPreferences node(String pathName, boolean create, List<EventBag<NodeChangeListener, NodeChangeEvent>> events) {
714
        if (pathName.length() > 0 && pathName.charAt(0) == '/') { //NOI18N
715
            // absolute path, if this is not the root then find the root
716
            // and pass the call to it
717
            if (parent != null) {
718
                Preferences root = this;
719
                while (root.parent() != null) {
720
                    root = root.parent();
721
                }
722
                return ((ProxyPreferences) root).node(pathName, create, events);
723
            } else {
724
                // this is the root, change the pathName to a relative path and proceed
725
                pathName = pathName.substring(1);
726
            }
727
        }
728
729
        if (pathName.length() > 0) {
730
            String childName;
731
            String pathFromChild;
732
733
            int idx = pathName.indexOf('/'); //NOI18N
734
            if (idx != -1) {
735
                childName = pathName.substring(0, idx);
736
                pathFromChild = pathName.substring(idx + 1);
737
            } else {
738
                childName = pathName;
739
                pathFromChild = null;
740
            }
741
742
            ProxyPreferences child = children.get(childName);
743
            if (child == null) {
744
                if (removedChildren.contains(childName) && !create) {
745
                    // this child has been removed
746
                    return null;
747
                }
748
                
749
                Preferences childDelegate = null;
750
                try {
751
                    if (delegate != null && delegate.nodeExists(childName)) {
752
                        childDelegate = delegate.node(childName);
753
                    }
754
                } catch (BackingStoreException bse) {
755
                    // ignore
756
                }
757
758
                if (childDelegate != null || create) {
759
                    child = tree.get(this, childName, childDelegate);
760
                    children.put(childName, child);
761
                    removedChildren.remove(childName);
762
763
                    // fire event if we really created the new child node
764
                    if (childDelegate == null) {
765
                        EventBag<NodeChangeListener, NodeChangeEvent> bag = new EventBag<NodeChangeListener, NodeChangeEvent>();
766
                        bag.addListeners(nodeListeners);
767
                        bag.addEvent(new NodeChangeEventExt(this, child, false));
768
                        events.add(bag);
769
                    }
770
                } else {
771
                    // childDelegate == null && !create
772
                    return null;
773
                }
774
            } else {
775
                assert !child.removed;
776
            }
777
778
            return pathFromChild != null ? child.node(pathFromChild, create, events) : child;
779
        } else {
780
            return this;
781
        }
782
    }
783
784
    private void addChild(ProxyPreferences child) {
785
        ProxyPreferences pp = children.get(child.name());
786
        if (pp == null) {
787
            children.put(child.name(), child);
788
        } else {
789
            assert pp == child;
790
        }
791
    }
792
    
793
    private void removeChild(ProxyPreferences child) {
794
        assert child != null;
795
        assert children.get(child.name()) == child;
796
797
        child.nodeRemoved();
798
        children.remove(child.name());
799
        removedChildren.add(child.name());
800
    }
801
    
802
    private void nodeRemoved() {
803
        for(ProxyPreferences pp : children.values()) {
804
            pp.nodeRemoved();
805
        }
806
807
        data.clear();
808
        removedKeys.clear();
809
        children.clear();
810
        removedChildren.clear();
811
        tree.removeNode(this);
812
        
813
        removed = true;
814
    }
815
    
816
    private void checkNotNull(Object paramValue, String paramName) {
817
        if (paramValue == null) {
818
            throw new NullPointerException("The " + paramName + " must not be null");
819
        }
820
    }
821
822
    private void checkRemoved() {
823
        if (removed) {
824
            throw new IllegalStateException("The node '" + this + " has already been removed."); //NOI18N
825
        }
826
    }
827
828
    private void changeDelegate(Preferences nueDelegate) {
829
        if (delegate != null) {
830
            try {
831
                if (delegate.nodeExists("")) { //NOI18N
832
                    assert weakPrefListener != null;
833
                    assert weakNodeListener != null;
834
                    delegate.removePreferenceChangeListener(weakPrefListener);
835
                    delegate.removeNodeChangeListener(weakNodeListener);
836
                }
837
            } catch (BackingStoreException bse) {
838
                LOG.log(Level.WARNING, null, bse);
839
            }
840
        }
841
842
        delegate = nueDelegate;
843
        weakPrefListener = null;
844
        weakNodeListener = null;
845
        
846
        if (delegate != null) {
847
            weakPrefListener = WeakListeners.create(PreferenceChangeListener.class, this, delegate);
848
            delegate.addPreferenceChangeListener(weakPrefListener);
849
            
850
            weakNodeListener = WeakListeners.create(NodeChangeListener.class, this, delegate);
851
            delegate.addNodeChangeListener(weakNodeListener);
852
        }
853
    }
854
855
    private void _sync(
856
        List<EventBag<PreferenceChangeListener, PreferenceChangeEvent>> prefEvents, 
857
        List<EventBag<NodeChangeListener, NodeChangeEvent>> nodeEvents
858
    ) {
859
        // synchronize all children firts
860
        for(ProxyPreferences pp : children.values()) {
861
            pp._sync(prefEvents, nodeEvents);
862
        }
863
864
        // report all new children as removed
865
        EventBag<NodeChangeListener, NodeChangeEvent> nodeBag = new EventBag<NodeChangeListener, NodeChangeEvent>();
866
        nodeBag.addListeners(nodeListeners);
867
868
        for(ProxyPreferences pp : children.values()) {
869
            if (pp.delegate == null) {
870
                // new node that does not have corresponding node in the original hierarchy
871
                nodeBag.addEvent(new NodeChangeEventExt(this, pp, true));
872
            }
873
        }
874
875
        if (!nodeBag.getEvents().isEmpty()) {
876
            nodeEvents.add(nodeBag);
877
        }
878
879
        // report all modified keys
880
        if (delegate != null) {
881
            EventBag<PreferenceChangeListener, PreferenceChangeEvent> prefBag = new EventBag<PreferenceChangeListener, PreferenceChangeEvent>();
882
            prefBag.addListeners(prefListeners);
883
            prefEvents.add(prefBag);
884
885
            for(String key : data.keySet()) {
886
                prefBag.addEvent(new PreferenceChangeEvent(this, key, delegate.get(key, data.get(key).getValue())));
887
            }
888
        } // else there is no corresponding node in the orig hierarchy and this node
889
          // will be reported as removed
890
891
        // erase modified data
892
        for(NodeChangeEvent nce : nodeBag.getEvents()) {
893
            children.remove(nce.getChild().name());
894
        }
895
        data.clear();
896
    }
897
898
    private void firePrefEvents(List<EventBag<PreferenceChangeListener, PreferenceChangeEvent>> events) {
899
        if (noEvents) {
900
            return;
901
        }
902
        
903
        for(EventBag<PreferenceChangeListener, PreferenceChangeEvent> bag : events) {
904
            for(PreferenceChangeEvent event : bag.getEvents()) {
905
                for(PreferenceChangeListener l : bag.getListeners()) {
906
                    try {
907
                        l.preferenceChange(event);
908
                    } catch (Throwable t) {
909
                        LOG.log(Level.WARNING, null, t);
910
                    }
911
                }
912
            }
913
        }
914
    }
915
916
    private void fireNodeEvents(List<EventBag<NodeChangeListener, NodeChangeEvent>> events) {
917
        if (noEvents) {
918
            return;
919
        }
920
        
921
        for(EventBag<NodeChangeListener, NodeChangeEvent> bag : events) {
922
            for(NodeChangeEvent event : bag.getEvents()) {
923
                for(NodeChangeListener l : bag.getListeners()) {
924
                    try {
925
                        if ((event instanceof NodeChangeEventExt) && ((NodeChangeEventExt) event).isRemovalEvent()) {
926
                            l.childRemoved(event);
927
                        } else {
928
                            l.childAdded(event);
929
                        }
930
                    } catch (Throwable t) {
931
                        LOG.log(Level.WARNING, null, t);
932
                    }
933
                }
934
            }
935
        }
936
    }
937
938
    /* test */ static final class Tree {
939
940
        public static Tree getTree(Object token, Preferences prefs) {
941
            synchronized (trees) {
942
                // find all trees for the token
943
                Map<Preferences, Tree> forest = trees.get(token);
944
                if (forest == null) {
945
                    forest = new HashMap<Preferences, Tree>();
946
                    trees.put(token, forest);
947
                }
948
949
                // find the tree for the prefs' root
950
                Preferences root = prefs.node("/"); //NOI18N
951
                Tree tree = forest.get(root);
952
                if (tree == null) {
953
                    tree = new Tree(token, root);
954
                    forest.put(root, tree);
955
                }
956
957
                return tree;
958
            }
959
        }
960
961
        /* test */ static final Map<Object, Map<Preferences, Tree>> trees = new WeakHashMap<Object, Map<Preferences, Tree>>();
962
963
        private final Preferences root;
964
        private final Reference<?> tokenRef;
965
        private final Map<String, ProxyPreferences> nodes = new HashMap<String, ProxyPreferences>();
966
        
967
        private Tree(Object token, Preferences root) {
968
            this.root = root;
969
            this.tokenRef = new WeakReference<Object>(token);
970
        }
971
972
        public Object treeLock() {
973
            return this;
974
        }
975
976
        public ProxyPreferences get(ProxyPreferences parent, String name, Preferences delegate) {
977
            if (delegate != null) {
978
                assert name.equals(delegate.name());
979
980
                if (parent == null) {
981
                    Preferences parentDelegate = delegate.parent();
982
                    if (parentDelegate != null) {
983
                        parent = get(null, parentDelegate.name(), parentDelegate);
984
                    } // else delegate is the root
985
                } else {
986
                    // sanity check
987
                    assert parent.delegate == delegate.parent();
988
                }
989
            }
990
991
            String absolutePath;
992
            if (parent == null) {
993
                absolutePath = "/"; //NOI18N
994
            } else if (parent.parent() == null) {
995
                absolutePath = "/" + name; //NOI18N
996
            } else {
997
                absolutePath = parent.absolutePath() + "/" + name; //NOI18N
998
            }
999
1000
            ProxyPreferences node = nodes.get(absolutePath);
1001
            if (node == null) {
1002
                node = new ProxyPreferences(parent, name, delegate, this);
1003
                nodes.put(absolutePath, node);
1004
1005
                if (parent != null) {
1006
                    parent.addChild(node);
1007
                }
1008
            } else {
1009
                assert !node.removed;
1010
            }
1011
1012
            return node;
1013
        }
1014
1015
        public void removeNode(ProxyPreferences node) {
1016
            String path = node.absolutePath();
1017
            assert nodes.containsKey(path);
1018
            ProxyPreferences pp = nodes.remove(path);
1019
        }
1020
1021
        public void destroy() {
1022
            synchronized (trees) {
1023
                Object token = tokenRef.get();
1024
                if (token != null) {
1025
                    trees.remove(token);
1026
                } // else the token has been GCed and therefore is not even in the trees map
1027
            }
1028
        }
1029
    } // End of Tree class
1030
1031
    private static final class EventBag<L, E extends EventObject> {
1032
        private final Set<L> listeners = new HashSet<L>();
1033
        private final Set<E> events = new HashSet<E>();
1034
1035
        public EventBag() {
1036
        }
1037
1038
        public Set<? extends L> getListeners() {
1039
            return listeners;
1040
        }
1041
1042
        public Set<? extends E> getEvents() {
1043
            return events;
1044
        }
1045
1046
        public void addListeners(Collection<? extends L> l) {
1047
            listeners.addAll(l);
1048
        }
1049
1050
        public void addEvent(E event) {
1051
            events.add(event);
1052
        }
1053
    } // End of EventBag class
1054
1055
    private static final class NodeChangeEventExt extends NodeChangeEvent {
1056
        private final boolean removal;
1057
        public NodeChangeEventExt(Preferences parent, Preferences child, boolean removal) {
1058
            super(parent, child);
1059
            this.removal = removal;
1060
        }
1061
1062
        public boolean isRemovalEvent() {
1063
            return removal;
1064
        }
1065
    } // End of NodeChangeEventExt class
1066
}
391
}
(-)a/options.editor/test/unit/src/org/netbeans/modules/options/indentation/ProxyPreferencesTest.java (-1 / +2 lines)
Lines 305-311 Link Here
305
        test = null;
305
        test = null;
306
        assertGC("Tree token was not GCed", treeTokenRef, Collections.singleton(this));
306
        assertGC("Tree token was not GCed", treeTokenRef, Collections.singleton(this));
307
        // touch the WeakHashMap to expungeStaleEntries
307
        // touch the WeakHashMap to expungeStaleEntries
308
        ProxyPreferences.Tree.trees.size();
308
        Object dummyToken = new Object();
309
        ProxyPreferences dummyPrefs = ProxyPreferences.getProxyPreferences(dummyToken, orig);
309
        assertGC("Test preferences were not GCed", testRef, Collections.singleton(this));
310
        assertGC("Test preferences were not GCed", testRef, Collections.singleton(this));
310
        
311
        
311
    }
312
    }
(-)a/php.editor/nbproject/project.xml (+9 lines)
Lines 113-118 Link Here
113
                    </run-dependency>
113
                    </run-dependency>
114
                </dependency>
114
                </dependency>
115
                <dependency>
115
                <dependency>
116
                    <code-name-base>org.netbeans.modules.editor.fold</code-name-base>
117
                    <build-prerequisite/>
118
                    <compile-dependency/>
119
                    <run-dependency>
120
                        <release-version>1</release-version>
121
                        <specification-version>1.34</specification-version>
122
                    </run-dependency>
123
                </dependency>
124
                <dependency>
116
                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
125
                    <code-name-base>org.netbeans.modules.editor.indent</code-name-base>
117
                    <build-prerequisite/>
126
                    <build-prerequisite/>
118
                    <compile-dependency/>
127
                    <compile-dependency/>
(-)a/php.editor/src/org/netbeans/modules/php/editor/nav/FoldingScanner.java (-14 / +47 lines)
Lines 47-52 Link Here
47
import java.util.List;
47
import java.util.List;
48
import java.util.Map;
48
import java.util.Map;
49
import javax.swing.text.Document;
49
import javax.swing.text.Document;
50
import org.netbeans.api.editor.fold.FoldTemplate;
51
import org.netbeans.api.editor.fold.FoldType;
50
import org.netbeans.modules.csl.api.OffsetRange;
52
import org.netbeans.modules.csl.api.OffsetRange;
51
import org.netbeans.modules.csl.spi.ParserResult;
53
import org.netbeans.modules.csl.spi.ParserResult;
52
import org.netbeans.modules.parsing.api.Source;
54
import org.netbeans.modules.parsing.api.Source;
Lines 77-93 Link Here
77
import org.netbeans.modules.php.editor.parser.astnodes.WhileStatement;
79
import org.netbeans.modules.php.editor.parser.astnodes.WhileStatement;
78
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
80
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
79
81
82
import org.openide.util.NbBundle;
83
import static org.netbeans.modules.php.editor.nav.Bundle.*;
80
/**
84
/**
81
 *
85
 *
82
 * @author Ondrej Brejla <obrejla@netbeans.org>
86
 * @author Ondrej Brejla <obrejla@netbeans.org>
83
 */
87
 */
84
public final class FoldingScanner {
88
public final class FoldingScanner {
89
    
90
    public static final FoldType TYPE_CODE_BLOCKS = FoldType.CODE_BLOCK;
91
    
92
    /**
93
     * FoldType for the PHP class (either nested, or top-level)
94
     */
95
    @NbBundle.Messages("FT_Classes=Classes")
96
    public static final FoldType TYPE_CLASS = FoldType.NESTED.derive(
97
            "class", 
98
            FT_Classes(), FoldTemplate.DEFAULT_BLOCK);
85
99
86
    private static final String FOLD_CODE_BLOCKS = "codeblocks"; //NOI18N
100
    /**
87
    private static final String FOLD_CLASS = "inner-classes"; //NOI18N
101
     * PHP documentation comments
88
    private static final String FOLD_PHPDOC = "comments"; //NOI18N
102
     */
89
    private static final String FOLD_COMMENT = "initial-comment"; //NOI18N
103
    @NbBundle.Messages("FT_PHPDoc=PHPDoc documentation")
90
    private static final String FOLD_OTHER_CODE_BLOCKS = "othercodeblocks"; //NOI18N
104
    public static final FoldType TYPE_PHPDOC = FoldType.DOCUMENTATION.override(
105
            FT_PHPDoc(),        // NOI18N
106
            new FoldTemplate(3, 2, "/**...*/")); // NOI18N
107
    
108
    public static final FoldType TYPE_COMMENT = FoldType.COMMENT.override(
109
            FoldType.COMMENT.getLabel(),
110
            new FoldTemplate(2, 2, "/*...*/")); // NOI18N
111
    
112
    /**
113
     * 
114
     */
115
    @NbBundle.Messages("FT_Functions=Functions and methods")
116
    public static final FoldType TYPE_FUNCTION = FoldType.MEMBER.derive("function", 
117
            FT_Functions(), 
118
            FoldTemplate.DEFAULT_BLOCK);
119
    
91
    private static final String LAST_CORRECT_FOLDING_PROPERTY = "LAST_CORRECT_FOLDING_PROPERY"; //NOI18N
120
    private static final String LAST_CORRECT_FOLDING_PROPERTY = "LAST_CORRECT_FOLDING_PROPERY"; //NOI18N
92
121
93
    public static FoldingScanner create() {
122
    public static FoldingScanner create() {
Lines 120-129 Link Here
120
            if (comments != null) {
149
            if (comments != null) {
121
                for (Comment comment : comments) {
150
                for (Comment comment : comments) {
122
                    if (comment.getCommentType() == Comment.Type.TYPE_PHPDOC) {
151
                    if (comment.getCommentType() == Comment.Type.TYPE_PHPDOC) {
123
                        getRanges(folds, FOLD_PHPDOC).add(createOffsetRange(comment));
152
                        getRanges(folds, TYPE_PHPDOC).add(createOffsetRange(comment, -3));
124
                    } else {
153
                    } else {
125
                        if (comment.getCommentType() == Comment.Type.TYPE_MULTILINE) {
154
                        if (comment.getCommentType() == Comment.Type.TYPE_MULTILINE) {
126
                            getRanges(folds, FOLD_COMMENT).add(createOffsetRange(comment));
155
                            getRanges(folds, TYPE_COMMENT).add(createOffsetRange(comment));
127
                        }
156
                        }
128
                    }
157
                    }
129
                }
158
                }
Lines 138-147 Link Here
138
                    continue;
167
                    continue;
139
                }
168
                }
140
                if (scope instanceof TypeScope) {
169
                if (scope instanceof TypeScope) {
141
                    getRanges(folds, FOLD_CLASS).add(offsetRange);
170
                    getRanges(folds, TYPE_CLASS).add(offsetRange);
142
                } else {
171
                } else {
143
                    if (scope instanceof FunctionScope || scope instanceof MethodScope) {
172
                    if (scope instanceof FunctionScope || scope instanceof MethodScope) {
144
                        getRanges(folds, FOLD_CODE_BLOCKS).add(offsetRange);
173
                        getRanges(folds, TYPE_FUNCTION).add(offsetRange);
145
                    }
174
                    }
146
                }
175
                }
147
            }
176
            }
Lines 157-172 Link Here
157
        }
186
        }
158
        return Collections.emptyMap();
187
        return Collections.emptyMap();
159
    }
188
    }
189
    
190
    private OffsetRange createOffsetRange(ASTNode node, int startShift) {
191
        return new OffsetRange(node.getStartOffset() + startShift, node.getEndOffset());
192
    }
160
193
161
    private OffsetRange createOffsetRange(ASTNode node) {
194
    private OffsetRange createOffsetRange(ASTNode node) {
162
        return new OffsetRange(node.getStartOffset(), node.getEndOffset());
195
        return createOffsetRange(node, 0);
163
    }
196
    }
164
197
165
    private List<OffsetRange> getRanges(Map<String, List<OffsetRange>> folds, String kind) {
198
    private List<OffsetRange> getRanges(Map<String, List<OffsetRange>> folds, FoldType kind) {
166
        List<OffsetRange> ranges = folds.get(kind);
199
        List<OffsetRange> ranges = folds.get(kind.code());
167
        if (ranges == null) {
200
        if (ranges == null) {
168
            ranges = new ArrayList<OffsetRange>();
201
            ranges = new ArrayList<OffsetRange>();
169
            folds.put(kind, ranges);
202
            folds.put(kind.code(), ranges);
170
        }
203
        }
171
        return ranges;
204
        return ranges;
172
    }
205
    }
Lines 290-296 Link Here
290
        }
323
        }
291
324
292
        private void addFold(final OffsetRange offsetRange) {
325
        private void addFold(final OffsetRange offsetRange) {
293
            getRanges(folds, FOLD_OTHER_CODE_BLOCKS).add(offsetRange);
326
            getRanges(folds, TYPE_CODE_BLOCKS).add(offsetRange);
294
        }
327
        }
295
328
296
    }
329
    }
(-)a/php.editor/src/org/netbeans/modules/php/editor/nav/PHPFoldingProvider.java (+75 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.php.editor.nav;
43
44
import java.util.ArrayList;
45
import java.util.Collection;
46
import org.netbeans.api.editor.fold.FoldType;
47
import org.netbeans.api.editor.mimelookup.MimeRegistration;
48
import org.netbeans.spi.editor.fold.FoldTypeProvider;
49
50
/**
51
 *
52
 * @author sdedic
53
 */
54
@MimeRegistration(mimeType = "text/x-php5", service = FoldTypeProvider.class)
55
public class PHPFoldingProvider implements FoldTypeProvider {
56
    private static final Collection<FoldType> TYPES = new ArrayList<FoldType>(5);
57
    
58
    static {
59
        TYPES.add(FoldingScanner.TYPE_CLASS);
60
        TYPES.add(FoldingScanner.TYPE_FUNCTION);
61
        TYPES.add(FoldingScanner.TYPE_CODE_BLOCKS);
62
        TYPES.add(FoldingScanner.TYPE_COMMENT);
63
        TYPES.add(FoldingScanner.TYPE_PHPDOC);
64
    }
65
    
66
    @Override
67
    public Collection getValues(Class type) {
68
        return type == FoldType.class ? TYPES : null;
69
    }
70
71
    @Override
72
    public boolean inheritable() {
73
        return false;
74
    }
75
}
(-)a/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml (-1 / +11 lines)
Lines 116-122 Link Here
116
                    <file name="org-netbeans-modules-php-editor-sql-PHPSQLCompletion.instance"/>
116
                    <file name="org-netbeans-modules-php-editor-sql-PHPSQLCompletion.instance"/>
117
                </folder>
117
                </folder>
118
                <folder name="FoldManager">
118
                <folder name="FoldManager">
119
                    <file name="org-netbeans-editor-CustomFoldManager$Factory.instance"/>
119
                    <file name="CustomFoldManager.instance">
120
                        <attr name="instanceOf" stringvalue="org.netbeans.spi.editor.fold.FoldManagerFactory"/>
121
                        <attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldingSupport.userFoldManagerFactory"/>
122
                    </file>
123
                    <file name="PHPDocReader.instance">
124
                        <attr name="instanceOf" stringvalue="org.netbeans.spi.editor.fold.ContentReader$Factory"/>
125
                        <attr name="instanceCreate" methodvalue="org.netbeans.api.editor.fold.FoldingSupport.contentReaderFactory"/>
126
                        <attr name="start" stringvalue="*"/>
127
                        <attr name="terminator" stringvalue="\."/>
128
                        <attr name="stop" stringvalue="@"/>
129
                    </file>
120
                </folder>
130
                </folder>
121
                <!--issue #127161:-->
131
                <!--issue #127161:-->
122
                <folder name="Popup">
132
                <folder name="Popup">

Return to bug 226413