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.form;
043:
044: import java.awt.event.*;
045: import java.beans.*;
046: import java.awt.datatransfer.*;
047: import java.util.ArrayList;
048: import java.util.List;
049: import javax.swing.text.DefaultEditorKit;
050:
051: import org.openide.*;
052: import org.openide.actions.PasteAction;
053: import org.openide.nodes.*;
054: import org.openide.explorer.*;
055: import org.openide.awt.UndoRedo;
056: import org.openide.explorer.view.BeanTreeView;
057: import org.openide.windows.*;
058: import org.openide.util.*;
059: import org.openide.util.actions.SystemAction;
060: import org.openide.util.actions.ActionPerformer;
061: import org.openide.util.datatransfer.*;
062:
063: import org.netbeans.modules.form.actions.TestAction;
064: import org.netbeans.modules.form.palette.PaletteUtils;
065:
066: /**
067: * The ComponentInspector - special explorer for form editor.
068: *
069: * @author Tomas Pavek
070: */
071:
072: public class ComponentInspector extends TopComponent implements
073: ExplorerManager.Provider {
074: private ExplorerManager explorerManager;
075:
076: private TestAction testAction = SystemAction.findObject(
077: TestAction.class, true);
078:
079: private PasteAction pasteAction = SystemAction.findObject(
080: PasteAction.class, true);
081:
082: private CopyCutActionPerformer copyActionPerformer = new CopyCutActionPerformer(
083: true);
084: private CopyCutActionPerformer cutActionPerformer = new CopyCutActionPerformer(
085: false);
086: private DeleteActionPerformer deleteActionPerformer = new DeleteActionPerformer();
087:
088: private ClipboardListener clipboardListener;
089:
090: /** Currently focused form or null if no form is opened/focused */
091: private FormEditor focusedForm;
092:
093: private EmptyInspectorNode emptyInspectorNode;
094:
095: private BeanTreeView treeView;
096:
097: /** Default icon base for control panel. */
098: private static final String EMPTY_INSPECTOR_ICON_BASE = "org/netbeans/modules/form/resources/emptyInspector.gif"; // NOI18N
099:
100: /** The icon for ComponentInspector */
101: private static final String iconURL = "org/netbeans/modules/form/resources/inspector.png"; // NOI18N
102:
103: private static ComponentInspector instance;
104:
105: // ------------
106: // construction (ComponentInspector is a singleton)
107:
108: /** Gets default instance. Don't use directly, it reserved for '.settings' file only,
109: * i.e. deserialization routines, otherwise you can get non-deserialized instance.
110: *
111: * @return ComponentInspector singleton.
112: */
113: public static synchronized ComponentInspector getDefault() {
114: if (instance == null)
115: instance = new ComponentInspector();
116: return instance;
117: }
118:
119: /** Finds default instance. Use in client code instead of {@link #getDefault()}.
120: *
121: * @return ComponentInspector singleton.
122: */
123: public static synchronized ComponentInspector getInstance() {
124: if (instance == null) {
125: TopComponent tc = WindowManager.getDefault()
126: .findTopComponent("ComponentInspector"); // NOI18N
127: if (instance == null) {
128: ErrorManager.getDefault().notify(
129: ErrorManager.INFORMATIONAL,
130: new IllegalStateException(
131: "Can not find ComponentInspector component for its ID. Returned "
132: + tc)); // NOI18N
133: instance = new ComponentInspector();
134: }
135: }
136: return instance;
137: }
138:
139: static boolean exists() {
140: return instance != null;
141: }
142:
143: /** Overriden to explicitely set persistence type of ComponentInspector
144: * to PERSISTENCE_ALWAYS */
145: @Override
146: public int getPersistenceType() {
147: return TopComponent.PERSISTENCE_ALWAYS;
148: }
149:
150: private ComponentInspector() {
151: explorerManager = new ExplorerManager();
152:
153: associateLookup(ExplorerUtils.createLookup(explorerManager,
154: setupActionMap(getActionMap())));
155:
156: emptyInspectorNode = new EmptyInspectorNode();
157: explorerManager.setRootContext(emptyInspectorNode);
158:
159: explorerManager
160: .addPropertyChangeListener(new NodeSelectionListener());
161:
162: setLayout(new java.awt.BorderLayout());
163: createComponents();
164:
165: setIcon(Utilities.loadImage(iconURL));
166: setName(FormUtils.getBundleString("CTL_InspectorTitle")); // NOI18N
167: setToolTipText(FormUtils
168: .getBundleString("HINT_ComponentInspector")); // NOI18N
169: }
170:
171: javax.swing.ActionMap setupActionMap(javax.swing.ActionMap map) {
172: map.put(DefaultEditorKit.copyAction, copyActionPerformer);
173: map.put(DefaultEditorKit.cutAction, cutActionPerformer);
174: //map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(explorerManager));
175: map.put("delete", deleteActionPerformer); // NOI18N
176:
177: return map;
178: }
179:
180: private void createComponents() {
181: treeView = new BeanTreeView();
182: treeView.setDragSource(true);
183: treeView.setDropTarget(true);
184: treeView.getAccessibleContext().setAccessibleName(
185: FormUtils.getBundleString("ACS_ComponentTree")); // NOI18N
186: treeView.getAccessibleContext().setAccessibleDescription(
187: FormUtils.getBundleString("ACSD_ComponentTree")); // NOI18N
188: add(java.awt.BorderLayout.CENTER, treeView);
189: }
190:
191: // --------------
192: // overriding superclasses, implementing interfaces
193:
194: // ExplorerManager.Provider
195: public ExplorerManager getExplorerManager() {
196: return explorerManager;
197: }
198:
199: @Override
200: public UndoRedo getUndoRedo() {
201: UndoRedo ur = focusedForm != null ? focusedForm
202: .getFormUndoRedoManager() : null;
203: return ur != null ? ur : super .getUndoRedo();
204: }
205:
206: @Override
207: public HelpCtx getHelpCtx() {
208: return new HelpCtx("gui.component-inspector"); // NOI18N
209: }
210:
211: /** Replaces this in object stream.
212: *
213: * @return ResolvableHelper
214: */
215: @Override
216: public Object writeReplace() {
217: return new ResolvableHelper();
218: }
219:
220: @Override
221: protected void componentActivated() {
222: attachActions();
223: }
224:
225: @Override
226: protected void componentDeactivated() {
227: detachActions();
228: }
229:
230: // ------------
231: // activating and focusing
232:
233: synchronized void attachActions() {
234: ExplorerUtils.activateActions(explorerManager, true);
235: updatePasteAction();
236:
237: Clipboard c = getClipboard();
238: if (c instanceof ExClipboard) {
239: ExClipboard clip = (ExClipboard) c;
240: if (clipboardListener == null)
241: clipboardListener = new ClipboardChangesListener();
242: clip.addClipboardListener(clipboardListener);
243: }
244: }
245:
246: synchronized void detachActions() {
247: ExplorerUtils.activateActions(explorerManager, false);
248:
249: Clipboard c = getClipboard();
250: if (c instanceof ExClipboard) {
251: ExClipboard clip = (ExClipboard) c;
252: clip.removeClipboardListener(clipboardListener);
253: }
254: }
255:
256: /** This method focuses the ComponentInspector on given form.
257: * @param form the form to focus on
258: */
259: public void focusForm(final FormEditor form) {
260: if (focusedForm != form)
261: focusFormInAwtThread(form, 0);
262: }
263:
264: /** This method focuses the ComponentInspector on given form.
265: * @param form the form to focus on
266: * @param visible true to open inspector, false to close
267: */
268: public void focusForm(final FormEditor form, boolean visible) {
269: if (focusedForm != form)
270: focusFormInAwtThread(form, visible ? 1 : -1);
271: }
272:
273: private void focusFormInAwtThread(final FormEditor form,
274: final int visibility) {
275: if (java.awt.EventQueue.isDispatchThread()) {
276: focusFormImpl(form, visibility);
277: } else {
278: java.awt.EventQueue.invokeLater(new Runnable() {
279: public void run() {
280: focusFormImpl(form, visibility);
281: }
282: });
283: }
284: }
285:
286: private void focusFormImpl(FormEditor form, int visibility) {
287: focusedForm = form;
288:
289: if ((form == null) || (form.getFormDesigner() == null)) {
290: testAction.setFormDesigner(null);
291: PaletteUtils.setContext(null);
292:
293: // swing memory leak workaround
294: removeAll();
295: createComponents();
296: revalidate();
297:
298: getExplorerManager().setRootContext(emptyInspectorNode);
299: } else {
300: Node[] selectedNodes = form.getFormDesigner()
301: .getSelectedComponentNodes();
302:
303: testAction.setFormDesigner(form.getFormDesigner());
304: PaletteUtils.setContext(form.getFormDataObject()
305: .getPrimaryFile());
306:
307: Node formNode = form.getFormRootNode();
308: if (formNode == null) { // form not loaded yet, should not happen
309: System.err
310: .println("Warning: FormEditorSupport.getFormRootNode() returns null"); // NOI18N
311: getExplorerManager().setRootContext(emptyInspectorNode);
312: } else
313: getExplorerManager().setRootContext(formNode);
314:
315: try {
316: getExplorerManager().setSelectedNodes(selectedNodes);
317: } catch (PropertyVetoException ex) {
318: ex.printStackTrace(); // should not happen
319: }
320:
321: }
322:
323: if (visibility > 0)
324: open();
325: else if (visibility < 0)
326: close();
327: }
328:
329: public FormEditor getFocusedForm() {
330: return focusedForm;
331: }
332:
333: /** Called to synchronize with FormDesigner. Invokes NodeSelectionListener.
334: */
335: void setSelectedNodes(Node[] nodes, FormEditor form)
336: throws PropertyVetoException {
337: if (form == focusedForm)
338: getExplorerManager().setSelectedNodes(nodes);
339: }
340:
341: public Node[] getSelectedNodes() {
342: return getExplorerManager().getSelectedNodes();
343: }
344:
345: private Node[] getSelectedRootNodes() {
346: // exclude nodes that are under other selected nodes
347: Node[] selected = getExplorerManager().getSelectedNodes();
348: if (selected != null && selected.length > 1) {
349: List<Node> list = new ArrayList<Node>(selected.length);
350: for (int i = 0; i < selected.length; i++) {
351: Node node = selected[i];
352: boolean subcontained = false;
353: for (int j = 0; j < selected.length; j++) {
354: if (j != i && isSubcontainedNode(node, selected[j])) {
355: subcontained = true;
356: break;
357: }
358: }
359: if (!subcontained) {
360: list.add(node);
361: }
362: }
363: if (list.size() < selected.length) {
364: selected = list.toArray(new Node[list.size()]);
365: }
366: }
367: return selected;
368: }
369:
370: private static boolean isSubcontainedNode(Node node,
371: Node maybeParent) {
372: RADComponentCookie cookie = node
373: .getCookie(RADComponentCookie.class);
374: RADComponent comp = (cookie != null) ? cookie.getRADComponent()
375: : null;
376: if (comp != null) {
377: cookie = maybeParent.getCookie(RADComponentCookie.class);
378: RADComponent parentComp = (cookie != null) ? cookie
379: .getRADComponent() : null;
380: if (parentComp != null
381: && parentComp.isParentComponent(comp)) {
382: return true;
383: }
384: }
385: return false;
386: }
387:
388: // ---------------
389: // actions
390:
391: // fix of issue 42082
392: private void updatePasteAction() {
393: if (java.awt.EventQueue.isDispatchThread()) {
394: updatePasteActionInAwtThread();
395: } else {
396: java.awt.EventQueue.invokeLater(new Runnable() {
397: public void run() {
398: updatePasteActionInAwtThread();
399: }
400: });
401: }
402: }
403:
404: private void updatePasteActionInAwtThread() {
405: Node[] selected = getExplorerManager().getSelectedNodes();
406: if (selected != null && selected.length >= 1) {
407: // pasting considered only on the first selected node
408: Clipboard clipboard = getClipboard();
409: Transferable trans = clipboard.getContents(this ); // [this??]
410: if (trans != null) {
411: Node node = selected[0];
412: PasteType[] pasteTypes = node.getPasteTypes(trans);
413: if (pasteTypes.length != 0) {
414: // transfer accepted by the node, we are done
415: pasteAction.setPasteTypes(pasteTypes);
416: return;
417: }
418:
419: boolean multiFlavor = false;
420: try {
421: multiFlavor = trans
422: .isDataFlavorSupported(ExTransferable.multiFlavor);
423: } catch (Exception e) {
424: } // ignore, should not happen
425:
426: if (multiFlavor) {
427: // The node did not accept whole multitransfer as is - try
428: // to break it into individual transfers and paste them in
429: // sequence instead.
430: try {
431: MultiTransferObject mto = (MultiTransferObject) trans
432: .getTransferData(ExTransferable.multiFlavor);
433:
434: int n = mto.getCount(), i;
435: Transferable[] t = new Transferable[n];
436: PasteType[] p = new PasteType[n];
437:
438: for (i = 0; i < n; i++) {
439: t[i] = mto.getTransferableAt(i);
440: pasteTypes = node.getPasteTypes(t[i]);
441: if (pasteTypes.length == 0)
442: break;
443:
444: p[i] = pasteTypes[0]; // ??
445: }
446:
447: if (i == n) { // all individual transfers accepted
448: pasteAction
449: .setPasteTypes(new PasteType[] { new MultiPasteType(
450: t, p) });
451: return;
452: }
453: } catch (Exception ex) {
454: org.openide.ErrorManager.getDefault().notify(
455: org.openide.ErrorManager.INFORMATIONAL,
456: ex);
457: }
458: }
459: }
460: }
461:
462: pasteAction.setPasteTypes(null);
463: }
464:
465: private Clipboard getClipboard() {
466: Clipboard c = Lookup.getDefault().lookup(
467: java.awt.datatransfer.Clipboard.class);
468: if (c == null)
469: c = java.awt.Toolkit.getDefaultToolkit()
470: .getSystemClipboard();
471: return c;
472: }
473:
474: @Override
475: protected String preferredID() {
476: return getClass().getName();
477: }
478:
479: @Override
480: @SuppressWarnings("deprecation")
481: public boolean requestFocusInWindow() {
482: super .requestFocusInWindow();
483: return treeView.requestFocusInWindow();
484: }
485:
486: // ---------------
487: // innerclasses
488:
489: // listener on nodes selection (ExplorerManager)
490: private class NodeSelectionListener implements
491: PropertyChangeListener, ActionListener, Runnable {
492: private javax.swing.Timer timer;
493:
494: NodeSelectionListener() {
495: timer = new javax.swing.Timer(150, this );
496: timer.setCoalesce(true);
497: timer.setRepeats(false);
498: }
499:
500: public void propertyChange(PropertyChangeEvent evt) {
501: if (!ExplorerManager.PROP_SELECTED_NODES.equals(evt
502: .getPropertyName()))
503: return;
504:
505: FormDesigner designer;
506: if (focusedForm == null
507: || (designer = focusedForm.getFormDesigner()) == null)
508: return;
509:
510: Node[] selectedNodes = getExplorerManager()
511: .getSelectedNodes();
512:
513: if (designer.getDesignerMode() == FormDesigner.MODE_CONNECT) {
514: // specially handle node selection in connection mode
515: if (selectedNodes.length > 0) {
516: RADComponentCookie cookie = selectedNodes[0]
517: .getCookie(RADComponentCookie.class);
518: if (cookie != null
519: && cookie.getRADComponent() == designer
520: .getConnectionSource()
521: && selectedNodes.length > 1) {
522: cookie = selectedNodes[selectedNodes.length - 1]
523: .getCookie(RADComponentCookie.class);
524: }
525: if (cookie != null)
526: designer.connectBean(cookie.getRADComponent(),
527: true);
528: }
529: } else if (evt.getSource() == ComponentInspector.this
530: .getExplorerManager()) { // the change comes from ComponentInspector => synchronize FormDesigner
531: designer.clearSelectionImpl();
532: for (int i = 0; i < selectedNodes.length; i++) {
533: FormCookie formCookie = selectedNodes[i]
534: .getCookie(FormCookie.class);
535: if (formCookie != null) {
536: Node node = formCookie.getOriginalNode();
537: if (node instanceof RADComponentNode)
538: designer
539: .addComponentToSelectionImpl(((RADComponentNode) node)
540: .getRADComponent());
541: }
542: }
543: designer.repaintSelection();
544: }
545:
546: // refresh nodes' lookup with current set of cookies
547: for (int i = 0; i < selectedNodes.length; i++)
548: ((FormNode) selectedNodes[i]).updateCookies();
549:
550: // restart waiting for expensive part of the update
551: timer.restart();
552: }
553:
554: public void actionPerformed(ActionEvent evt) { // invoked by Timer
555: java.awt.EventQueue.invokeLater(this ); // replan to EventQueue thread
556: }
557:
558: /** Updates activated nodes and actions. It is executed via timer
559: * restarted each time a new selection change appears - if they come
560: * quickly e.g. due to the user is holding a cursor key, this
561: * (relatively time expensive update) is done only at the end.
562: */
563: public void run() {
564: Node[] selectedNodes = getExplorerManager()
565: .getSelectedNodes();
566: setActivatedNodes(selectedNodes);
567: // set activated nodes also on FormDesigner - to keep it in sync
568: FormDesigner designer = focusedForm != null ? focusedForm
569: .getFormDesigner() : null;
570: if (designer != null)
571: designer.setActivatedNodes(selectedNodes);
572:
573: updatePasteAction();
574:
575: timer.stop();
576: }
577: }
578:
579: // listener on clipboard changes
580: private class ClipboardChangesListener implements ClipboardListener {
581: public void clipboardChanged(ClipboardEvent ev) {
582: if (!ev.isConsumed())
583: updatePasteAction();
584: }
585: }
586:
587: // performer for DeleteAction
588: private class DeleteActionPerformer extends
589: javax.swing.AbstractAction implements ActionPerformer,
590: Mutex.Action<Object> {
591: private Node[] nodesToDestroy;
592:
593: public void actionPerformed(ActionEvent e) {
594: performAction(null);
595: }
596:
597: public void performAction(SystemAction action) {
598: Node[] selected = getSelectedRootNodes();
599:
600: if (selected == null || selected.length == 0)
601: return;
602:
603: for (int i = 0; i < selected.length; i++)
604: if (!selected[i].canDestroy())
605: return;
606:
607: try { // clear nodes selection first
608: getExplorerManager().setSelectedNodes(new Node[0]);
609: } catch (PropertyVetoException e) {
610: } // cannot be vetoed
611:
612: nodesToDestroy = selected;
613: if (java.awt.EventQueue.isDispatchThread())
614: doDelete();
615: else
616: // reinvoke synchronously in AWT thread
617: Mutex.EVENT.readAccess(this );
618: }
619:
620: public Object run() {
621: doDelete();
622: return null;
623: }
624:
625: private void doDelete() {
626: if (nodesToDestroy != null) {
627: for (int i = 0; i < nodesToDestroy.length; i++) {
628: try {
629: nodesToDestroy[i].destroy();
630: } catch (java.io.IOException ex) { // should not happen
631: ex.printStackTrace();
632: }
633: }
634: nodesToDestroy = null;
635: }
636: }
637: }
638:
639: // performer for CopyAction and CutAction
640: private class CopyCutActionPerformer extends
641: javax.swing.AbstractAction implements ActionPerformer {
642: private boolean copy;
643:
644: public CopyCutActionPerformer(boolean copy) {
645: this .copy = copy;
646: }
647:
648: public void actionPerformed(ActionEvent e) {
649: performAction(null);
650: }
651:
652: public void performAction(SystemAction action) {
653: Transferable trans;
654: Node[] selected = getSelectedRootNodes();
655:
656: if (selected == null || selected.length == 0)
657: trans = null;
658: else if (selected.length == 1)
659: trans = getTransferableOwner(selected[0]);
660: else {
661: Transferable[] transArray = new Transferable[selected.length];
662: for (int i = 0; i < selected.length; i++)
663: if ((transArray[i] = getTransferableOwner(selected[i])) == null)
664: return;
665:
666: trans = new ExTransferable.Multi(transArray);
667: }
668:
669: if (trans != null) {
670: Clipboard clipboard = getClipboard();
671: clipboard.setContents(trans, new StringSelection("")); // NOI18N
672: }
673: }
674:
675: private Transferable getTransferableOwner(Node node) {
676: try {
677: return copy ? node.clipboardCopy() : node
678: .clipboardCut();
679: } catch (java.io.IOException e) {
680: ErrorManager.getDefault().notify(
681: ErrorManager.INFORMATIONAL, e);
682: return null;
683: }
684: }
685: }
686:
687: // paste type used for ExTransferable.Multi
688: private static class MultiPasteType extends PasteType implements
689: Mutex.ExceptionAction<Transferable> {
690: private Transferable[] transIn;
691: private PasteType[] pasteTypes;
692:
693: MultiPasteType(Transferable[] t, PasteType[] p) {
694: transIn = t;
695: pasteTypes = p;
696: }
697:
698: // performs the paste action
699: public Transferable paste() throws java.io.IOException {
700: if (java.awt.EventQueue.isDispatchThread())
701: return doPaste();
702: else { // reinvoke synchronously in AWT thread
703: try {
704: return Mutex.EVENT.readAccess(this );
705: } catch (MutexException ex) {
706: Exception e = ex.getException();
707: if (e instanceof java.io.IOException)
708: throw (java.io.IOException) e;
709: else { // should not happen, ignore
710: e.printStackTrace();
711: return ExTransferable.EMPTY;
712: }
713: }
714: }
715: }
716:
717: public Transferable run() throws Exception {
718: return doPaste();
719: }
720:
721: private Transferable doPaste() throws java.io.IOException {
722: Transferable[] transOut = new Transferable[transIn.length];
723: for (int i = 0; i < pasteTypes.length; i++) {
724: Transferable newTrans = pasteTypes[i].paste();
725: transOut[i] = newTrans != null ? newTrans : transIn[i];
726: }
727: return new ExTransferable.Multi(transOut);
728: }
729: }
730:
731: // -----------
732:
733: // node for empty ComponentInspector
734: private static class EmptyInspectorNode extends AbstractNode {
735: public EmptyInspectorNode() {
736: super (Children.LEAF);
737: setIconBaseWithExtension(EMPTY_INSPECTOR_ICON_BASE);
738: }
739:
740: @Override
741: public boolean canRename() {
742: return false;
743: }
744: }
745:
746: final public static class ResolvableHelper implements
747: java.io.Serializable {
748: static final long serialVersionUID = 7424646018839457544L;
749:
750: public Object readResolve() {
751: return ComponentInspector.getDefault();
752: }
753: }
754: }
|