~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to projects/projectuiapi/src/org/netbeans/spi/project/ui/support/ProjectCustomizer.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.spi.project.ui.support;
 
43
 
 
44
import java.awt.Dialog;
 
45
import java.awt.Image;
 
46
import java.awt.event.ActionListener;
 
47
import java.io.IOException;
 
48
import java.util.ArrayList;
 
49
import java.util.Collections;
 
50
import java.util.HashMap;
 
51
import java.util.List;
 
52
import java.util.Map;
 
53
import java.util.logging.Level;
 
54
import java.util.logging.Logger;
 
55
import javax.swing.JComponent;
 
56
import javax.swing.JPanel;
 
57
 
 
58
import org.netbeans.modules.project.uiapi.CategoryModel;
 
59
import org.netbeans.modules.project.uiapi.CategoryView;
 
60
import org.netbeans.modules.project.uiapi.CategoryChangeSupport;
 
61
import org.netbeans.modules.project.uiapi.CustomizerDialog;
 
62
import org.netbeans.modules.project.uiapi.CustomizerPane;
 
63
import org.netbeans.modules.project.uiapi.Utilities;
 
64
import org.netbeans.spi.project.ui.support.ProjectCustomizer.Category;
 
65
import org.openide.cookies.InstanceCookie;
 
66
import org.openide.filesystems.FileObject;
 
67
import org.openide.filesystems.FileStateInvalidException;
 
68
import org.openide.filesystems.Repository;
 
69
import org.openide.loaders.DataFolder;
 
70
import org.openide.loaders.DataObject;
 
71
import org.openide.util.HelpCtx;
 
72
import org.openide.util.Lookup;
 
73
 
 
74
/** Support for creating dialogs which can be used as project
 
75
 * customizers. The dialog may display multiple panels or categories.
 
76
 * @see org.netbeans.spi.project.ui.CustomizerProvider
 
77
 * @see ProjectCustomizer.Category
 
78
 *
 
79
 * @author Petr Hrebejk, Martin Krauskopf
 
80
 */
 
81
public final class ProjectCustomizer {
 
82
    
 
83
    /** Factory/Namespace class only. */
 
84
    private ProjectCustomizer() {
 
85
    }
 
86
    
 
87
    /** Creates standard customizer dialog which can be used for implementation
 
88
     * of {@link org.netbeans.spi.project.ui.CustomizerProvider}. You don't need
 
89
     * to call <code>pack()</code> method on the dialog. The resulting dialog will
 
90
     * be non-modal. <br>
 
91
     * Call <code>show()</code> on the dialog to make it visible. The dialog 
 
92
     * will be closed automatically after click on "OK" or "Cancel" button.
 
93
     * 
 
94
     * @param categories array of descriptions of categories to be shown in the
 
95
     *        dialog. Note that categories have the <code>valid</code>
 
96
     *        property. If any of the given categories is not valid cusomizer's
 
97
     *        OK button will be disabled until all categories become valid
 
98
     *        again.
 
99
     * @param componentProvider creator of GUI components for categories in the
 
100
     *        customizer dialog.
 
101
     * @param preselectedCategory name of one of the supplied categories or null.
 
102
     *        Category with given name will be selected. If  <code>null</code>
 
103
     *        or if the category of given name does not exist the first category will
 
104
     *        be selected.
 
105
     * @param okOptionListener listener which will be notified when the user presses
 
106
     *        the OK button.
 
107
     * @param helpCtx Help context for the dialog, which will be used when the
 
108
     *        panels in the customizer do not specify their own help context.
 
109
     * @return standard project customizer dialog.
 
110
     */
 
111
    public static Dialog createCustomizerDialog( Category[] categories,
 
112
                                                 CategoryComponentProvider componentProvider,
 
113
                                                 String preselectedCategory,
 
114
                                                 ActionListener okOptionListener,
 
115
                                                 HelpCtx helpCtx ) {
 
116
        CustomizerPane innerPane = createCustomizerPane(categories, componentProvider, preselectedCategory);
 
117
        Dialog dialog = CustomizerDialog.createDialog( okOptionListener, innerPane, helpCtx, categories, componentProvider);
 
118
        return dialog;
 
119
    }
 
120
 
 
121
    /**
 
122
     * Creates standard customizer dialog that can be used for implementation of
 
123
     * {@link org.netbeans.spi.project.ui.CustomizerProvider} based on content of a folder in Layers.
 
124
     * Use this method when you want to allow composition and 3rd party additions to your customizer UI.
 
125
     * You don't need to call <code>pack()</code> method on the dialog. The resulting dialog will
 
126
     * be non-modal. <br> 
 
127
     * Call <code>show()</code> on the dialog to make it visible. The dialog 
 
128
     * will be closed automatically after click on "OK" or "Cancel" button.
 
129
     * 
 
130
     * @since org.netbeans.modules.projectuiapi/1 1.15
 
131
     * @param folderPath the path in the System Filesystem that is used as root for panel composition.
 
132
     *        The content of the folder is assummed to be {@link org.netbeans.spi.project.ui.support.ProjectCustomizer.CompositeCategoryProvider} instances
 
133
     * @param context the context for the panels, up to the project type what the context shall be, for example org.netbeans.api.project.Project instance
 
134
     * @param preselectedCategory name of one of the supplied categories or null.
 
135
     *        Category with given name will be selected. If  <code>null</code>
 
136
     *        or if the category of given name does not exist the first category will
 
137
     *        be selected.
 
138
     * @param okOptionListener listener which will be notified when the user presses
 
139
     *        the OK button.
 
140
     * @param helpCtx Help context for the dialog, which will be used when the
 
141
     *        panels in the customizer do not specify their own help context.
 
142
     * @return standard project customizer dialog.
 
143
     */
 
144
    public static Dialog createCustomizerDialog( String folderPath,
 
145
                                                 Lookup context,
 
146
                                                 String preselectedCategory,
 
147
                                                 ActionListener okOptionListener,
 
148
                                                 HelpCtx helpCtx) {
 
149
        FileObject root = Repository.getDefault().getDefaultFileSystem().findResource(folderPath);
 
150
        if (root == null) {
 
151
            throw new IllegalArgumentException("The designated path " + folderPath + " doesn't exist. Cannot create customizer.");
 
152
        }
 
153
        DataFolder def = DataFolder.findFolder(root);
 
154
        assert def != null : "Cannot find DataFolder for " + folderPath;
 
155
        DelegateCategoryProvider prov = new DelegateCategoryProvider(def, context);
 
156
        return createCustomizerDialog(prov.getSubCategories(),
 
157
                                      prov,
 
158
                                      preselectedCategory, okOptionListener, helpCtx);
 
159
 
 
160
    }
 
161
    
 
162
    /** Creates standard innerPane for customizer dialog.
 
163
     */
 
164
    private static CustomizerPane createCustomizerPane( Category[] categories,
 
165
                                                CategoryComponentProvider componentProvider,
 
166
                                                String preselectedCategory ) {
 
167
        
 
168
        CategoryChangeSupport changeSupport = new CategoryChangeSupport();
 
169
        registerCategoryChangeSupport(changeSupport, categories);
 
170
        
 
171
        CategoryModel categoryModel = new CategoryModel( categories );
 
172
        JPanel categoryView = new CategoryView( categoryModel );
 
173
        CustomizerPane customizerPane = new CustomizerPane( categoryView, categoryModel, componentProvider );
 
174
        
 
175
        if ( preselectedCategory == null ) {
 
176
            preselectedCategory = categories[0].getName();
 
177
        }
 
178
        
 
179
        Category c = categoryModel.getCategory( preselectedCategory );
 
180
        if ( c != null ) {
 
181
            categoryModel.setCurrentCategory( c );
 
182
        }
 
183
        
 
184
        return customizerPane;
 
185
    }
 
186
 
 
187
    private static void registerCategoryChangeSupport(final CategoryChangeSupport changeSupport, 
 
188
            final Category[] categories) {        
 
189
        for (int i = 0; i < categories.length; i++) {
 
190
            Utilities.putCategoryChangeSupport(categories[i], changeSupport);
 
191
            Category[] subCategories = categories[i].getSubcategories();
 
192
            if (subCategories != null) {
 
193
                registerCategoryChangeSupport(changeSupport, subCategories);
 
194
            }
 
195
        }
 
196
    }
 
197
 
 
198
    
 
199
    /** Provides components for categories.
 
200
     */
 
201
    public static interface CategoryComponentProvider {
 
202
        
 
203
        /** Creates component which has to be shown for given category.
 
204
         * @param category The Category
 
205
         * @return UI component for category customization
 
206
         */
 
207
        JComponent create( Category category );
 
208
        
 
209
    }
 
210
 
 
211
    /**
 
212
     * Interface for creation of Customizer categories and their respective UI panels.
 
213
     * Implementations are to be registered in System FileSystem via module layers. Used by the
 
214
     * {@link org.netbeans.spi.project.ui.support.ProjectCustomizer#createCustomizerDialog(String,Lookup,String,ActionListener,HelpCtx)}
 
215
     * The panel/category created by the provider can get notified that the customizer got
 
216
     * closed by setting an <code>ActionListener</code> to 
 
217
     * {@link org.netbeans.spi.project.ui.support.ProjectCustomizer.Category#setOkButtonListener} .
 
218
     * UI Component can be defined for category folder that is represented as node with subnodes in the category
 
219
     * tree of project customizer. Name of the file that defines the instance class in layer for such category 
 
220
     * must be named "Self". Such CompositeCategory won't have the createCategory() method called, but will have the category created by
 
221
     * the infrastructure based on the folder content.
 
222
     * For details and usage see issue #91276.
 
223
     * @since org.netbeans.modules.projectuiapi/1 1.22
 
224
     */
 
225
    public static interface CompositeCategoryProvider {
 
226
 
 
227
        /**
 
228
         * create the Category instance for the given project customizer context.
 
229
         * @param context Lookup instance passed from project The content is up to the project type, please consult documentation
 
230
         * for the project type you want to integrate your panel into.
 
231
         * @return A category instance, can be null, in which case no category and no panels are created for given context.
 
232
         *   The instance is expected to have no subcategories.
 
233
         */
 
234
        Category createCategory( Lookup context );
 
235
 
 
236
        /**
 
237
         * create the UI component for given category and context.
 
238
         * The panel/category created by the provider can get notified that the customizer got
 
239
         * closed by setting an <code>ActionListener</code> to 
 
240
         * {@link org.netbeans.spi.project.ui.support.ProjectCustomizer.Category#setOkButtonListener}.
 
241
         * @param category Category instance that was created in the createCategory method.
 
242
         * @param context Lookup instance passed from project The content is up to the project type, please consult documentation
 
243
         * for the project type you want to integrate your panel into.
 
244
         */
 
245
        JComponent createComponent (Category category, Lookup context );
 
246
    }
 
247
    
 
248
    /** Describes category of properties to be customized by given component
 
249
     */
 
250
    public static final class Category {
 
251
        
 
252
        private String name;
 
253
        private String displayName;
 
254
        private Image icon;
 
255
        private Category[] subcategories;
 
256
        private boolean valid;
 
257
        private String errorMessage;
 
258
        private ActionListener okListener;
 
259
        
 
260
        /** Private constructor. See the factory method.
 
261
         */
 
262
        private Category( String name,
 
263
                         String displayName,
 
264
                         Image icon,
 
265
                         Category[] subcategories ) {
 
266
            
 
267
            this.name = name;
 
268
            this.displayName = displayName;
 
269
            this.icon = icon;
 
270
            this.subcategories = subcategories;
 
271
            this.valid = true; // default
 
272
        }
 
273
        
 
274
        /** Factory method which creates new category description.
 
275
         * @param name Programmatic name of the category
 
276
         * @param displayName Name to be shown to the user
 
277
         * @param icon Icon for given category. Will use default icon if null.
 
278
         * @param subcategories Subcategories to be shown under given category.
 
279
         *        Category won't be expandable if null or empty array.
 
280
         * @return a new category description
 
281
         */
 
282
        public static Category create( String name,
 
283
                                       String displayName,
 
284
                                       Image icon,
 
285
                                       Category... subcategories ) {
 
286
            return new Category( name, displayName, icon, subcategories );
 
287
        }
 
288
        
 
289
        // Public methods ------------------------------------------------------
 
290
        
 
291
        /** Gets programmatic name of given category.
 
292
         * @return Programmatic name of the category
 
293
         */
 
294
        public String getName() {
 
295
            return this.name;
 
296
        }
 
297
        
 
298
        /** Gets display name of given category.
 
299
         * @return Display name of the category
 
300
         */
 
301
        public String getDisplayName() {
 
302
            return this.displayName;
 
303
        }
 
304
        
 
305
        /** Gets icon of given category.
 
306
         * @return Icon name of the category or null
 
307
         */
 
308
        public Image getIcon() {
 
309
            return this.icon;
 
310
        }
 
311
        
 
312
        /** Gets subcategories of given category.
 
313
         * @return Subcategories of the category or null
 
314
         */
 
315
        public Category[] getSubcategories() {
 
316
            return this.subcategories;
 
317
        }
 
318
        
 
319
        /**
 
320
         * Returns an error message for this category.
 
321
         * @return the error message (could be null)
 
322
         */
 
323
        public String getErrorMessage() {
 
324
            return errorMessage;
 
325
        }
 
326
        
 
327
        /**
 
328
         * Returns whether this category is valid or not. See {@link
 
329
         * ProjectCustomizer#createCustomizerDialog} for more details.
 
330
         * @return whether this category is valid or not (true by default)
 
331
         */
 
332
        public boolean isValid() {
 
333
            return valid;
 
334
        }
 
335
        
 
336
        /**
 
337
         * Set a validity of this category. See {@link
 
338
         * ProjectCustomizer#createCustomizerDialog} for more details.
 
339
         * @param valid set whether this category is valid or not
 
340
         */
 
341
        public void setValid(boolean valid) {
 
342
            if (this.valid != valid) {
 
343
                this.valid = valid;
 
344
                Utilities.getCategoryChangeSupport(this).firePropertyChange(
 
345
                        CategoryChangeSupport.VALID_PROPERTY, !valid, valid);
 
346
            }
 
347
        }
 
348
        
 
349
        /**
 
350
         * Set an errror message for this category which than may be shown in a
 
351
         * project customizer.
 
352
         *
 
353
         * @param message message for this category. To <em>reset</em> a
 
354
         *        message usually <code>null</code> or an empty string is
 
355
         *        passed. (similar to behaviour of {@link
 
356
         *        javax.swing.text.JTextComponent#setText(String)})
 
357
         */
 
358
        public void setErrorMessage(String message) {
 
359
            if (message == null) {
 
360
                message = "";
 
361
            }
 
362
            if (!message.equals(this.errorMessage)) {
 
363
                String oldMessage = this.errorMessage;
 
364
                this.errorMessage = message;
 
365
                Utilities.getCategoryChangeSupport(this).firePropertyChange(
 
366
                        CategoryChangeSupport.ERROR_MESSAGE_PROPERTY, oldMessage, message);
 
367
            }
 
368
        }
 
369
        
 
370
        /**
 
371
         * Set the action listener that will get notified when the changes in the customizer 
 
372
         * are to be applied.
 
373
         * @param okButtonListener ActionListener to notify 
 
374
         * @since org.netbeans.modules.projectuiapi/1 1.20
 
375
         */ 
 
376
        public void setOkButtonListener(ActionListener okButtonListener) {
 
377
            okListener = okButtonListener;
 
378
        }
 
379
        
 
380
        /**
 
381
         * Returns the action listener associated with this category that gets notified
 
382
         * when OK button is pressed on the customizer.
 
383
         * @return instance of ActionListener or null if not set.
 
384
         * @since org.netbeans.modules.projectuiapi/1 1.20
 
385
         */ 
 
386
        public ActionListener getOkButtonListener() {
 
387
            return okListener;
 
388
        }
 
389
        
 
390
    }
 
391
 
 
392
    /*private*/ static class DelegateCategoryProvider implements CategoryComponentProvider, CompositeCategoryProvider, Lookup.Provider {
 
393
 
 
394
        private final Lookup context;
 
395
        private final Map<ProjectCustomizer.Category,CompositeCategoryProvider> category2provider;
 
396
        private final DataFolder folder;
 
397
        private final CompositeCategoryProvider selfProvider;
 
398
 
 
399
        public DelegateCategoryProvider(DataFolder folder, Lookup context) {
 
400
            this(folder, context, new HashMap<ProjectCustomizer.Category,CompositeCategoryProvider>());
 
401
        }
 
402
 
 
403
        private DelegateCategoryProvider(DataFolder folder, Lookup context, Map<ProjectCustomizer.Category,CompositeCategoryProvider> cat2Provider) {
 
404
            this(folder, context, cat2Provider, null);
 
405
        }
 
406
        
 
407
        private DelegateCategoryProvider(DataFolder folder, Lookup context, Map<ProjectCustomizer.Category,CompositeCategoryProvider> cat2Provider, CompositeCategoryProvider sProv) {
 
408
            this.context = context;
 
409
            this.folder = folder;
 
410
            category2provider = cat2Provider;
 
411
            selfProvider = sProv;
 
412
        }
 
413
 
 
414
        public JComponent create(ProjectCustomizer.Category category) {
 
415
            CompositeCategoryProvider prov = category2provider.get(category);
 
416
            assert prov != null : "Category doesn't have a provider associated.";
 
417
            return prov.createComponent(category, context);
 
418
        }
 
419
 
 
420
        public ProjectCustomizer.Category[] getSubCategories() {
 
421
            try {
 
422
               return readCategories(folder);
 
423
            } catch (IOException exc) {
 
424
                Logger.getAnonymousLogger().log(Level.WARNING, "Cannot construct Project UI panels", exc);
 
425
                return new ProjectCustomizer.Category[0];
 
426
            } catch (ClassNotFoundException ex) {
 
427
                Logger.getAnonymousLogger().log(Level.WARNING, "Cannot construct Project UI panels", ex);
 
428
                return new ProjectCustomizer.Category[0];
 
429
            }
 
430
        }
 
431
 
 
432
 
 
433
        /*private*/ ProjectCustomizer.Category[] readCategories(DataFolder folder) throws IOException, ClassNotFoundException {
 
434
            List<ProjectCustomizer.Category> toRet = new ArrayList<ProjectCustomizer.Category>();
 
435
            for (DataObject dob : folder.getChildren()) {
 
436
                if (dob instanceof DataFolder) {
 
437
                    CompositeCategoryProvider sProvider = null;
 
438
                    DataObject subDobs[] = ((DataFolder) dob).getChildren();
 
439
                    for (DataObject subDob : subDobs) {
 
440
                        if (subDob.getName().equals("Self")) { // NOI18N
 
441
                            InstanceCookie cookie = subDob.getCookie(InstanceCookie.class);
 
442
                            if (cookie != null && CompositeCategoryProvider.class.isAssignableFrom(cookie.instanceClass())) {
 
443
                                sProvider = (CompositeCategoryProvider) cookie.instanceCreate();
 
444
                            }
 
445
                        }
 
446
                    }
 
447
                    CompositeCategoryProvider prov = null;
 
448
                    if (sProvider != null) {
 
449
                        prov = new DelegateCategoryProvider((DataFolder) dob, context, category2provider, sProvider);
 
450
                    } else {
 
451
                        prov = new DelegateCategoryProvider((DataFolder) dob, context, category2provider);
 
452
                    }
 
453
                    ProjectCustomizer.Category cat = prov.createCategory(context);
 
454
                    toRet.add(cat);
 
455
                    category2provider.put(cat, prov);
 
456
                }
 
457
                if (!dob.getName().equals("Self")) { // NOI18N
 
458
                    InstanceCookie cook = dob.getCookie(InstanceCookie.class);
 
459
                    if (cook != null && CompositeCategoryProvider.class.isAssignableFrom(cook.instanceClass())) {
 
460
                        CompositeCategoryProvider provider = (CompositeCategoryProvider)cook.instanceCreate();
 
461
                        ProjectCustomizer.Category cat = provider.createCategory(context);
 
462
                        if (cat != null) {
 
463
                            toRet.add(cat);
 
464
                            category2provider.put(cat, provider);
 
465
                            includeSubcats(cat.getSubcategories(), provider);
 
466
                        }
 
467
                    }
 
468
                }
 
469
            }
 
470
            return toRet.toArray(new ProjectCustomizer.Category[toRet.size()]);
 
471
        }
 
472
        
 
473
        private void includeSubcats(ProjectCustomizer.Category[] cats, ProjectCustomizer.CompositeCategoryProvider provider) {
 
474
            if (cats != null) {
 
475
                for (ProjectCustomizer.Category cat : cats) {
 
476
                    category2provider.put(cat, provider);
 
477
                    includeSubcats(cat.getSubcategories(), provider);
 
478
                }
 
479
            }
 
480
        }
 
481
 
 
482
        /**
 
483
         * provides category for folder..
 
484
         */
 
485
        public ProjectCustomizer.Category createCategory(Lookup context) {
 
486
            FileObject fo = folder.getPrimaryFile();
 
487
            String dn = fo.getNameExt();
 
488
            try {
 
489
                dn = fo.getFileSystem().getStatus().annotateName(fo.getNameExt(), Collections.singleton(fo));
 
490
            } catch (FileStateInvalidException ex) {
 
491
                Logger.getAnonymousLogger().log(Level.WARNING, "Cannot retrieve display name for folder " + fo.getPath(), ex);
 
492
            }
 
493
            return ProjectCustomizer.Category.create(folder.getName(), dn, null, getSubCategories());
 
494
        }
 
495
 
 
496
        /**
 
497
         * provides component for folder category
 
498
         */
 
499
        public JComponent createComponent(ProjectCustomizer.Category category, Lookup context) {
 
500
            if (selfProvider != null) {
 
501
                return selfProvider.createComponent(category, context);
 
502
            }
 
503
            return new JPanel();
 
504
        }
 
505
        //#97998 related
 
506
        public Lookup getLookup() {
 
507
            return context;
 
508
        }
 
509
    }
 
510
}