001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.options;
043:
044: import java.beans.PropertyChangeListener;
045: import java.lang.ref.Reference;
046: import java.lang.ref.WeakReference;
047: import java.util.ArrayList;
048: import java.util.Arrays;
049: import java.util.Collections;
050: import java.util.Iterator;
051: import java.util.LinkedHashMap;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.Set;
055: import javax.swing.Icon;
056: import javax.swing.JComponent;
057: import org.netbeans.spi.options.OptionsCategory;
058: import org.netbeans.spi.options.OptionsPanelController;
059: import org.openide.util.HelpCtx;
060: import org.openide.util.Lookup;
061: import org.openide.util.LookupEvent;
062: import org.openide.util.LookupListener;
063: import org.openide.util.RequestProcessor;
064: import org.openide.util.lookup.Lookups;
065: import org.openide.util.lookup.ProxyLookup;
066:
067: /**
068: * @author Radek Matous
069: */
070: public final class CategoryModel implements LookupListener {
071: private static Reference<CategoryModel> INSTANCE = new WeakReference<CategoryModel>(
072: new CategoryModel());
073: private final RequestProcessor RP = new RequestProcessor();
074: private static String currentCategoryID = null;
075: private String highlitedCategoryID = null;
076: private boolean categoriesValid = true;
077: private final Map<String, CategoryModel.Category> id2Category = Collections
078: .synchronizedMap(new LinkedHashMap<String, CategoryModel.Category>());
079: private MasterLookup masterLookup;
080: private final RequestProcessor.Task masterLookupTask = RP.create(
081: new Runnable() {
082: public void run() {
083: String[] categoryIDs = getCategoryIDs();
084: List<Lookup> all = new ArrayList<Lookup>();
085: for (int i = 0; i < categoryIDs.length; i++) {
086: Category item = getCategory(categoryIDs[i]);
087: Lookup lkp = item.getLookup();
088: assert lkp != null;
089: if (lkp != Lookup.EMPTY) {
090: all.add(lkp);
091: }
092: }
093: getMasterLookup().setLookups(all);
094: }
095: }, true);
096: private final RequestProcessor.Task categoryTask = RP.create(
097: new Runnable() {
098: public void run() {
099: Map<String, OptionsCategory> all = loadOptionsCategories();
100: Map<String, CategoryModel.Category> temp = new LinkedHashMap<String, CategoryModel.Category>();
101: for (Iterator<Map.Entry<String, OptionsCategory>> it = all
102: .entrySet().iterator(); it.hasNext();) {
103: Map.Entry<String, OptionsCategory> entry = it
104: .next();
105: OptionsCategory oc = entry.getValue();
106: String id = entry.getKey();
107: Category cat = new Category(id, oc);
108: temp.put(cat.getID(), cat);
109: }
110: id2Category.clear();
111: id2Category.putAll(temp);
112: masterLookupTask.schedule(0);
113: }
114: }, true);
115:
116: private CategoryModel() {
117: categoryTask.schedule(0);
118: }
119:
120: public static CategoryModel getInstance() {
121: CategoryModel retval = INSTANCE.get();
122: if (retval == null) {
123: retval = new CategoryModel();
124: INSTANCE = new WeakReference<CategoryModel>(retval);
125: }
126: return retval;
127: }
128:
129: boolean needsReinit() {
130: synchronized (CategoryModel.class) {
131: return !categoriesValid;
132: }
133: }
134:
135: boolean isInitialized() {
136: return categoryTask.isFinished();
137: }
138:
139: boolean isLookupInitialized() {
140: return masterLookupTask.isFinished();
141: }
142:
143: void waitForInitialization() {
144: categoryTask.waitFinished();
145: }
146:
147: public String getCurrentCategoryID() {
148: return verifyCategoryID(currentCategoryID);
149: }
150:
151: public void setCurrentCategoryID(String categoryID) {
152: currentCategoryID = verifyCategoryID(categoryID);
153: }
154:
155: String getHighlitedCategoryID() {
156: return verifyCategoryID(highlitedCategoryID);
157: }
158:
159: private String verifyCategoryID(String categoryID) {
160: String retval = findCurrentCategoryID(categoryID) != -1 ? categoryID
161: : null;
162: if (retval == null) {
163: String[] categoryIDs = getCategoryIDs();
164: if (categoryIDs.length > 0) {
165: retval = categoryID = categoryIDs[0];
166: }
167: }
168: return retval;
169: }
170:
171: private int findCurrentCategoryID(String categoryID) {
172: return categoryID == null ? -1 : Arrays
173: .asList(getCategoryIDs()).indexOf(categoryID);
174: }
175:
176: public String[] getCategoryIDs() {
177: categoryTask.waitFinished();
178: Set<String> keys = id2Category.keySet();
179: return keys.toArray(new String[keys.size()]);
180: }
181:
182: Category getCurrent() {
183: String categoryID = getCurrentCategoryID();
184: return (categoryID == null) ? null : getCategory(categoryID);
185: }
186:
187: void setCurrent(Category item) {
188: item.setCurrent();
189: }
190:
191: void setHighlited(Category item, boolean highlited) {
192: item.setHighlited(highlited);
193: }
194:
195: HelpCtx getHelpCtx() {
196: final CategoryModel.Category category = getCurrent();
197: return (category == null) ? null : category.getHelpCtx();
198: }
199:
200: void update(PropertyChangeListener l, boolean force) {
201: String[] categoryIDs = getCategoryIDs();
202: for (int i = 0; i < categoryIDs.length; i++) {
203: CategoryModel.Category item = getCategory(categoryIDs[i]);
204: item.update(l, force);
205: }
206: }
207:
208: void save() {
209: String[] categoryIDs = getCategoryIDs();
210: for (int i = 0; i < categoryIDs.length; i++) {
211: CategoryModel.Category item = getCategory(categoryIDs[i]);
212: item.applyChanges();
213: }
214: }
215:
216: void cancel() {
217: String[] categoryIDs = getCategoryIDs();
218: for (int i = 0; i < categoryIDs.length; i++) {
219: CategoryModel.Category item = getCategory(categoryIDs[i]);
220: item.cancel();
221: }
222: }
223:
224: boolean dataValid() {
225: boolean retval = true;
226: String[] categoryIDs = getCategoryIDs();
227: for (int i = 0; retval && i < categoryIDs.length; i++) {
228: CategoryModel.Category item = getCategory(categoryIDs[i]);
229: retval = item.isValid();
230: }
231: return retval;
232: }
233:
234: boolean isChanged() {
235: boolean retval = false;
236: String[] categoryIDs = getCategoryIDs();
237: for (int i = 0; !retval && i < categoryIDs.length; i++) {
238: CategoryModel.Category item = getCategory(categoryIDs[i]);
239: retval = item.isChanged();
240: }
241: return retval;
242: }
243:
244: Category getNextCategory() {
245: int idx = findCurrentCategoryID(getCurrentCategoryID());
246: String[] categoryIDs = getCategoryIDs();
247: String nextId = "";
248: if (idx >= 0 && idx < categoryIDs.length
249: && categoryIDs.length > 0) {
250: if (idx + 1 < categoryIDs.length) {
251: nextId = categoryIDs[idx + 1];
252: } else {
253: nextId = categoryIDs[0];
254: }
255: } else {
256: nextId = null;
257: }
258: return nextId != null ? getCategory(nextId) : null;
259: }
260:
261: Category getPreviousCategory() {
262: int idx = findCurrentCategoryID(getCurrentCategoryID());
263: String[] categoryIDs = getCategoryIDs();
264: String previousId = "";
265: if (idx >= 0 && idx < categoryIDs.length
266: && categoryIDs.length > 0) {
267: if (idx - 1 >= 0) {
268: previousId = categoryIDs[idx - 1];
269: } else {
270: previousId = categoryIDs[categoryIDs.length - 1];
271: }
272: } else {
273: previousId = null;
274: }
275: return previousId != null ? getCategory(previousId) : null;
276: }
277:
278: Category getCategory(String categoryID) {
279: categoryTask.waitFinished();
280: return id2Category.get(categoryID);
281: }
282:
283: private MasterLookup getMasterLookup() {
284: if (masterLookup == null) {
285: masterLookup = new MasterLookup();
286: }
287: return masterLookup;
288: }
289:
290: private Map<String, OptionsCategory> loadOptionsCategories() {
291: Lookup lookup = Lookups.forPath("OptionsDialog"); // NOI18N
292: Lookup.Result<OptionsCategory> result = lookup
293: .lookup(new Lookup.Template<OptionsCategory>(
294: OptionsCategory.class));
295: result.addLookupListener(this );
296: Map<String, OptionsCategory> m = new LinkedHashMap<String, OptionsCategory>();
297: for (Iterator<? extends Lookup.Item<OptionsCategory>> it = result
298: .allItems().iterator(); it.hasNext();) {
299: Lookup.Item<OptionsCategory> item = it.next();
300: m.put(item.getId().substring("OptionsDialog".length() + 1),
301: item.getInstance()); // NOI18N
302: }
303: return Collections.unmodifiableMap(m);
304: }
305:
306: public void resultChanged(LookupEvent ev) {
307: synchronized (CategoryModel.class) {
308: categoriesValid = false;
309: OptionsDisplayerImpl.lookupListener.resultChanged(ev);
310: INSTANCE = new WeakReference<CategoryModel>(
311: new CategoryModel());
312: }
313: }
314:
315: final class Category {
316: private OptionsCategory category;
317: private OptionsPanelController controller;
318: private boolean isUpdated;
319: private HelpCtx helpCtx;
320: private JComponent component;
321: private Lookup lookup;
322: private final String id;
323:
324: private Category(final String id, final OptionsCategory category) {
325: this .category = category;
326: this .id = id;
327: }
328:
329: boolean isCurrent() {
330: return getID().equals(getCurrentCategoryID());
331: }
332:
333: boolean isHighlited() {
334: return getID().equals(getHighlitedCategoryID());
335: }
336:
337: private void setCurrent() {
338: setCurrentCategoryID(getID());
339: }
340:
341: private void setHighlited(boolean highlited) {
342: if (highlited) {
343: highlitedCategoryID = getID();
344: } else {
345: highlitedCategoryID = currentCategoryID;
346: }
347: }
348:
349: public Icon getIcon() {
350: return category.getIcon();
351: }
352:
353: //whatever ID representing category (dataObject name,category name, just mnemonic, ...)
354: //for impl. #74855: Add an API for opening the Options dialog
355: public String getID() {
356: return id;
357: }
358:
359: public String getCategoryName() {
360: return category.getCategoryName();
361: }
362:
363: public String getTitle() {
364: return category.getTitle();
365: }
366:
367: private synchronized OptionsPanelController create() {
368: if (controller == null) {
369: controller = category.create();
370: }
371: return controller;
372: }
373:
374: final void update(PropertyChangeListener l, boolean forceUpdate) {
375: if ((!isUpdated && !forceUpdate)
376: || (isUpdated && forceUpdate)) {
377: isUpdated = true;
378: getComponent();
379: create().update();
380: if (l != null) {
381: create().addPropertyChangeListener(l);
382: }
383: }
384: }
385:
386: private void applyChanges() {
387: if (isUpdated) {
388: create().applyChanges();
389: }
390: isUpdated = false;
391: }
392:
393: private void cancel() {
394: if (isUpdated) {
395: create().cancel();
396: }
397: isUpdated = false;
398: }
399:
400: private boolean isValid() {
401: boolean retval = true;
402: if (isUpdated) {
403: retval = create().isValid();
404: }
405: return retval;
406: }
407:
408: private boolean isChanged() {
409: boolean retval = false;
410: if (isUpdated) {
411: retval = create().isChanged();
412: }
413: return retval;
414: }
415:
416: public JComponent getComponent() {
417: if (component == null) {
418: component = create().getComponent(getMasterLookup());
419: }
420: return component;
421: }
422:
423: private HelpCtx getHelpCtx() {
424: return create().getHelpCtx();
425: }
426:
427: private Lookup getLookup() {
428: if (lookup == null) {
429: lookup = create().getLookup();
430: }
431: return lookup;
432: }
433: }
434:
435: private class MasterLookup extends ProxyLookup {
436: private void setLookups(List<Lookup> lookups) {
437: setLookups(lookups.toArray(new Lookup[lookups.size()]));
438: }
439:
440: protected void beforeLookup(Lookup.Template template) {
441: super.beforeLookup(template);
442: masterLookupTask.waitFinished();
443: }
444: }
445: }
|