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.core.actions;
043:
044: import java.awt.Component;
045: import java.awt.Dialog;
046: import java.awt.Dimension;
047: import java.awt.Font;
048: import java.awt.GridBagConstraints;
049: import java.awt.Toolkit;
050: import java.awt.event.ActionEvent;
051: import java.awt.event.ActionListener;
052: import java.awt.event.MouseEvent;
053: import java.awt.event.MouseListener;
054: import java.beans.PropertyChangeEvent;
055: import java.beans.PropertyChangeListener;
056: import java.io.ObjectStreamException;
057: import java.lang.ref.Reference;
058: import java.lang.ref.WeakReference;
059: import java.util.ArrayList;
060: import java.util.Collection;
061: import java.util.Iterator;
062: import java.util.List;
063: import javax.swing.JButton;
064: import javax.swing.JComponent;
065: import javax.swing.JSplitPane;
066: import javax.swing.KeyStroke;
067: import javax.swing.SwingUtilities;
068: import javax.swing.UIManager;
069: import javax.swing.border.Border;
070: import javax.swing.table.JTableHeader;
071: import org.netbeans.core.NbMainExplorer;
072: import org.netbeans.core.NbPlaces;
073: import org.netbeans.core.NbTopManager;
074: import org.netbeans.core.projects.SettingChildren;
075: import org.netbeans.core.startup.layers.SessionManager;
076: import org.openide.DialogDescriptor;
077: import org.openide.DialogDisplayer;
078: import org.openide.awt.Mnemonics;
079: import org.openide.awt.StatusDisplayer;
080: import org.openide.cookies.InstanceCookie;
081: import org.openide.explorer.ExplorerManager;
082: import org.netbeans.beaninfo.ExplorerPanel;
083: import org.openide.explorer.propertysheet.PropertySheetView;
084: import org.openide.explorer.view.NodeTableModel;
085: import org.openide.explorer.view.TreeTableView;
086: import org.openide.explorer.view.TreeView;
087: import org.openide.loaders.DataObject;
088: import org.openide.loaders.DataShadow;
089: import org.openide.nodes.FilterNode;
090: import org.openide.nodes.Node;
091: import org.openide.nodes.PropertySupport;
092: import org.openide.util.HelpCtx;
093: import org.openide.util.Mutex;
094: import org.openide.util.NbBundle;
095: import org.openide.util.RequestProcessor;
096: import org.openide.util.WeakListeners;
097: import org.openide.util.actions.CallableSystemAction;
098: import org.openide.windows.TopComponent;
099:
100: /** Action that opens explorer view which displays global
101: * options of the IDE.
102: *
103: * @author Dafe Simonek
104: */
105: public class OptionsAction extends CallableSystemAction {
106:
107: public OptionsAction() {
108: putValue("noIconInMenu", Boolean.TRUE); //NOI18N
109: }
110:
111: private static final String HELP_ID = "org.netbeans.core.actions.OptionsAction"; // NOI18N
112:
113: /** Weak reference to the dialog showing singleton options. */
114: private Reference<Dialog> dialogWRef = new WeakReference<Dialog>(
115: null);
116:
117: public void performAction() {
118: final OptionsPanel optionPanel = OptionsPanel.singleton();
119:
120: Mutex.EVENT.readAccess(new Runnable() {
121: public void run() {
122: Dialog dialog = dialogWRef.get();
123:
124: if (dialog == null || !dialog.isShowing()) {
125: JButton closeButton = new JButton();
126: Mnemonics.setLocalizedText(closeButton, NbBundle
127: .getMessage(OptionsAction.class,
128: "CTL_close_button"));
129: closeButton.getAccessibleContext()
130: .setAccessibleDescription(
131: NbBundle.getMessage(
132: OptionsAction.class,
133: "ACSD_close_button"));
134: String title = (String) OptionsAction.this
135: .getValue("optionsDialogTitle");
136: DialogDescriptor dd = new DialogDescriptor(
137: InitPanel.getDefault(optionPanel),
138: title == null ? optionPanel.getName()
139: : title, false,
140: new Object[] { closeButton }, closeButton,
141: DialogDescriptor.DEFAULT_ALIGN,
142: getHelpCtx(), null);
143:
144: // HACK: switch back to new options dialog hack
145: String name = (String) OptionsAction.this
146: .getValue("additionalActionName");
147: if (name != null) {
148: ActionListener actionListener = (ActionListener) OptionsAction.this
149: .getValue("additionalActionListener");
150: JButton additionalButton = new JButton();
151: Mnemonics.setLocalizedText(additionalButton,
152: name);
153: additionalButton
154: .addActionListener(new ActionListener() {
155: public void actionPerformed(
156: ActionEvent e) {
157: Dialog dialog = dialogWRef
158: .get();
159: dialog.setVisible(false);
160: }
161: });
162: additionalButton
163: .addActionListener(actionListener);
164: dd
165: .setAdditionalOptions(new Object[] { additionalButton });
166: }
167: // end of HACK
168:
169: // #37673
170: optionPanel.setDialogDescriptor(dd);
171:
172: dialog = DialogDisplayer.getDefault().createDialog(
173: dd);
174: dialog.setVisible(true);
175: dialogWRef = new WeakReference<Dialog>(dialog);
176: } else {
177: dialog.toFront();
178: }
179: }
180: }); // EQ.iL
181: }
182:
183: protected boolean asynchronous() {
184: return false;
185: }
186:
187: public HelpCtx getHelpCtx() {
188: return new HelpCtx(HELP_ID);
189: }
190:
191: public String getName() {
192: return NbBundle.getBundle(OptionsAction.class).getString(
193: "Options");
194: }
195:
196: /** Options panel. Uses singleton pattern. */
197: public static final class OptionsPanel extends
198: NbMainExplorer.ExplorerTab implements
199: PropertyChangeListener {
200: /** Name of mode in which options panel is docked by default */
201: public static final String MODE_NAME = "options";
202: /** Singleton instance of options panel */
203: private static OptionsPanel singleton;
204:
205: private static String TEMPLATES_DISPLAY_NAME = NbBundle
206: .getBundle(OptionsAction.class).getString(
207: "CTL_Templates_name"); // NOI18N
208:
209: /** list of nodes that should be expanded when the tree is shown */
210: private Collection<Node> toExpand;
211: private transient boolean expanded;
212: /** root node to use */
213: private transient Node rootNode;
214:
215: // XXX #37673
216: private transient Reference<DialogDescriptor> descriptorRef = new WeakReference<DialogDescriptor>(
217: null);
218:
219: private OptionsPanel() {
220: validateRootContext();
221: // show only name of top component is typical case
222: putClientProperty("NamingType", "BothOnlyCompName"); // NOI18N
223: // Show without tab when alone in container cell.
224: putClientProperty("TabPolicy", "HideWhenAlone"); // NOI18N
225:
226: getExplorerManager().addPropertyChangeListener(this );
227: }
228:
229: /** Overriden to explicitely set persistence type of OptionsPanel
230: * to PERSISTENCE_ALWAYS */
231: public int getPersistenceType() {
232: return TopComponent.PERSISTENCE_ALWAYS;
233: }
234:
235: protected String preferredID() {
236: return "options"; //NOI18N
237: }
238:
239: // #37673 It was requested to update helpCtx according to node selection in explorer.
240: public void propertyChange(PropertyChangeEvent evt) {
241: if (ExplorerManager.PROP_SELECTED_NODES.equals(evt
242: .getPropertyName())) {
243: DialogDescriptor dd = descriptorRef.get();
244: if (dd != null) {
245: dd.setHelpCtx(getHelpCtx());
246: }
247: }
248: }
249:
250: // #37673
251: public void setDialogDescriptor(DialogDescriptor dd) {
252: descriptorRef = new WeakReference<DialogDescriptor>(dd);
253: }
254:
255: public HelpCtx getHelpCtx() {
256: HelpCtx defaultHelp = new HelpCtx(HELP_ID);
257: HelpCtx help = org.openide.explorer.ExplorerUtils
258: .getHelpCtx(
259: getExplorerManager().getSelectedNodes(),
260: defaultHelp);
261: // bugfix #23551, add help id to subnodes of Templates category
262: // this check prevents mixed help ids on more selected nodes
263: if (!defaultHelp.equals(help)) {
264: // try if selected node isn't template
265: Node node = getExplorerManager().getSelectedNodes()[0];
266: HelpCtx readHelpId = getHelpId(node);
267: if (readHelpId != null)
268: return readHelpId;
269:
270: // next bugfix #23551, children have same helpId as parent if no specific is declared
271: while (node != null
272: && !TEMPLATES_DISPLAY_NAME.equals(node
273: .getDisplayName())) {
274: readHelpId = getHelpId(node);
275: if (readHelpId != null)
276: return readHelpId;
277: node = node.getParentNode();
278: }
279: if (node != null
280: && TEMPLATES_DISPLAY_NAME.equals(node
281: .getDisplayName())) {
282: return new HelpCtx(
283: "org.netbeans.core.actions.OptionsAction$TemplatesSubnode"); // NOI18N
284: }
285: }
286: return help;
287: }
288:
289: private HelpCtx getHelpId(Node node) {
290: // it's template, return specific help id
291: DataObject dataObj = (DataObject) node
292: .getCookie(DataObject.class);
293: if (dataObj != null) {
294: Object o = dataObj.getPrimaryFile().getAttribute(
295: "helpID"); // NOI18N
296: if (o != null) {
297: return new HelpCtx(o.toString());
298: }
299: }
300: return null;
301: }
302:
303: /** Accessor to the singleton instance */
304: static OptionsPanel singleton() {
305: if (singleton == null) {
306: singleton = new OptionsPanel();
307: }
308: return singleton;
309: }
310:
311: private transient JSplitPane split = null;
312:
313: protected TreeView initGui() {
314: TTW retVal = new TTW();
315:
316: split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
317: PropertySheetView propertyView = new PropertySheetView();
318:
319: split.setLeftComponent(retVal);
320: split.setRightComponent(propertyView);
321: // install proper border for split pane
322: split.setBorder((Border) UIManager
323: .get("Nb.ScrollPane.border")); // NOI18N
324:
325: setLayout(new java.awt.GridBagLayout());
326:
327: GridBagConstraints gridBagConstraints = new GridBagConstraints();
328: gridBagConstraints.fill = GridBagConstraints.BOTH;
329: gridBagConstraints.weightx = 1.0;
330: gridBagConstraints.weighty = 1.0;
331: gridBagConstraints.gridwidth = 2;
332: add(split, gridBagConstraints);
333:
334: return retVal;
335: }
336:
337: /** Overridden to provide a larger preferred size if the default font
338: * is larger, for locales that require this. */
339: public Dimension getPreferredSize() {
340: //issue 34104, bad sizing/split location for Chinese locales that require
341: //a larger default font size
342: Dimension result = super .getPreferredSize();
343: Font treeFont = UIManager.getFont("Tree.font"); // NOI18N
344: int fontsize = treeFont != null ? treeFont.getSize() : 11;
345: if (fontsize > 11) {
346: int factor = fontsize - 11;
347: result.height += 15 * factor;
348: result.width += 50 * factor;
349: Dimension screen = Toolkit.getDefaultToolkit()
350: .getScreenSize();
351: if (result.height > screen.height) {
352: result.height = screen.height - 30;
353: }
354: if (result.width > screen.width) {
355: result.width = screen.width - 30;
356: }
357: } else {
358: result.width += 20;
359: result.height += 20;
360: }
361:
362: return result;
363: }
364:
365: /** Called when the explored context changes.
366: * Overriden - we don't want title to chnage in this style.
367: */
368: protected void updateTitle() {
369: // empty to keep the title unchanged
370: }
371:
372: boolean isPrepared() {
373: return toExpand != null;
374: }
375:
376: public void prepareNodes() {
377: if (toExpand == null) {
378: List<Node> arr = new ArrayList<Node>(101);
379: expandNodes(getRootContext(), 2, arr);
380: toExpand = arr;
381: }
382: }
383:
384: protected void componentShowing() {
385: super .componentShowing();
386: if (!expanded) {
387: ((TTW) view).expandTheseNodes(toExpand,
388: getExplorerManager().getRootContext());
389: expanded = true;
390: }
391: // initialize divider location
392: split.setDividerLocation(getPreferredSize().width / 2);
393: }
394:
395: protected void validateRootContext() {
396: Node n = initRC();
397: setRootContext(n);
398: }
399:
400: /** Resolves to the singleton instance of options panel. */
401: public Object readResolve() throws ObjectStreamException {
402: if (singleton == null) {
403: singleton = this ;
404: }
405: singleton.scheduleValidation();
406: // set deserialized root node
407: rootNode = getRootContext();
408: return singleton;
409: }
410:
411: private synchronized Node initRC() {
412: if (rootNode == null) {
413: rootNode = new OptionsFilterNode();
414: }
415: return rootNode;
416: }
417:
418: /** Expands the node in explorer.
419: */
420: private static void expandNodes(Node n, final int depth,
421: final Collection<Node> list) {
422: if (depth == 0) {
423: return;
424: }
425:
426: DataObject obj = (DataObject) n.getCookie(DataObject.class);
427: if (obj instanceof DataShadow) {
428: obj = ((DataShadow) obj).getOriginal();
429: }
430:
431: if (obj != null) {
432: if (!obj.getPrimaryFile().getPath().startsWith(
433: "UI/Services")) { // NOI18N
434: return;
435: }
436:
437: InstanceCookie ic = (InstanceCookie) obj
438: .getCookie(InstanceCookie.class);
439: if (ic != null) {
440:
441: if (ic instanceof InstanceCookie.Of) {
442: if (((InstanceCookie.Of) ic)
443: .instanceOf(Node.class)) {
444: return;
445: }
446: }
447: }
448: }
449:
450: // ok, expand this node
451: if (!list.contains(n)) {
452: list.add(n);
453: }
454:
455: Node[] arr = n.getChildren().getNodes(true);
456: for (int i = 0; i < arr.length; i++) {
457: final Node p = arr[i];
458: expandNodes(p, depth - 1, list);
459: }
460: }
461:
462: //
463: // Model to implement the special handling of SettingChildren.* properties
464: //
465:
466: /** Model that tries to extract properties from the node.getValue
467: * instead of creating its getPropertySets.
468: */
469: private static class NTM extends NodeTableModel {
470: public NTM() {
471: super ();
472: }
473:
474: protected Node.Property getPropertyFor(Node node,
475: Node.Property prop) {
476: Object value = node.getValue(prop.getName());
477: if (value instanceof Node.Property) {
478: return (Node.Property) value;
479: }
480:
481: return null;
482: }
483: }
484:
485: private static class TTW extends TreeTableView implements
486: MouseListener, PropertyChangeListener,
487: java.awt.event.ActionListener {
488: /** Dummy property that can be expanded or collapsed. */
489: private final Node.Property indicator = new IndicatorProperty();
490: /** Session layer state indicator property */
491: private final Node.Property session = new SettingChildren.FileStateProperty(
492: SettingChildren.PROP_LAYER_SESSION);
493: /** Modules layer state indicator property */
494: private final Node.Property modules = new SettingChildren.FileStateProperty(
495: SettingChildren.PROP_LAYER_MODULES);
496:
497: /** Active set of properties (columns) */
498: private Node.Property active_set[] = null;
499: PropertyChangeListener weakL = null;
500:
501: public TTW() {
502: super (new NTM());
503:
504: refreshColumns(true);
505: addMouseListener(this );
506: weakL = WeakListeners.propertyChange(this ,
507: SessionManager.getDefault());
508: SessionManager.getDefault().addPropertyChangeListener(
509: weakL);
510:
511: registerKeyboardAction(this , KeyStroke
512: .getKeyStroke('+'),
513: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
514:
515: getAccessibleContext().setAccessibleName(
516: NbBundle.getBundle(OptionsAction.class)
517: .getString("ACSN_optionsTree"));
518: getAccessibleContext().setAccessibleDescription(
519: NbBundle.getBundle(OptionsAction.class)
520: .getString("ACSD_optionsTree"));
521: }
522:
523: public void mouseExited(MouseEvent evt) {
524: }
525:
526: public void mouseReleased(MouseEvent evt) {
527: }
528:
529: public void mousePressed(MouseEvent evt) {
530: }
531:
532: public void mouseClicked(MouseEvent evt) {
533: Component c = evt.getComponent();
534: if (c instanceof JTableHeader) {
535: JTableHeader h = (JTableHeader) c;
536:
537: // show/hide additional properties
538: if (1 == h.columnAtPoint(evt.getPoint())) {
539: refreshColumns(true);
540: }
541: }
542: }
543:
544: public void mouseEntered(MouseEvent evt) {
545: }
546:
547: public void propertyChange(PropertyChangeEvent evt) {
548: if (SessionManager.PROP_OPEN.equals(evt
549: .getPropertyName())) {
550: refreshColumns(false);
551: }
552: }
553:
554: private void refreshColumns(boolean changeSets) {
555: Node.Property new_set[] = active_set;
556: int length = active_set == null ? 0 : active_set.length;
557:
558: if ((changeSets && length == 1)
559: || (!changeSets && length > 1)) {
560: // build full_set
561: new_set = new Node.Property[] { indicator, session,
562: modules };
563:
564: indicator.setDisplayName(NbBundle.getMessage(
565: OptionsAction.class,
566: "LBL_IndicatorProperty_Name_Expanded")); //NOI18N
567: indicator
568: .setShortDescription(NbBundle
569: .getMessage(OptionsAction.class,
570: "LBL_IndicatorProperty_Description_Expanded")); //NOI18N
571: } else {
572: if (changeSets) {
573: new_set = new Node.Property[] { indicator };
574: indicator.setDisplayName(NbBundle.getMessage(
575: OptionsAction.class,
576: "LBL_IndicatorProperty_Name")); //NOI18N
577: indicator
578: .setShortDescription(NbBundle
579: .getMessage(
580: OptionsAction.class,
581: "LBL_IndicatorProperty_Description")); //NOI18N
582: }
583: }
584:
585: if (active_set != new_set) {
586: // setup new columns
587: final Node.Property set[] = new_set;
588: if (SwingUtilities.isEventDispatchThread()) {
589: setNewSet(set);
590: } else {
591: SwingUtilities.invokeLater(new Runnable() {
592: public void run() {
593: setNewSet(set);
594: }
595: });
596: }
597: // remeber the last set of columns
598: active_set = new_set;
599: }
600: }
601:
602: private void setNewSet(Node.Property[] set) {
603: // change columns
604: setProperties(set);
605: // set preferred colunm sizes
606: setTreePreferredWidth(set.length == 1 ? 480 : 300);
607: setTableColumnPreferredWidth(0, 20);
608: for (int i = 1; i < set.length; i++) {
609: setTableColumnPreferredWidth(i, 60);
610: }
611: }
612:
613: public void actionPerformed(ActionEvent e) {
614: refreshColumns(true);
615: }
616:
617: public void expandTheseNodes(Collection<Node> paths,
618: Node root) {
619: Iterator<Node> it = paths.iterator();
620:
621: Node first = null;
622: while (it.hasNext()) {
623: Node n = it.next();
624: if (first == null) {
625: first = n;
626: }
627:
628: this .expandNode(n);
629: }
630:
631: if (first != null) {
632: collapseNode(first);
633: expandNode(first);
634: }
635:
636: // move to top
637: tree.scrollRowToVisible(0);
638: }
639:
640: /** Dummy placeholder property. */
641: private static final class IndicatorProperty extends
642: PropertySupport.ReadOnly<String> {
643:
644: public IndicatorProperty() {
645: super ("indicator", String.class, "", ""); // NOI18N
646: }
647:
648: public String getValue() {
649: return ""; // NOI18N
650: }
651:
652: }
653:
654: }
655:
656: private static class OptionsFilterNode extends FilterNode {
657: public OptionsFilterNode() {
658: super (NbPlaces.getDefault().session(),
659: new SettingChildren(NbPlaces.getDefault()
660: .session()));
661: }
662:
663: public HelpCtx getHelpCtx() {
664: return new HelpCtx(OptionsFilterNode.class);
665: }
666:
667: public Node.Handle getHandle() {
668: return new H();
669: }
670:
671: private static class H implements Node.Handle {
672: H() {
673: }
674:
675: private static final long serialVersionUID = -5158460093499159177L;
676:
677: public Node getNode() throws java.io.IOException {
678: return new OptionsFilterNode();
679: }
680: }
681: }
682:
683: } // end of inner class OptionsPanel
684: }
|