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.Component;
045: import java.awt.Dimension;
046: import java.awt.Rectangle;
047: import java.io.IOException;
048: import java.awt.datatransfer.*;
049: import java.util.ArrayList;
050: import java.util.HashMap;
051: import java.util.LinkedList;
052: import java.util.List;
053: import java.util.Map;
054: import javax.swing.undo.UndoableEdit;
055: import org.openide.*;
056: import org.openide.nodes.*;
057: import org.openide.filesystems.FileObject;
058: import org.openide.util.Mutex;
059: import org.openide.util.MutexException;
060: import org.openide.util.datatransfer.PasteType;
061: import org.openide.util.datatransfer.ExTransferable;
062: import org.openide.util.datatransfer.MultiTransferObject;
063: import org.openide.ErrorManager;
064: import org.openide.loaders.DataObject;
065: import org.netbeans.modules.form.project.*;
066: import org.netbeans.modules.form.layoutdesign.*;
067: import org.netbeans.modules.form.layoutsupport.LayoutSupportManager;
068: import org.netbeans.modules.form.palette.BeanInstaller;
069:
070: /**
071: * Support class for copy/cut/paste operations in form editor.
072: *
073: * @author Tomas Pavek
074: */
075:
076: class CopySupport {
077:
078: private static final String flavorMimeType = "application/x-form-metacomponent;class=java.lang.Object"; // NOI18N
079:
080: private static DataFlavor copyFlavor;
081: private static DataFlavor cutFlavor;
082:
083: static DataFlavor getComponentCopyFlavor() {
084: if (copyFlavor == null) {
085: copyFlavor = new DataFlavor(flavorMimeType,
086: "COMPONENT_COPY_FLAVOR"); // NOI18N
087: }
088: return copyFlavor;
089: }
090:
091: static DataFlavor getComponentCutFlavor() {
092: if (cutFlavor == null) {
093: cutFlavor = new DataFlavor(flavorMimeType,
094: "COMPONENT_CUT_FLAVOR"); // NOI18N
095: }
096: return cutFlavor;
097: }
098:
099: // -----------
100:
101: static class RADTransferable implements Transferable {
102: private RADComponent radComponent;
103: private DataFlavor[] flavors;
104:
105: RADTransferable(DataFlavor flavor, RADComponent radComponent) {
106: this (new DataFlavor[] { flavor }, radComponent);
107: }
108:
109: RADTransferable(DataFlavor[] flavors, RADComponent radComponent) {
110: this .flavors = flavors;
111: this .radComponent = radComponent;
112: }
113:
114: public DataFlavor[] getTransferDataFlavors() {
115: return flavors;
116: }
117:
118: public boolean isDataFlavorSupported(DataFlavor flavor) {
119: for (int i = 0; i < flavors.length; i++) {
120: if (flavors[i] == flavor) {
121: return true;
122: }
123: }
124: return false;
125: }
126:
127: public Object getTransferData(DataFlavor flavor)
128: throws UnsupportedFlavorException, IOException {
129: if ("x-form-metacomponent".equals(flavor.getSubType())) // NOI18N
130: return radComponent;
131:
132: throw new UnsupportedFlavorException(flavor);
133: }
134: }
135:
136: // -----------
137:
138: /** Checks whether a component can be moved to a container (the component
139: * cannot be pasted to its own sub-container or even to itself). */
140: public static boolean canPasteCut(RADComponent sourceComponent,
141: FormModel targetForm, RADComponent targetComponent) {
142: if (!sourceComponent.isInModel())
143: return false;
144: if (sourceComponent.getFormModel() != targetForm)
145: return true;
146:
147: if (targetComponent == null)
148: return targetForm.getModelContainer().getIndexOf(
149: sourceComponent) < 0;
150:
151: return sourceComponent != targetComponent
152: && sourceComponent.getParentComponent() != targetComponent
153: && !sourceComponent.isParentComponent(targetComponent);
154: }
155:
156: // -----------
157:
158: static void createPasteTypes(Transferable trans,
159: java.util.List<PasteType> s, FormModel targetForm,
160: RADComponent targetComponent) {
161: if (targetForm.isReadOnly()) {
162: return;
163: }
164:
165: Transferable[] allTrans;
166: if (trans.isDataFlavorSupported(ExTransferable.multiFlavor)) {
167: try {
168: MultiTransferObject transObj = (MultiTransferObject) trans
169: .getTransferData(ExTransferable.multiFlavor);
170: allTrans = new Transferable[transObj.getCount()];
171: for (int i = 0; i < allTrans.length; i++) {
172: allTrans[i] = transObj.getTransferableAt(i);
173: }
174: } catch (UnsupportedFlavorException ex) {
175: ErrorManager.getDefault().notify(
176: ErrorManager.INFORMATIONAL, ex);
177: return;
178: } catch (IOException ex) {
179: ErrorManager.getDefault().notify(
180: ErrorManager.INFORMATIONAL, ex);
181: return;
182: }
183: } else {
184: allTrans = new Transferable[] { trans };
185: }
186:
187: boolean canPaste = false;
188: boolean cut = false; // true - cut, false - copy
189: List<RADComponent> sourceComponents = null;
190:
191: for (int i = 0; i < allTrans.length; i++) {
192: Transferable t = allTrans[i];
193: boolean metaCompTransfer;
194: if (t.isDataFlavorSupported(getComponentCopyFlavor())) {
195: assert !cut;
196: metaCompTransfer = true;
197: } else if (t.isDataFlavorSupported(getComponentCutFlavor())) {
198: assert cut || sourceComponents == null;
199: metaCompTransfer = true;
200: cut = true;
201: } else {
202: metaCompTransfer = false;
203: }
204: if (metaCompTransfer) {
205: RADComponent transComp = null;
206: try {
207: Object data = t.getTransferData(t
208: .getTransferDataFlavors()[0]);
209: if (data instanceof RADComponent) {
210: transComp = (RADComponent) data;
211: }
212: } catch (UnsupportedFlavorException e) {
213: } // should not happen
214: catch (java.io.IOException e) {
215: } // should not happen
216:
217: if (transComp != null
218: // only cut to another container
219: && (!cut || canPasteCut(transComp, targetForm,
220: targetComponent))
221: // must be a valid source/target combination
222: && (MetaComponentCreator.canAddComponent(
223: transComp.getBeanClass(),
224: targetComponent) || (!cut && MetaComponentCreator
225: .canApplyComponent(transComp
226: .getBeanClass(),
227: targetComponent)))
228: // hack needed due to screwed design of menu metacomponents
229: && (!(targetComponent instanceof RADMenuComponent) || transComp instanceof RADMenuItemComponent)) { // pasting this meta component is allowed
230: if (sourceComponents == null) {
231: sourceComponents = new LinkedList<RADComponent>();
232: }
233: sourceComponents.add(getComponentToCopy(transComp,
234: targetComponent, cut));
235: canPaste = true;
236: }
237: } else { // java node (compiled class) could be copied
238: ClassSource classSource = CopySupport
239: .getCopiedBeanClassSource(t);
240: if (classSource != null) {
241: // && (MetaComponentCreator.canAddComponent(cls, component)
242: // || MetaComponentCreator.canApplyComponent(cls, component)))
243: s.add(new ClassPaste(t, classSource, targetForm,
244: targetComponent));
245: canPaste = true;
246: }
247: }
248: }
249:
250: if (sourceComponents != null) {
251: s.add(new RADPaste(sourceComponents, targetForm,
252: targetComponent, cut));
253: }
254:
255: if (!canPaste
256: && targetComponent != null
257: && (!(targetComponent instanceof ComponentContainer) || MetaComponentCreator
258: .isTransparentLayoutComponent(targetComponent))
259: && targetComponent.getParentComponent() != null) {
260: // allow paste on non-container component - try its parent
261: createPasteTypes(trans, s, targetForm, targetComponent
262: .getParentComponent());
263: }
264: }
265:
266: private static RADComponent getComponentToCopy(
267: RADComponent metacomp, RADComponent targetComp, boolean cut) {
268: RADComponent parent = metacomp.getParentComponent();
269: if (MetaComponentCreator.isTransparentLayoutComponent(parent)
270: && (!cut || parent.getParentComponent() != targetComp)) {
271: return parent;
272: }
273: return metacomp;
274: }
275:
276: /**
277: * Paste type for meta components.
278: */
279: private static class RADPaste extends PasteType implements
280: Mutex.ExceptionAction<Transferable> {
281: private List<RADComponent> sourceComponents;
282: private FormModel targetForm;
283: private RADComponent targetComponent;
284: private boolean fromCut;
285:
286: RADPaste(List<RADComponent> sourceComponents,
287: FormModel targetForm, RADComponent targetComponent,
288: boolean cut) {
289: this .sourceComponents = sourceComponents;
290: this .targetForm = targetForm;
291: this .targetComponent = targetComponent;
292: this .fromCut = cut;
293: }
294:
295: @Override
296: public String getName() {
297: return FormUtils.getBundleString(fromCut ? "CTL_CutPaste"
298: : "CTL_CopyPaste"); // NOI18N
299: }
300:
301: public Transferable paste() throws IOException {
302: if (java.awt.EventQueue.isDispatchThread())
303: return doPaste();
304: else { // reinvoke synchronously in AWT thread
305: try {
306: return Mutex.EVENT.readAccess(this );
307: } catch (MutexException ex) {
308: Exception e = ex.getException();
309: if (e instanceof IOException)
310: throw (IOException) e;
311: else { // should not happen, ignore
312: ErrorManager.getDefault().notify(
313: ErrorManager.INFORMATIONAL, e);
314: return ExTransferable.EMPTY;
315: }
316: }
317: }
318: }
319:
320: public Transferable run() throws Exception {
321: return doPaste();
322: }
323:
324: private Transferable doPaste() throws IOException {
325: if (sourceComponents == null || sourceComponents.isEmpty()) {
326: return null;
327: }
328:
329: FormModel sourceForm = sourceComponents.get(0)
330: .getFormModel();
331: boolean move = fromCut && sourceForm == targetForm;
332: boolean autoUndo = true; // in case of unexpected error, for robustness
333: LayoutModel sourceLayout = sourceForm.getLayoutModel();
334: LayoutModel targetLayout = targetForm.getLayoutModel();
335: List<RADComponent> copiedComponents = null; // only for new-to-new layout copy
336:
337: // is the target container a visual container with new layout?
338: boolean targetNewLayout = targetComponent instanceof RADVisualContainer
339: && ((RADVisualContainer) targetComponent)
340: .getLayoutSupport() == null;
341: RADVisualContainer sourceContainer = null;
342: Map<String, String> sourceToTargetId = null; // for new-to-new layout copy
343: Map<String, Rectangle> idToBounds = null; // for old-to-new layout copy
344:
345: if (targetNewLayout) {
346: // do all source components come from one visual container from
347: // which we can copy the layout to the new layout?
348: for (RADComponent sourceComp : sourceComponents) {
349: if (sourceComp instanceof RADVisualComponent) {
350: RADVisualComponent sourceCompVisual = (RADVisualComponent) sourceComp;
351: if (!sourceCompVisual.isMenuComponent()) {
352: RADVisualContainer parent = sourceCompVisual
353: .getParentContainer();
354: if (sourceContainer == null) {
355: sourceContainer = parent;
356: } else if (parent != sourceContainer) {
357: sourceContainer = null;
358: break;
359: }
360: }
361: } else {
362: sourceContainer = null;
363: break;
364: }
365: }
366: if (sourceContainer != null
367: && sourceContainer.getLayoutSupport() != null
368: && (getComponentBounds(sourceComponents.get(0)) == null || !isConvertibleLayout(sourceContainer))) {
369: sourceContainer = null; // old layout not suitable for conversion
370: }
371: }
372:
373: // do we need to care about undo in layout model?
374: Object layoutUndoMark = null;
375: UndoableEdit layoutEdit = null;
376: boolean layoutModelAffected = false;
377: if (targetNewLayout) {
378: layoutModelAffected = true;
379: } else if (move) {
380: for (RADComponent sourceComp : sourceComponents) {
381: if (sourceComp instanceof RADVisualComponent) {
382: RADVisualComponent sourceCompVisual = (RADVisualComponent) sourceComp;
383: if (!sourceCompVisual.isMenuComponent()) {
384: RADVisualContainer parent = sourceCompVisual
385: .getParentContainer();
386: if (parent != null
387: && parent.getLayoutSupport() == null) {
388: // this source component comes from new layout
389: layoutModelAffected = true;
390: break;
391: }
392: }
393: }
394: }
395: }
396: if (layoutModelAffected) {
397: layoutUndoMark = targetLayout.getChangeMark();
398: layoutEdit = targetLayout.getUndoableEdit();
399: }
400:
401: try {
402: // copy or move the components
403: for (RADComponent sourceComp : sourceComponents) {
404: RADComponent copiedComp;
405: if (!move) {
406: copiedComp = targetForm.getComponentCreator()
407: .copyComponent(sourceComp,
408: targetComponent);
409: if (copiedComp == null) {
410: return null; // copy failed...
411: }
412: } else { // move within the same form
413: targetForm.getComponentCreator().moveComponent(
414: sourceComp, targetComponent);
415: copiedComp = sourceComp;
416: }
417:
418: if (copiedComp instanceof RADVisualComponent
419: && !((RADVisualComponent) copiedComp)
420: .isMenuComponent()
421: && sourceComp instanceof RADVisualComponent) { // might be even a LayoutManager componen...
422: // for visual components we must care about the layout model (new layout)
423: if (targetNewLayout) {
424: RADVisualContainer targetContainer = (RADVisualContainer) targetComponent;
425: if (sourceContainer != null) { // source is one visual container, we can copy the layout
426: if (sourceContainer.getLayoutSupport() == null) { // copying from new layout
427: if (sourceToTargetId == null) {
428: sourceToTargetId = new HashMap<String, String>();
429: }
430: sourceToTargetId.put(sourceComp
431: .getId(), copiedComp
432: .getId());
433: // remember the copied component - for next paste operation
434: if (copiedComp != sourceComp) {
435: if (copiedComponents == null) {
436: copiedComponents = new ArrayList<RADComponent>(
437: sourceComponents
438: .size());
439: }
440: copiedComponents
441: .add(copiedComp);
442: }
443: } else { // copying from old layout - requires conversion
444: if (idToBounds == null) {
445: idToBounds = new HashMap<String, Rectangle>();
446: }
447: idToBounds
448: .put(
449: copiedComp.getId(),
450: getComponentBounds(sourceComp));
451: }
452: } else { // layout can't be copied, place component on a default location
453: getLayoutDesigner()
454: .addUnspecifiedComponent(
455: copiedComp.getId(),
456: sourceComp.getId(),
457: getComponentSize(sourceComp),
458: targetComponent.getId());
459: }
460: } else if (move && sourceLayout != null) { // new-to-old layout copy
461: // need to remove layout component
462: LayoutComponent layoutComp = sourceLayout
463: .getLayoutComponent(sourceComp
464: .getId());
465: if (layoutComp != null) {
466: sourceLayout.removeComponent(sourceComp
467: .getId(), !layoutComp
468: .isLayoutContainer());
469: }
470: } // old-to-old layout copy is fully handled by MetaComponentCreator
471: } // also copying menu component needs no additional treatment
472: }
473:
474: // copy the layout for all visual components together
475: if (sourceToTargetId != null) { // new layout to new layout
476: getLayoutDesigner().copyLayout(sourceLayout,
477: sourceToTargetId, targetComponent.getId());
478: } else if (idToBounds != null) { // old layout to new layout
479: getLayoutDesigner().copyLayoutFromOutside(
480: idToBounds, targetComponent.getId(), true);
481: }
482:
483: autoUndo = false;
484: } finally {
485: if (layoutUndoMark != null
486: && !layoutUndoMark.equals(targetLayout
487: .getChangeMark())) {
488: targetForm.addUndoableEdit(layoutEdit);
489: }
490: if (autoUndo) {
491: targetForm.forceUndoOfCompoundEdit();
492: // [don't expect problems in source form...]
493: }
494: }
495:
496: // remove components if cut from another form (the components have been copied)
497: if (fromCut && sourceForm != targetForm) {
498: for (RADComponent sourceComp : sourceComponents) {
499: Node sourceNode = sourceComp.getNodeReference();
500: if (sourceNode != null) {
501: sourceNode.destroy();
502: }
503: }
504: }
505:
506: // return Transferable object for the next paste operation
507: if (fromCut) { // cut - can't be pasted again
508: return ExTransferable.EMPTY;
509: } else if (copiedComponents != null) {
510: // make the newly copied components the source for the next paste
511: if (copiedComponents.size() == 1) {
512: return new RADTransferable(
513: getComponentCopyFlavor(), copiedComponents
514: .get(0));
515: } else {
516: Transferable[] trans = new Transferable[copiedComponents
517: .size()];
518: int i = 0;
519: for (RADComponent comp : copiedComponents) {
520: trans[i++] = new RADTransferable(
521: getComponentCopyFlavor(), comp);
522: }
523: return new ExTransferable.Multi(trans);
524: }
525: } else { // keep the original clipboard content
526: return null;
527: }
528: // TODO: menu components edge cases
529: }
530:
531: private static Rectangle getComponentBounds(
532: RADComponent sourceComp) {
533: FormDesigner designer = FormEditor
534: .getFormDesigner(sourceComp.getFormModel());
535: Component comp = (Component) designer
536: .getComponent(sourceComp);
537: return comp != null ? comp.getBounds() : null;
538: }
539:
540: private static Dimension getComponentSize(
541: RADComponent sourceComp) {
542: FormDesigner designer = FormEditor
543: .getFormDesigner(sourceComp.getFormModel());
544: Component comp = (Component) designer
545: .getComponent(sourceComp);
546: return comp != null ? comp.getSize() : null;
547: }
548:
549: private static boolean isConvertibleLayout(
550: RADVisualContainer metaCont) {
551: LayoutSupportManager ls = metaCont.getLayoutSupport();
552: return !ls.isDedicated()
553: && ls.getSupportedClass() != java.awt.CardLayout.class;
554: }
555:
556: private LayoutDesigner getLayoutDesigner() {
557: return FormEditor.getFormDesigner(targetForm)
558: .getLayoutDesigner();
559: }
560: }
561:
562: // ------------
563:
564: /**
565: * Paste type for a class (java node).
566: */
567: private static class ClassPaste extends PasteType implements
568: Mutex.ExceptionAction<Transferable> {
569: private Transferable transferable;
570: private ClassSource classSource;
571: private FormModel targetForm;
572: private RADComponent targetComponent; // may be null if pasting to Other Components
573:
574: ClassPaste(Transferable t, ClassSource classSource,
575: FormModel targetForm, RADComponent targetComponent) {
576: this .transferable = t;
577: this .classSource = classSource;
578: this .targetForm = targetForm;
579: this .targetComponent = targetComponent;
580: }
581:
582: public Transferable paste() throws IOException {
583: if (java.awt.EventQueue.isDispatchThread()) {
584: return doPaste();
585: } else { // reinvoke synchronously in AWT thread
586: try {
587: return Mutex.EVENT.readAccess(this );
588: } catch (MutexException ex) {
589: Exception e = ex.getException();
590: if (e instanceof IOException)
591: throw (IOException) e;
592: else { // should not happen, ignore
593: ErrorManager.getDefault().notify(
594: ErrorManager.INFORMATIONAL, e);
595: return transferable;
596: }
597: }
598: }
599: }
600:
601: public Transferable run() throws Exception {
602: return doPaste();
603: }
604:
605: private Transferable doPaste() throws IOException {
606: if ((classSource.getClassName().indexOf('.') == -1) // Issue 79573
607: && !FormJavaSource.isInDefaultPackage(targetForm)) {
608: String message = FormUtils
609: .getBundleString("MSG_DefaultPackageBean"); // NOI18N
610: NotifyDescriptor nd = new NotifyDescriptor.Message(
611: message, NotifyDescriptor.WARNING_MESSAGE);
612: DialogDisplayer.getDefault().notify(nd);
613: } else {
614: targetForm.getComponentCreator().createComponent(
615: classSource, targetComponent, null);
616: }
617: return transferable;
618: }
619: }
620:
621: static String getCopiedBeanClassName(FileObject fo) {
622: return BeanInstaller.findJavaBeanName(fo);
623: }
624:
625: static ClassSource getCopiedBeanClassSource(Transferable t) {
626: DataObject dobj = NodeTransfer.cookie(t, NodeTransfer.COPY,
627: DataObject.class);
628: FileObject fo = (dobj != null && dobj.isValid()) ? dobj
629: .getPrimaryFile() : null;
630: if (fo == null)
631: return null;
632:
633: String clsName = getCopiedBeanClassName(fo);
634: if (clsName == null)
635: return null;
636:
637: return ClassPathUtils.getProjectClassSource(fo, clsName);
638: }
639:
640: }
|