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.palette;
043:
044: import java.awt.event.ActionEvent;
045: import java.beans.PropertyChangeEvent;
046: import java.beans.PropertyChangeSupport;
047: import java.util.ArrayList;
048: import javax.swing.Action;
049: import javax.swing.SwingUtilities;
050: import org.netbeans.spi.palette.DragAndDropHandler;
051: import org.netbeans.spi.palette.PaletteController;
052: import org.openide.nodes.*;
053: import org.openide.util.*;
054: import org.netbeans.modules.palette.ui.Customizer;
055: import org.netbeans.spi.palette.PaletteActions;
056:
057: /**
058: * Default implementation of PaletteModel interface based on Nodes.
059: *
060: * @author S. Aubrecht
061: */
062: public class DefaultModel implements Model, NodeListener {
063:
064: /**
065: * Palette's root node. Its subnodes are palette categories.
066: */
067: private RootNode rootNode;
068:
069: /**
070: * Item currently selected in the palette or null.
071: */
072: private Item selectedItem;
073: /**
074: * Category that owns the selected item or null.
075: */
076: private Category selectedCategory;
077: private PropertyChangeSupport propertySupport;
078: private ArrayList<ModelListener> modelListeners = new ArrayList<ModelListener>(
079: 3);
080: private boolean categoriesNeedRefresh = true;
081: /**
082: * Cached categories
083: */
084: private Category[] categories;
085:
086: /**
087: * Creates a new instance of DefaultPaletteModel
088: *
089: * @param rootNode Palette's root node.
090: */
091: public DefaultModel(RootNode rootNode) {
092: this .rootNode = rootNode;
093:
094: propertySupport = new PropertyChangeSupport(this );
095: this .rootNode.addNodeListener(this );
096: }
097:
098: public void setSelectedItem(Lookup category, Lookup item) {
099: Category cat = null;
100: Item it = null;
101: if (null != category) {
102: Node catNode = category.lookup(Node.class);
103: if (null != catNode) {
104: cat = findCategory(catNode);
105: }
106: }
107: if (null != item && null != cat) {
108: Node itNode = item.lookup(Node.class);
109: if (null != itNode) {
110: it = findItem(cat, itNode);//new DefaultItem( itNode );
111: }
112: }
113:
114: Item oldValue = selectedItem;
115: this .selectedItem = it;
116: this .selectedCategory = cat;
117: propertySupport.firePropertyChange(Model.PROP_SELECTED_ITEM,
118: oldValue, selectedItem);
119: }
120:
121: public void clearSelection() {
122: setSelectedItem(null, null);
123: }
124:
125: public Action[] getActions() {
126: return rootNode.getActions(false);
127: }
128:
129: public Item getSelectedItem() {
130: return selectedItem;
131: }
132:
133: public Category getSelectedCategory() {
134: return selectedCategory;
135: }
136:
137: public void addModelListener(ModelListener listener) {
138: synchronized (modelListeners) {
139: modelListeners.add(listener);
140: propertySupport.addPropertyChangeListener(listener);
141: }
142: }
143:
144: public void removeModelListener(ModelListener listener) {
145: synchronized (modelListeners) {
146: modelListeners.remove(listener);
147: propertySupport.removePropertyChangeListener(listener);
148: }
149: }
150:
151: public synchronized Category[] getCategories() {
152: if (null == categories || categoriesNeedRefresh) {
153: Node[] nodes = rootNode.getChildren().getNodes(canBlock());
154: categories = nodes2categories(nodes);
155: categoriesNeedRefresh = false;
156: }
157: return categories;
158: }
159:
160: public static boolean canBlock() {
161: return !Children.MUTEX.isReadAccess()
162: && !Children.MUTEX.isWriteAccess();
163: }
164:
165: /** Fired when a set of new children is added.
166: * @param ev event describing the action
167: */
168: public void childrenAdded(NodeMemberEvent ev) {
169: categoriesNeedRefresh = true;
170: if (isRefreshingChildren)
171: return;
172: final Node[] nodes = ev.getDelta();
173: SwingUtilities.invokeLater(new Runnable() {
174: public void run() {
175: Category[] addedCategories = findCategories(nodes);
176: fireCategoriesChanged(addedCategories, true);
177: }
178: });
179: }
180:
181: /** Fired when a set of children is removed.
182: * @param ev event describing the action
183: */
184: public void childrenRemoved(NodeMemberEvent ev) {
185: categoriesNeedRefresh = true;
186: final Node[] nodes = ev.getDelta();
187: SwingUtilities.invokeLater(new Runnable() {
188: public void run() {
189: Category[] removedCategories = findCategories(nodes);
190: fireCategoriesChanged(removedCategories, false);
191: }
192: });
193: }
194:
195: /** Fired when the order of children is changed.
196: * @param ev event describing the change
197: */
198: public void childrenReordered(NodeReorderEvent ev) {
199: categoriesNeedRefresh = true;
200: fireCategoriesChanged(null, false);
201: }
202:
203: /** Fired when the node is deleted.
204: * @param ev event describing the node
205: */
206: public synchronized void nodeDestroyed(NodeEvent ev) {
207: this .rootNode.removeNodeListener(this );
208: }
209:
210: public void propertyChange(PropertyChangeEvent evt) {
211: }
212:
213: private void fireCategoriesChanged(Category[] changedCategories,
214: boolean added) {
215: ModelListener[] listeners;
216: synchronized (modelListeners) {
217: listeners = new ModelListener[modelListeners.size()];
218: listeners = modelListeners.toArray(listeners);
219: }
220: for (int i = 0; i < listeners.length; i++) {
221: if (null != changedCategories) {
222: if (added) {
223: listeners[i].categoriesAdded(changedCategories);
224: } else {
225: listeners[i].categoriesRemoved(changedCategories);
226: }
227: } else {
228: listeners[i].categoriesReordered();
229: }
230: }
231: }
232:
233: /**
234: * Wrap the given Nodes in PaletteCategory instances.
235: */
236: private Category[] nodes2categories(Node[] nodes) {
237: Category[] res = new Category[nodes.length];
238:
239: for (int i = 0; i < res.length; i++) {
240: res[i] = new DefaultCategory(nodes[i]);
241: }
242:
243: return res;
244: }
245:
246: private Category[] findCategories(Node[] nodes) {
247: Category[] res = new Category[nodes.length];
248:
249: Category[] current = getCategories();
250: for (int i = 0; i < res.length; i++) {
251: boolean found = false;
252: for (int j = 0; !found && null != current
253: && j < current.length; j++) {
254: Node catNode = current[j].getLookup()
255: .lookup(Node.class);
256: if (nodes[i].equals(catNode)) {
257: res[i] = current[j];
258: found = true;
259: }
260: }
261: if (!found) {
262: res[i] = new DefaultCategory(nodes[i]);
263: }
264: }
265:
266: return res;
267: }
268:
269: private boolean isRefreshingChildren = false;
270:
271: public void refresh() {
272: synchronized (rootNode) {
273: PaletteActions customActions = rootNode.getLookup().lookup(
274: PaletteActions.class);
275: Action customRefreshAction = customActions
276: .getRefreshAction();
277: if (null != customRefreshAction) {
278: customRefreshAction.actionPerformed(new ActionEvent(
279: getRoot(), 0, "refresh")); //NOI18N
280: }
281: clearSelection();
282: categoriesNeedRefresh = true;
283: isRefreshingChildren = true;
284: try {
285: rootNode.refreshChildren();
286: } finally {
287: isRefreshingChildren = false;
288: }
289: fireCategoriesChanged(null, false);
290: }
291: }
292:
293: public void showCustomizer(PaletteController controller,
294: Settings settings) {
295: Customizer.show(rootNode, controller, settings);
296: }
297:
298: public Lookup getRoot() {
299: return rootNode.getLookup();
300: }
301:
302: public boolean moveCategory(Category source, Category target,
303: boolean moveBefore) {
304: int targetIndex = categoryToIndex(target);
305: if (!moveBefore) {
306: targetIndex++;
307: }
308: DragAndDropHandler handler = getDragAndDropHandler();
309: return handler.moveCategory(source.getLookup(), targetIndex);
310: }
311:
312: private int categoryToIndex(Category category) {
313: Node node = category.getLookup().lookup(Node.class);
314: if (null != node) {
315: Index order = rootNode.getCookie(Index.class);
316: if (null != order) {
317: return order.indexOf(node);
318: }
319: }
320: return -1;
321: }
322:
323: public String getName() {
324: return rootNode.getName();
325: }
326:
327: private Category findCategory(Node node) {
328: Category[] cats = getCategories();
329: for (int i = 0; i < cats.length; i++) {
330: Node catNode = cats[i].getLookup().lookup(Node.class);
331: if (null != catNode && catNode.equals(node))
332: return cats[i];
333: }
334: return null;
335: }
336:
337: private Item findItem(Category category, Node node) {
338: Item[] items = category.getItems();
339: for (int i = 0; i < items.length; i++) {
340: Node itNode = items[i].getLookup().lookup(Node.class);
341: if (null != itNode && itNode.equals(node))
342: return items[i];
343: }
344: return null;
345: }
346:
347: public boolean canReorderCategories() {
348: return getDragAndDropHandler().canReorderCategories(
349: rootNode.getLookup());
350: }
351:
352: private DragAndDropHandler getDragAndDropHandler() {
353: return rootNode.getLookup().lookup(DragAndDropHandler.class);
354: }
355: }
|