• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.editors.layout.gscripts.INode;
21 import com.android.ide.eclipse.adt.editors.layout.gscripts.Point;
22 import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect;
23 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
24 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
25 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
26 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
27 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
28 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
29 import com.android.ide.eclipse.adt.internal.editors.ui.tree.CopyCutAction;
30 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
31 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
32 import com.android.layoutlib.api.ILayoutResult;
33 
34 import org.eclipse.core.runtime.ListenerList;
35 import org.eclipse.jface.text.BadLocationException;
36 import org.eclipse.jface.util.SafeRunnable;
37 import org.eclipse.jface.viewers.ISelection;
38 import org.eclipse.jface.viewers.ISelectionChangedListener;
39 import org.eclipse.jface.viewers.ISelectionProvider;
40 import org.eclipse.jface.viewers.ITreeSelection;
41 import org.eclipse.jface.viewers.SelectionChangedEvent;
42 import org.eclipse.jface.viewers.TreePath;
43 import org.eclipse.jface.viewers.TreeSelection;
44 import org.eclipse.swt.SWT;
45 import org.eclipse.swt.SWTException;
46 import org.eclipse.swt.dnd.Clipboard;
47 import org.eclipse.swt.dnd.DND;
48 import org.eclipse.swt.dnd.DragSource;
49 import org.eclipse.swt.dnd.DragSourceEvent;
50 import org.eclipse.swt.dnd.DragSourceListener;
51 import org.eclipse.swt.dnd.DropTarget;
52 import org.eclipse.swt.dnd.TextTransfer;
53 import org.eclipse.swt.dnd.Transfer;
54 import org.eclipse.swt.events.ControlAdapter;
55 import org.eclipse.swt.events.ControlEvent;
56 import org.eclipse.swt.events.MouseEvent;
57 import org.eclipse.swt.events.MouseListener;
58 import org.eclipse.swt.events.MouseMoveListener;
59 import org.eclipse.swt.events.PaintEvent;
60 import org.eclipse.swt.events.PaintListener;
61 import org.eclipse.swt.events.SelectionAdapter;
62 import org.eclipse.swt.events.SelectionEvent;
63 import org.eclipse.swt.graphics.Color;
64 import org.eclipse.swt.graphics.Font;
65 import org.eclipse.swt.graphics.GC;
66 import org.eclipse.swt.graphics.Image;
67 import org.eclipse.swt.graphics.ImageData;
68 import org.eclipse.swt.graphics.PaletteData;
69 import org.eclipse.swt.graphics.Rectangle;
70 import org.eclipse.swt.widgets.Canvas;
71 import org.eclipse.swt.widgets.Composite;
72 import org.eclipse.swt.widgets.Control;
73 import org.eclipse.swt.widgets.Display;
74 import org.eclipse.swt.widgets.Event;
75 import org.eclipse.swt.widgets.ScrollBar;
76 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
77 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
78 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
79 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
80 import org.eclipse.wst.xml.core.internal.document.NodeContainer;
81 import org.w3c.dom.Node;
82 
83 import java.awt.image.BufferedImage;
84 import java.awt.image.DataBufferInt;
85 import java.awt.image.Raster;
86 import java.util.ArrayList;
87 import java.util.HashSet;
88 import java.util.Iterator;
89 import java.util.LinkedList;
90 import java.util.List;
91 import java.util.ListIterator;
92 import java.util.Set;
93 
94 /**
95  * Displays the image rendered by the {@link GraphicalEditorPart} and handles
96  * the interaction with the widgets.
97  * <p/>
98  * {@link LayoutCanvas} implements the "Canvas" control. The editor part
99  * actually uses the {@link LayoutCanvasViewer}, which is a JFace viewer wrapper
100  * around this control.
101  * <p/>
102  * This class implements {@link ISelectionProvider} so that it can delegate
103  * the selection provider from the {@link LayoutCanvasViewer}.
104  * <p/>
105  * Note that {@link LayoutCanvasViewer} sets a selection change listener on this
106  * control so that it can invoke its own fireSelectionChanged when the control's
107  * selection changes.
108  *
109  * @since GLE2
110  *
111  * TODO list:
112  * - gray on error, keep select but disable d'n'd.
113  * - handle context menu (depending on selection).
114  * - delete, copy/paste linked with menus and in context menu
115  * - context menu handling of layout + local props (via IViewRules)
116  * - outline should include same context menu + delete/copy/paste ops.
117  * - outline should include drop support (from canvas or from palette)
118  */
119 class LayoutCanvas extends Canvas implements ISelectionProvider {
120 
121     /** The layout editor that uses this layout canvas. */
122     private final LayoutEditor mLayoutEditor;
123 
124     /** The Groovy Rules Engine, associated with the current project. */
125     private RulesEngine mRulesEngine;
126 
127     /** SWT clipboard instance. */
128     private Clipboard mClipboard;
129 
130     /*
131      * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}.
132      * This can be null.
133      * When non null, {@link #mLastValidViewInfoRoot} is guaranteed to be non-null too.
134     */
135     private ILayoutResult mLastValidResult;
136 
137     /**
138      * The CanvasViewInfo root created for the last update of {@link #mLastValidResult}.
139      * This is null when {@link #mLastValidResult} is null.
140      * When non null, {@link #mLastValidResult} is guaranteed to be non-null too.
141      */
142     private CanvasViewInfo mLastValidViewInfoRoot;
143 
144     /**
145      * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}
146      * in which case it is also available in {@link #mLastValidResult}.
147      * When false this means the canvas is displaying an out-dated result image & bounds and some
148      * features should be disabled accordingly such a drag'n'drop.
149      * <p/>
150      * When this is false, {@link #mLastValidResult} can be non-null and points to an older
151      * layout result.
152      */
153     private boolean mIsResultValid;
154 
155     /** Current background image. Null when there's no image. */
156     private Image mImage;
157 
158     /** The current selection list. The list is never null, however it can be empty. */
159     private final LinkedList<CanvasSelection> mSelections = new LinkedList<CanvasSelection>();
160 
161     /** CanvasSelection border color. Do not dispose, it's a system color. */
162     private Color mSelectionFgColor;
163 
164     /** GC wrapper given to the IViewRule methods. The GC itself is only defined in the
165      *  context of {@link #onPaint(PaintEvent)}; otherwise it is null. */
166     private final GCWrapper mGCWrapper;
167 
168     /** Default font used on the canvas. Do not dispose, it's a system font. */
169     private Font mFont;
170 
171     /** Current hover view info. Null when no mouse hover. */
172     private CanvasViewInfo mHoverViewInfo;
173 
174     /** Current mouse hover border rectangle. Null when there's no mouse hover.
175      * The rectangle coordinates do not take account of the translation, which must
176      * be applied to the rectangle when drawing.
177      */
178     private Rectangle mHoverRect;
179 
180     /** Hover border color. Must be disposed, it's NOT a system color. */
181     private Color mHoverFgColor;
182 
183     /** Outline color. Do not dispose, it's a system color. */
184     private Color mOutlineColor;
185 
186     /**
187      * The <em>current</em> alternate selection, if any, which changes when the Alt key is
188      * used during a selection. Can be null.
189      */
190     private CanvasAlternateSelection mAltSelection;
191 
192     /** When true, always display the outline of all views. */
193     private boolean mShowOutline;
194 
195     /** Drop target associated with this composite. */
196     private DropTarget mDropTarget;
197 
198     /** Drop listener, with feedback from current drop */
199     private CanvasDropListener mDropListener;
200 
201     /** Factory that can create {@link INode} proxies. */
202     private final NodeFactory mNodeFactory = new NodeFactory();
203 
204     /** Vertical scaling & scrollbar information. */
205     private ScaleInfo mVScale;
206 
207     /** Horizontal scaling & scrollbar information. */
208     private ScaleInfo mHScale;
209 
210     /** Drag source associated with this canvas. */
211     private DragSource mSource;
212 
213     /** List of clients listening to selection changes. */
214     private final ListenerList mSelectionListeners = new ListenerList();
215 
216     /** The current Outline Page, to set its model. */
217     private OutlinePage2 mOutlinePage;
218 
219     /** Barrier set when updating the selection to prevent from recursively
220      * invoking ourselves. */
221     private boolean mInsideUpdateSelection;
222 
223 
LayoutCanvas(LayoutEditor layoutEditor, RulesEngine rulesEngine, Composite parent, int style)224     public LayoutCanvas(LayoutEditor layoutEditor,
225             RulesEngine rulesEngine,
226             Composite parent,
227             int style) {
228         super(parent, style | SWT.DOUBLE_BUFFERED | SWT.V_SCROLL | SWT.H_SCROLL);
229         mLayoutEditor = layoutEditor;
230         mRulesEngine = rulesEngine;
231 
232         mClipboard = new Clipboard(parent.getDisplay());
233 
234         mHScale = new ScaleInfo(getHorizontalBar());
235         mVScale = new ScaleInfo(getVerticalBar());
236 
237         mGCWrapper = new GCWrapper(mHScale, mVScale);
238 
239         Display d = getDisplay();
240         mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
241         mHoverFgColor     = new Color(d, 0xFF, 0x99, 0x00); // orange
242         mOutlineColor     = d.getSystemColor(SWT.COLOR_GREEN);
243 
244         mFont = d.getSystemFont();
245 
246         addPaintListener(new PaintListener() {
247             public void paintControl(PaintEvent e) {
248                 onPaint(e);
249             }
250         });
251 
252         addControlListener(new ControlAdapter() {
253             @Override
254             public void controlResized(ControlEvent e) {
255                 super.controlResized(e);
256                 mHScale.setClientSize(getClientArea().width);
257                 mVScale.setClientSize(getClientArea().height);
258             }
259         });
260 
261         addMouseMoveListener(new MouseMoveListener() {
262             public void mouseMove(MouseEvent e) {
263                 onMouseMove(e);
264             }
265         });
266 
267         addMouseListener(new MouseListener() {
268             public void mouseUp(MouseEvent e) {
269                 onMouseUp(e);
270             }
271 
272             public void mouseDown(MouseEvent e) {
273                 onMouseDown(e);
274             }
275 
276             public void mouseDoubleClick(MouseEvent e) {
277                 onDoubleClick(e);
278             }
279         });
280 
281         // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
282 
283         mDropTarget = new DropTarget(this, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT);
284         mDropTarget.setTransfer(new Transfer[] { SimpleXmlTransfer.getInstance() } );
285         mDropListener = new CanvasDropListener(this);
286         mDropTarget.addDropListener(mDropListener);
287 
288         mSource = new DragSource(this, DND.DROP_COPY | DND.DROP_MOVE);
289         mSource.setTransfer(new Transfer[] {
290                 TextTransfer.getInstance(),
291                 SimpleXmlTransfer.getInstance()
292             } );
293         mSource.addDragListener(new CanvasDragSourceListener());
294 
295         // Get the outline associated with this editor, if any and of the right type.
296         Object outline = layoutEditor.getAdapter(IContentOutlinePage.class);
297         if (outline instanceof OutlinePage2) {
298             mOutlinePage = (OutlinePage2) outline;
299         }
300     }
301 
302 
303 
304     @Override
dispose()305     public void dispose() {
306         super.dispose();
307 
308         if (mOutlinePage != null) {
309             mOutlinePage.setModel(null);
310             mOutlinePage = null;
311         }
312 
313         if (mHoverFgColor != null) {
314             mHoverFgColor.dispose();
315             mHoverFgColor = null;
316         }
317 
318         if (mDropTarget != null) {
319             mDropTarget.dispose();
320             mDropTarget = null;
321         }
322 
323         if (mRulesEngine != null) {
324             mRulesEngine.dispose();
325             mRulesEngine = null;
326         }
327 
328         if (mClipboard != null) {
329             mClipboard.dispose();
330             mClipboard = null;
331         }
332     }
333 
334     /**
335      * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid
336      * {@link ILayoutResult} in which case it is also available in {@link #mLastValidResult}.
337      * When false this means the canvas is displaying an out-dated result image & bounds and some
338      * features should be disabled accordingly such a drag'n'drop.
339      * <p/>
340      * When this is false, {@link #mLastValidResult} can be non-null and points to an older
341      * layout result.
342      */
isResultValid()343     /* package */ boolean isResultValid() {
344         return mIsResultValid;
345     }
346 
347     /** Returns the Groovy Rules Engine, associated with the current project. */
getRulesEngine()348     /* package */ RulesEngine getRulesEngine() {
349         return mRulesEngine;
350     }
351 
352     /** Sets the Groovy Rules Engine, associated with the current project. */
setRulesEngine(RulesEngine rulesEngine)353     /* package */ void setRulesEngine(RulesEngine rulesEngine) {
354         mRulesEngine = rulesEngine;
355     }
356 
357     /**
358      * Returns the factory to use to convert from {@link CanvasViewInfo} or from
359      * {@link UiViewElementNode} to {@link INode} proxies.
360      */
getNodeFactory()361     public NodeFactory getNodeFactory() {
362         return mNodeFactory;
363     }
364 
365     /** Returns the shared SWT keyboard. */
getClipboard()366     public Clipboard getClipboard() {
367         return mClipboard;
368     }
369 
370     /**
371      * Sets the result of the layout rendering. The result object indicates if the layout
372      * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
373      *
374      * Implementation detail: the bridge's computeLayout() method already returns a newly
375      * allocated ILayourResult. That means we can keep this result and hold on to it
376      * when it is valid.
377      *
378      * @param result The new rendering result, either valid or not.
379      */
setResult(ILayoutResult result)380     public void setResult(ILayoutResult result) {
381         // disable any hover
382         mHoverRect = null;
383 
384         mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
385 
386         if (mIsResultValid && result != null) {
387             mLastValidResult = result;
388             mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView());
389             setImage(result.getImage());
390 
391             updateNodeProxies(mLastValidViewInfoRoot);
392             mOutlinePage.setModel(mLastValidViewInfoRoot);
393 
394             // Check if the selection is still the same (based on the object keys)
395             // and eventually recompute their bounds.
396             for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
397                 CanvasSelection s = it.next();
398 
399                 // Check if the selected object still exists
400                 Object key = s.getViewInfo().getUiViewKey();
401                 CanvasViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot);
402 
403                 // Remove the previous selection -- if the selected object still exists
404                 // we need to recompute its bounds in case it moved so we'll insert a new one
405                 // at the same place.
406                 it.remove();
407                 if (vi != null) {
408                     it.add(new CanvasSelection(vi, mRulesEngine, mNodeFactory));
409                 }
410             }
411             fireSelectionChanged();
412 
413             // remove the current alternate selection views
414             mAltSelection = null;
415 
416             mHScale.setSize(mImage.getImageData().width, getClientArea().width);
417             mVScale.setSize(mImage.getImageData().height, getClientArea().height);
418 
419             // Pre-load the android.view.View rule in the Rules Engine. Doing it here means
420             // it will be done after the first rendering is finished. Successive calls are
421             // superfluous but harmless since the rule will be cached.
422             mRulesEngine.preloadAndroidView();
423         }
424 
425         redraw();
426     }
427 
setShowOutline(boolean newState)428     public void setShowOutline(boolean newState) {
429         mShowOutline = newState;
430         redraw();
431     }
432 
getScale()433     public double getScale() {
434         return mHScale.getScale();
435     }
436 
setScale(double scale)437     public void setScale(double scale) {
438         mHScale.setScale(scale);
439         mVScale.setScale(scale);
440         redraw();
441     }
442 
443     /**
444      * Called by the {@link GraphicalEditorPart} when the Copy action is requested.
445      *
446      * @param clipboard The shared clipboard. Must not be disposed.
447      */
onCopy(Clipboard clipboard)448     public void onCopy(Clipboard clipboard) {
449         // TODO implement copy to clipbard. Also will need to provide feedback to enable
450         // copy only when there's a selection.
451     }
452 
453     /**
454      * Called by the {@link GraphicalEditorPart} when the Cut action is requested.
455      *
456      * @param clipboard The shared clipboard. Must not be disposed.
457      */
onCut(Clipboard clipboard)458     public void onCut(Clipboard clipboard) {
459         // TODO implement copy to clipbard. Also will need to provide feedback to enable
460         // cut only when there's a selection.
461     }
462 
463     /**
464      * Called by the {@link GraphicalEditorPart} when the Paste action is requested.
465      *
466      * @param clipboard The shared clipboard. Must not be disposed.
467      */
onPaste(Clipboard clipboard)468     public void onPaste(Clipboard clipboard) {
469 
470     }
471 
472     /**
473      * Called by the {@link GraphicalEditorPart} when the Select All action is requested.
474      */
onSelectAll()475     public void onSelectAll() {
476         // First clear the current selection, if any.
477         mSelections.clear();
478         mAltSelection = null;
479 
480         // Now select everything if there's a valid layout
481         if (mIsResultValid && mLastValidResult != null) {
482             selectAllViewInfos(mLastValidViewInfoRoot);
483             redraw();
484         }
485 
486         fireSelectionChanged();
487     }
488 
489     /**
490      * Delete action
491      */
onDelete()492     public void onDelete() {
493         // TODO not implemented yet, not even hooked in yet!
494     }
495 
496     /**
497      * Transforms a point, expressed in SWT display coordinates
498      * (e.g. from a Drag'n'Drop {@link Event}, not local {@link Control} coordinates)
499      * into the canvas' image coordinates according to the current zoom and scroll.
500      *
501      * @param displayX X in SWT display coordinates
502      * @param displayY Y in SWT display coordinates
503      * @return A new {@link Point} in canvas coordinates
504      */
displayToCanvasPoint(int displayX, int displayY)505     public Point displayToCanvasPoint(int displayX, int displayY) {
506         // convert screen coordinates to local SWT control coordinates
507         org.eclipse.swt.graphics.Point p = this.toControl(displayX, displayY);
508 
509         int x = mHScale.inverseTranslate(p.x);
510         int y = mVScale.inverseTranslate(p.y);
511         return new Point(x, y);
512     }
513 
514 
515     //----
516     // Implementation of ISelectionProvider
517 
518     /**
519      * Returns a {@link TreeSelection} compatible with a TreeViewer
520      * where each {@link TreePath} item is actually a {@link CanvasViewInfo}.
521      */
getSelection()522     public ISelection getSelection() {
523         if (mSelections.size() == 0) {
524             return TreeSelection.EMPTY;
525         }
526 
527         ArrayList<TreePath> paths = new ArrayList<TreePath>();
528 
529         for (CanvasSelection cs : mSelections) {
530             CanvasViewInfo vi = cs.getViewInfo();
531             if (vi != null) {
532                 ArrayList<Object> segments = new ArrayList<Object>();
533                 while (vi != null) {
534                     segments.add(0, vi);
535                     vi = vi.getParent();
536                 }
537                 paths.add(new TreePath(segments.toArray()));
538             }
539         }
540 
541         return new TreeSelection(paths.toArray(new TreePath[paths.size()]));
542     }
543 
544     /**
545      * Sets the selection. It must be an {@link ITreeSelection} where each segment
546      * of the tree path is a {@link CanvasViewInfo}. A null selection is considered
547      * as an empty selection.
548      */
setSelection(ISelection selection)549     public void setSelection(ISelection selection) {
550         if (mInsideUpdateSelection) {
551             return;
552         }
553 
554         try {
555             mInsideUpdateSelection = true;
556 
557             if (selection == null) {
558                 selection = TreeSelection.EMPTY;
559             }
560 
561             if (selection instanceof ITreeSelection) {
562                 ITreeSelection treeSel = (ITreeSelection) selection;
563 
564                 if (treeSel.isEmpty()) {
565                     // Clear existing selection, if any
566                     if (mSelections.size() > 0) {
567                         mSelections.clear();
568                         mAltSelection = null;
569                         redraw();
570                     }
571                     return;
572                 }
573 
574                 boolean changed = false;
575 
576                 // Create a list of all currently selected view infos
577                 Set<CanvasViewInfo> oldSelected = new HashSet<CanvasViewInfo>();
578                 for (CanvasSelection cs : mSelections) {
579                     oldSelected.add(cs.getViewInfo());
580                 }
581 
582                 // Go thru new selection and take care of selecting new items
583                 // or marking those which are the same as in the current selection
584                 for (TreePath path : treeSel.getPaths()) {
585                     Object seg = path.getLastSegment();
586                     if (seg instanceof CanvasViewInfo) {
587                         CanvasViewInfo newVi = (CanvasViewInfo) seg;
588                         if (oldSelected.contains(newVi)) {
589                             // This view info is already selected. Remove it from the
590                             // oldSelected list so that we don't de-select it later.
591                             oldSelected.remove(newVi);
592                         } else {
593                             // This view info is not already selected. Select it now.
594 
595                             // reset alternate selection if any
596                             mAltSelection = null;
597                             // otherwise add it.
598                             mSelections.add(
599                                     new CanvasSelection(newVi, mRulesEngine, mNodeFactory));
600                             changed = true;
601                         }
602                     }
603                 }
604 
605                 // De-select old selected items that are not in the new one
606                 for (CanvasViewInfo vi : oldSelected) {
607                     deselect(vi);
608                     changed = true;
609                 }
610 
611                 if (changed) {
612                     redraw();
613                 }
614             }
615         } finally {
616             mInsideUpdateSelection = false;
617         }
618     }
619 
addSelectionChangedListener(ISelectionChangedListener listener)620     public void addSelectionChangedListener(ISelectionChangedListener listener) {
621         mSelectionListeners.add(listener);
622     }
623 
removeSelectionChangedListener(ISelectionChangedListener listener)624     public void removeSelectionChangedListener(ISelectionChangedListener listener) {
625         mSelectionListeners.remove(listener);
626     }
627 
628     //---
629 
630     private class ScaleInfo implements ICanvasTransform {
631         /** Canvas image size (original, before zoom), in pixels */
632         private int mImgSize;
633 
634         /** Client size, in pixels */
635         private int mClientSize;
636 
637         /** Left-top offset in client pixel coordinates */
638         private int mTranslate;
639 
640         /** Scaling factor, > 0 */
641         private double mScale;
642 
643         /** Scrollbar widget */
644         ScrollBar mScrollbar;
645 
ScaleInfo(ScrollBar scrollbar)646         public ScaleInfo(ScrollBar scrollbar) {
647             mScrollbar = scrollbar;
648             mScale = 1.0;
649             mTranslate = 0;
650 
651             mScrollbar.addSelectionListener(new SelectionAdapter() {
652                 @Override
653                 public void widgetSelected(SelectionEvent e) {
654                     // User requested scrolling. Changes translation and redraw canvas.
655                     mTranslate = mScrollbar.getSelection();
656                     redraw();
657                 }
658             });
659         }
660 
661         /**
662          * Sets the new scaling factor. Recomputes scrollbars.
663          * @param scale Scaling factor, > 0.
664          */
setScale(double scale)665         public void setScale(double scale) {
666             if (mScale != scale) {
667                 mScale = scale;
668                 resizeScrollbar();
669             }
670         }
671 
672         /** Returns current scaling factor. */
getScale()673         public double getScale() {
674             return mScale;
675         }
676 
677         /** Returns Canvas image size (original, before zoom), in pixels. */
getImgSize()678         public int getImgSize() {
679             return mImgSize;
680         }
681 
682         /** Returns the scaled image size in pixels. */
getScalledImgSize()683         public int getScalledImgSize() {
684             return (int) (mImgSize * mScale);
685         }
686 
687         /** Changes the size of the canvas image and the client size. Recomputes scrollbars. */
setSize(int imgSize, int clientSize)688         public void setSize(int imgSize, int clientSize) {
689             mImgSize = imgSize;
690             setClientSize(clientSize);
691         }
692 
693         /** Changes the size of the client size. Recomputes scrollbars. */
setClientSize(int clientSize)694         public void setClientSize(int clientSize) {
695             mClientSize = clientSize;
696             resizeScrollbar();
697         }
698 
resizeScrollbar()699         private void resizeScrollbar() {
700             // scaled image size
701             int sx = (int) (mImgSize * mScale);
702 
703             // actual client area is always reduced by the margins
704             int cx = mClientSize - 2 * IMAGE_MARGIN;
705 
706             if (sx < cx) {
707                 mScrollbar.setEnabled(false);
708             } else {
709                 mScrollbar.setEnabled(true);
710 
711                 // max scroll value is the scaled image size
712                 // thumb value is the actual viewable area out of the scaled img size
713                 mScrollbar.setMaximum(sx);
714                 mScrollbar.setThumb(cx);
715             }
716         }
717 
translate(int canvasX)718         public int translate(int canvasX) {
719             return IMAGE_MARGIN - mTranslate + (int)(mScale * canvasX);
720         }
721 
scale(int canwasW)722         public int scale(int canwasW) {
723             return (int)(mScale * canwasW);
724         }
725 
inverseTranslate(int screenX)726         public int inverseTranslate(int screenX) {
727             return (int) ((screenX - IMAGE_MARGIN + mTranslate) / mScale);
728         }
729     }
730 
731     /**
732      * Creates or updates the node proxy for this canvas view info.
733      * <p/>
734      * Since proxies are reused, this will update the bounds of an existing proxy when the
735      * canvas is refreshed and a view changes position or size.
736      * <p/>
737      * This is a recursive call that updates the whole hierarchy starting at the given
738      * view info.
739      */
updateNodeProxies(CanvasViewInfo vi)740     private void updateNodeProxies(CanvasViewInfo vi) {
741 
742         if (vi == null) {
743             return;
744         }
745 
746         UiViewElementNode key = vi.getUiViewKey();
747 
748         if (key != null) {
749             mNodeFactory.create(vi);
750         }
751 
752         for (CanvasViewInfo child : vi.getChildren()) {
753             updateNodeProxies(child);
754         }
755     }
756 
757     /**
758      * Sets the image of the last *successful* rendering.
759      * Converts the AWT image into an SWT image.
760      */
setImage(BufferedImage awtImage)761     private void setImage(BufferedImage awtImage) {
762         int width = awtImage.getWidth();
763         int height = awtImage.getHeight();
764 
765         Raster raster = awtImage.getData(new java.awt.Rectangle(width, height));
766         int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();
767 
768         ImageData imageData = new ImageData(width, height, 32,
769                 new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
770 
771         imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
772 
773         mImage = new Image(getDisplay(), imageData);
774     }
775 
776     /**
777      * Sets the alpha for the given GC.
778      * <p/>
779      * Alpha may not work on all platforms and may fail with an exception.
780      *
781      * @param gc the GC to change
782      * @param alpha the new alpha, 0 for transparent, 255 for opaque.
783      * @return True if the operation worked, false if it failed with an exception.
784      *
785      * @see GC#setAlpha(int)
786      */
gc_setAlpha(GC gc, int alpha)787     private boolean gc_setAlpha(GC gc, int alpha) {
788         try {
789             gc.setAlpha(alpha);
790             return true;
791         } catch (SWTException e) {
792             return false;
793         }
794     }
795 
796     /**
797      * Sets the non-text antialias flag for the given GC.
798      * <p/>
799      * Antialias may not work on all platforms and may fail with an exception.
800      *
801      * @param gc the GC to change
802      * @param alias One of {@link SWT#DEFAULT}, {@link SWT#ON}, {@link SWT#OFF}.
803      * @return The previous aliasing mode if the operation worked,
804      *         or -2 if it failed with an exception.
805      *
806      * @see GC#setAntialias(int)
807      */
gc_setAntialias(GC gc, int alias)808     private int gc_setAntialias(GC gc, int alias) {
809         try {
810             int old = gc.getAntialias();
811             gc.setAntialias(alias);
812             return old;
813         } catch (SWTException e) {
814             return -2;
815         }
816     }
817 
818     /**
819      * Paints the canvas in response to paint events.
820      */
onPaint(PaintEvent e)821     private void onPaint(PaintEvent e) {
822         GC gc = e.gc;
823         gc.setFont(mFont);
824         mGCWrapper.setGC(gc);
825         try {
826 
827             if (mImage != null) {
828                 if (!mIsResultValid) {
829                     gc_setAlpha(gc, 128);  // half-transparent
830                 }
831 
832                 ScaleInfo hi = mHScale;
833                 ScaleInfo vi = mVScale;
834 
835                 // we only anti-alias when reducing the image size.
836                 int oldAlias = -2;
837                 if (hi.getScale() < 1.0) {
838                     oldAlias = gc_setAntialias(gc, SWT.ON);
839                 }
840 
841                 gc.drawImage(mImage,
842                         0,                          // srcX
843                         0,                          // srcY
844                         hi.getImgSize(),            // srcWidth
845                         vi.getImgSize(),            // srcHeight
846                         hi.translate(0),            // destX
847                         vi.translate(0),            // destY
848                         hi.getScalledImgSize(),     // destWidth
849                         vi.getScalledImgSize()      // destHeight
850                         );
851 
852                 if (oldAlias != -2) {
853                     gc_setAntialias(gc, oldAlias);
854                 }
855 
856                 if (!mIsResultValid) {
857                     gc_setAlpha(gc, 255);  // opaque
858                 }
859             }
860 
861             if (mShowOutline && mLastValidViewInfoRoot != null) {
862                 gc.setForeground(mOutlineColor);
863                 gc.setLineStyle(SWT.LINE_DOT);
864                 drawOutline(gc, mLastValidViewInfoRoot);
865             }
866 
867             if (mHoverRect != null) {
868                 gc.setForeground(mHoverFgColor);
869                 gc.setLineStyle(SWT.LINE_DOT);
870 
871                 int x = mHScale.translate(mHoverRect.x);
872                 int y = mVScale.translate(mHoverRect.y);
873                 int w = mHScale.scale(mHoverRect.width);
874                 int h = mVScale.scale(mHoverRect.height);
875 
876                 gc.drawRectangle(x, y, w, h);
877             }
878 
879             int n = mSelections.size();
880             if (n > 0) {
881                 boolean isMultipleSelection = n > 1;
882 
883                 if (n == 1) {
884                     gc.setForeground(mSelectionFgColor);
885                     mSelections.get(0).paintParentSelection(mRulesEngine, mGCWrapper);
886                 }
887 
888                 for (CanvasSelection s : mSelections) {
889                     gc.setForeground(mSelectionFgColor);
890                     s.paintSelection(mRulesEngine, mGCWrapper, isMultipleSelection);
891                 }
892             }
893 
894             if (mDropListener != null) {
895                 mDropListener.paintFeedback(mGCWrapper);
896             }
897 
898         } finally {
899             mGCWrapper.setGC(null);
900         }
901     }
902 
drawOutline(GC gc, CanvasViewInfo info)903     private void drawOutline(GC gc, CanvasViewInfo info) {
904 
905         Rectangle r = info.getAbsRect();
906 
907         int x = mHScale.translate(r.x);
908         int y = mVScale.translate(r.y);
909         int w = mHScale.scale(r.width);
910         int h = mVScale.scale(r.height);
911 
912         gc.drawRectangle(x, y, w, h);
913 
914         for (CanvasViewInfo vi : info.getChildren()) {
915             drawOutline(gc, vi);
916         }
917     }
918 
919     /**
920      * Hover on top of a known child.
921      */
onMouseMove(MouseEvent e)922     private void onMouseMove(MouseEvent e) {
923         if (mLastValidResult != null) {
924             CanvasViewInfo root = mLastValidViewInfoRoot;
925 
926             int x = mHScale.inverseTranslate(e.x);
927             int y = mVScale.inverseTranslate(e.y);
928 
929             CanvasViewInfo vi = findViewInfoAt(x, y);
930 
931             // We don't hover on the root since it's not a widget per see and it is always there.
932             if (vi == root) {
933                 vi = null;
934             }
935 
936             boolean needsUpdate = vi != mHoverViewInfo;
937             mHoverViewInfo = vi;
938 
939             if (vi == null) {
940                 mHoverRect = null;
941             } else {
942                 Rectangle r = vi.getSelectionRect();
943                 mHoverRect = new Rectangle(r.x, r.y, r.width, r.height);
944             }
945 
946             if (needsUpdate) {
947                 redraw();
948             }
949         }
950     }
951 
onMouseDown(MouseEvent e)952     private void onMouseDown(MouseEvent e) {
953         // pass, not used yet.
954     }
955 
956     /**
957      * Performs selection on mouse up (not mouse down).
958      * <p/>
959      * Shift key is used to toggle in multi-selection.
960      * Alt key is used to cycle selection through objects at the same level than the one
961      * pointed at (i.e. click on an object then alt-click to cycle).
962      */
onMouseUp(MouseEvent e)963     private void onMouseUp(MouseEvent e) {
964         if (mLastValidResult != null) {
965 
966             boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
967             boolean isAlt   = (e.stateMask & SWT.ALT)   != 0;
968 
969             int x = mHScale.inverseTranslate(e.x);
970             int y = mVScale.inverseTranslate(e.y);
971 
972             CanvasViewInfo vi = findViewInfoAt(x, y);
973 
974             if (isShift && !isAlt) {
975                 // Case where shift is pressed: pointed object is toggled.
976 
977                 // reset alternate selection if any
978                 mAltSelection = null;
979 
980                 // If nothing has been found at the cursor, assume it might be a user error
981                 // and avoid clearing the existing selection.
982 
983                 if (vi != null) {
984                     // toggle this selection on-off: remove it if already selected
985                     if (deselect(vi)) {
986                         redraw();
987                         return;
988                     }
989 
990                     // otherwise add it.
991                     mSelections.add(new CanvasSelection(vi, mRulesEngine, mNodeFactory));
992                     fireSelectionChanged();
993                     redraw();
994                 }
995 
996             } else if (isAlt) {
997                 // Case where alt is pressed: select or cycle the object pointed at.
998 
999                 // Note: if shift and alt are pressed, shift is ignored. The alternate selection
1000                 // mechanism does not reset the current multiple selection unless they intersect.
1001 
1002                 // We need to remember the "origin" of the alternate selection, to be
1003                 // able to continue cycling through it later. If there's no alternate selection,
1004                 // create one. If there's one but not for the same origin object, create a new
1005                 // one too.
1006                 if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) {
1007                     mAltSelection = new CanvasAlternateSelection(vi, findAltViewInfoAt(
1008                                                     x, y, mLastValidViewInfoRoot, null));
1009 
1010                     // deselect them all, in case they were partially selected
1011                     deselectAll(mAltSelection.getAltViews());
1012 
1013                     // select the current one
1014                     CanvasViewInfo vi2 = mAltSelection.getCurrent();
1015                     if (vi2 != null) {
1016                         mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine, mNodeFactory));
1017                         fireSelectionChanged();
1018                     }
1019                 } else {
1020                     // We're trying to cycle through the current alternate selection.
1021                     // First remove the current object.
1022                     CanvasViewInfo vi2 = mAltSelection.getCurrent();
1023                     deselect(vi2);
1024 
1025                     // Now select the next one.
1026                     vi2 = mAltSelection.getNext();
1027                     if (vi2 != null) {
1028                         mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine, mNodeFactory));
1029                         fireSelectionChanged();
1030                     }
1031                 }
1032                 redraw();
1033 
1034             } else {
1035                 // Case where no modifier is pressed: either select or reset the selection.
1036 
1037                 selectSingle(vi);
1038             }
1039         }
1040     }
1041 
1042     /**
1043      * Removes all the currently selected item and only select the given item.
1044      * Issues a {@link #redraw()} if the selection changes.
1045      *
1046      * @param vi The new selected item if non-null. Selection becomes empty if null.
1047      */
selectSingle(CanvasViewInfo vi)1048     private void selectSingle(CanvasViewInfo vi) {
1049         // reset alternate selection if any
1050         mAltSelection = null;
1051 
1052         // reset (multi)selection if any
1053         if (mSelections.size() > 0) {
1054             if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {
1055                 // CanvasSelection remains the same, don't touch it.
1056                 return;
1057             }
1058             mSelections.clear();
1059         }
1060 
1061         if (vi != null) {
1062             mSelections.add(new CanvasSelection(vi, mRulesEngine, mNodeFactory));
1063         }
1064         fireSelectionChanged();
1065         redraw();
1066     }
1067 
1068     /**
1069      * Deselects a view info.
1070      * Returns true if the object was actually selected.
1071      * Callers are responsible for calling redraw() and updateOulineSelection() after.
1072      */
deselect(CanvasViewInfo canvasViewInfo)1073     private boolean deselect(CanvasViewInfo canvasViewInfo) {
1074         if (canvasViewInfo == null) {
1075             return false;
1076         }
1077 
1078         for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
1079             CanvasSelection s = it.next();
1080             if (canvasViewInfo == s.getViewInfo()) {
1081                 it.remove();
1082                 return true;
1083             }
1084         }
1085 
1086         return false;
1087     }
1088 
1089     /**
1090      * Deselects multiple view infos.
1091      * Callers are responsible for calling redraw() and updateOulineSelection() after.
1092      */
deselectAll(List<CanvasViewInfo> canvasViewInfos)1093     private void deselectAll(List<CanvasViewInfo> canvasViewInfos) {
1094         for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
1095             CanvasSelection s = it.next();
1096             if (canvasViewInfos.contains(s.getViewInfo())) {
1097                 it.remove();
1098             }
1099         }
1100     }
1101 
onDoubleClick(MouseEvent e)1102     private void onDoubleClick(MouseEvent e) {
1103         // pass, not used yet.
1104     }
1105 
1106     /**
1107      * Tries to find a child with the same view key in the view info sub-tree.
1108      * Returns null if not found.
1109      */
findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo)1110     private CanvasViewInfo findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo) {
1111         if (canvasViewInfo.getUiViewKey() == viewKey) {
1112             return canvasViewInfo;
1113         }
1114 
1115         // try to find a matching child
1116         for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
1117             CanvasViewInfo v = findViewInfoKey(viewKey, child);
1118             if (v != null) {
1119                 return v;
1120             }
1121         }
1122 
1123         return null;
1124     }
1125 
1126 
1127     /**
1128      * Tries to find the inner most child matching the given x,y coordinates in the view
1129      * info sub-tree, starting at the last know view info root.
1130      * This uses the potentially-expanded selection bounds.
1131      *
1132      * Returns null if not found or if there's view info root.
1133      */
findViewInfoAt(int x, int y)1134     /* package */ CanvasViewInfo findViewInfoAt(int x, int y) {
1135         if (mLastValidViewInfoRoot == null) {
1136             return null;
1137         } else {
1138             return findViewInfoAt(x, y, mLastValidViewInfoRoot);
1139         }
1140     }
1141 
1142     /**
1143      * Tries to find the inner most child matching the given x,y coordinates in the view
1144      * info sub-tree. This uses the potentially-expanded selection bounds.
1145      *
1146      * Returns null if not found.
1147      */
findViewInfoAt(int x, int y, CanvasViewInfo canvasViewInfo)1148     private CanvasViewInfo findViewInfoAt(int x, int y, CanvasViewInfo canvasViewInfo) {
1149         Rectangle r = canvasViewInfo.getSelectionRect();
1150         if (r.contains(x, y)) {
1151 
1152             // try to find a matching child first
1153             for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
1154                 CanvasViewInfo v = findViewInfoAt(x, y, child);
1155                 if (v != null) {
1156                     return v;
1157                 }
1158             }
1159 
1160             // if no children matched, this is the view that we're looking for
1161             return canvasViewInfo;
1162         }
1163 
1164         return null;
1165     }
1166 
findAltViewInfoAt( int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList)1167     private ArrayList<CanvasViewInfo> findAltViewInfoAt(
1168             int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList) {
1169         Rectangle r;
1170 
1171         if (outList == null) {
1172             outList = new ArrayList<CanvasViewInfo>();
1173 
1174             // add the parent root only once
1175             r = parent.getSelectionRect();
1176             if (r.contains(x, y)) {
1177                 outList.add(parent);
1178             }
1179         }
1180 
1181         if (parent.getChildren().size() > 0) {
1182             // then add all children that match the position
1183             for (CanvasViewInfo child : parent.getChildren()) {
1184                 r = child.getSelectionRect();
1185                 if (r.contains(x, y)) {
1186                     outList.add(child);
1187                 }
1188             }
1189 
1190             // finally recurse in the children
1191             for (CanvasViewInfo child : parent.getChildren()) {
1192                 r = child.getSelectionRect();
1193                 if (r.contains(x, y)) {
1194                     findAltViewInfoAt(x, y, child, outList);
1195                 }
1196             }
1197         }
1198 
1199         return outList;
1200     }
1201 
1202     /**
1203      * Used by {@link #onSelectAll()} to add all current view infos to the selection list.
1204      *
1205      * @param canvasViewInfo The root to add. This info and all its children will be added to the
1206      *                 selection list.
1207      */
selectAllViewInfos(CanvasViewInfo canvasViewInfo)1208     private void selectAllViewInfos(CanvasViewInfo canvasViewInfo) {
1209         mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine, mNodeFactory));
1210         for (CanvasViewInfo vi : canvasViewInfo.getChildren()) {
1211             selectAllViewInfos(vi);
1212         }
1213     }
1214 
1215     /**
1216      * Notifies listeners that the selection has changed.
1217      */
fireSelectionChanged()1218     private void fireSelectionChanged() {
1219         if (mInsideUpdateSelection) {
1220             return;
1221         }
1222         try {
1223             mInsideUpdateSelection = true;
1224 
1225             final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
1226 
1227             SafeRunnable.run(new SafeRunnable() {
1228                 public void run() {
1229                     for (Object listener : mSelectionListeners.getListeners()) {
1230                         ((ISelectionChangedListener)listener).selectionChanged(event);
1231                     }
1232                 }
1233             });
1234         } finally {
1235             mInsideUpdateSelection = false;
1236         }
1237     }
1238 
1239 
1240     //---------------
1241 
1242     private class CanvasDragSourceListener implements DragSourceListener {
1243 
1244         /**
1245          * The current selection being dragged.
1246          * This may be a subset of the canvas selection.
1247          * Can be empty but never null.
1248          */
1249         final ArrayList<CanvasSelection> mDragSelection = new ArrayList<CanvasSelection>();
1250         private SimpleElement[] mDragElements;
1251 
1252         /**
1253          * The user has begun the actions required to drag the widget.
1254          * <p/>
1255          * Initiate a drag only if there is one or more item selected.
1256          * If there's none, try to auto-select the one under the cursor.
1257          *
1258          * {@inheritDoc}
1259          */
dragStart(DragSourceEvent e)1260         public void dragStart(DragSourceEvent e) {
1261             // We need a selection (simple or multiple) to do any transfer.
1262             // If there's a selection *and* the cursor is over this selection, use all the
1263             // currently selected elements.
1264             // If there is no selection or the cursor is not over a selected element, drag
1265             // the element under the cursor.
1266             // If nothing can be selected, abort the drag operation.
1267 
1268             mDragSelection.clear();
1269 
1270             if (mSelections.size() > 0) {
1271                 // Is the cursor on top of a selected element?
1272                 int x = mHScale.inverseTranslate(e.x);
1273                 int y = mVScale.inverseTranslate(e.y);
1274 
1275                 for (CanvasSelection cs : mSelections) {
1276                     if (cs.getRect().contains(x, y)) {
1277                         mDragSelection.addAll(mSelections);
1278                         break;
1279                     }
1280                 }
1281 
1282                 if (mDragSelection.isEmpty()) {
1283                     // There is no selected element under the cursor.
1284                     // We'll now try to find another element.
1285 
1286                     CanvasViewInfo vi = findViewInfoAt(x, y);
1287                     if (vi != null) {
1288                         mDragSelection.add(new CanvasSelection(vi, mRulesEngine, mNodeFactory));
1289                     }
1290                 }
1291             }
1292 
1293             if (mDragSelection.size() > 0) {
1294                 // Sanitize the list to make sure all elements have a valid XML attached to it.
1295                 // This avoids us from making repeated checks in dragSetData.
1296 
1297                 // In case of multiple selection, we also need to remove all children when their
1298                 // parent is already selected since parents will always be added with all their
1299                 // children.
1300 
1301                 for (Iterator<CanvasSelection> it = mDragSelection.iterator(); it.hasNext(); ) {
1302                     CanvasSelection cs = it.next();
1303                     CanvasViewInfo vi = cs.getViewInfo();
1304                     UiViewElementNode key = vi == null ? null : vi.getUiViewKey();
1305                     Node node = key == null ? null : key.getXmlNode();
1306                     if (node == null) {
1307                         // Missing ViewInfo or view key or XML, discard this.
1308                         it.remove();
1309                         continue;
1310                     }
1311 
1312                     if (vi != null) {
1313                         for (Iterator<CanvasSelection> it2 = mDragSelection.iterator();
1314                              it2.hasNext(); ) {
1315                             CanvasSelection cs2 = it2.next();
1316                             if (cs != cs2) {
1317                                 CanvasViewInfo vi2 = cs2.getViewInfo();
1318                                 if (vi.isParent(vi2)) {
1319                                     // vi2 is a parent for vi. Remove vi.
1320                                     it.remove();
1321                                     break;
1322                                 }
1323                             }
1324                         }
1325                     }
1326                 }
1327             }
1328 
1329             e.doit = mDragSelection.size() > 0;
1330             if (e.doit) {
1331                 mDragElements = getSelectionAsElements();
1332                 GlobalCanvasDragInfo.getInstance().startDrag(
1333                         mDragElements,
1334                         mDragSelection.toArray(new CanvasSelection[mDragSelection.size()]),
1335                         LayoutCanvas.this);
1336             }
1337         }
1338 
1339         /**
1340          * Callback invoked when data is needed for the event, typically right before drop.
1341          * The drop side decides what type of transfer to use and this side must now provide
1342          * the adequate data.
1343          *
1344          * {@inheritDoc}
1345          */
dragSetData(DragSourceEvent e)1346         public void dragSetData(DragSourceEvent e) {
1347             if (TextTransfer.getInstance().isSupportedType(e.dataType)) {
1348                 e.data = getSelectionAsText();
1349                 return;
1350             }
1351 
1352             if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) {
1353                 e.data = mDragElements;
1354                 return;
1355             }
1356 
1357             // otherwise we failed
1358             e.detail = DND.DROP_NONE;
1359             e.doit = false;
1360         }
1361 
getSelectionAsElements()1362         private SimpleElement[] getSelectionAsElements() {
1363             ArrayList<SimpleElement> elements = new ArrayList<SimpleElement>();
1364 
1365             for (CanvasSelection cs : mDragSelection) {
1366                 CanvasViewInfo vi = cs.getViewInfo();
1367 
1368                 SimpleElement e = transformToSimpleElement(vi);
1369                 elements.add(e);
1370             }
1371 
1372             return elements.toArray(new SimpleElement[elements.size()]);
1373         }
1374 
transformToSimpleElement(CanvasViewInfo vi)1375         private SimpleElement transformToSimpleElement(CanvasViewInfo vi) {
1376 
1377             UiViewElementNode uiNode = vi.getUiViewKey();
1378 
1379             String fqcn = SimpleXmlTransfer.getFqcn(uiNode.getDescriptor());
1380             String parentFqcn = null;
1381             Rect bounds = new Rect(vi.getAbsRect());
1382             Rect parentBounds = null;
1383 
1384             UiElementNode uiParent = uiNode.getUiParent();
1385             if (uiParent != null) {
1386                 parentFqcn = SimpleXmlTransfer.getFqcn(uiParent.getDescriptor());
1387             }
1388             if (vi.getParent() != null) {
1389                 parentBounds = new Rect(vi.getParent().getAbsRect());
1390             }
1391 
1392             SimpleElement e = new SimpleElement(fqcn, parentFqcn, bounds, parentBounds);
1393 
1394             for (UiAttributeNode attr : uiNode.getUiAttributes()) {
1395                 String value = attr.getCurrentValue();
1396                 if (value != null && value.length() > 0) {
1397                     AttributeDescriptor attrDesc = attr.getDescriptor();
1398                     SimpleAttribute a = new SimpleAttribute(
1399                             attrDesc.getNamespaceUri(),
1400                             attrDesc.getXmlLocalName(),
1401                             value);
1402                     e.addAttribute(a);
1403                 }
1404             }
1405 
1406             for (CanvasViewInfo childVi : vi.getChildren()) {
1407                 SimpleElement e2 = transformToSimpleElement(childVi);
1408                 if (e2 != null) {
1409                     e.addInnerElement(e2);
1410                 }
1411             }
1412 
1413             return e;
1414         }
1415 
1416         /** Get the XML text from the current drag selection for a text transfer. */
getSelectionAsText()1417         private String getSelectionAsText() {
1418             StringBuilder sb = new StringBuilder();
1419 
1420             for (CanvasSelection cs : mDragSelection) {
1421                 CanvasViewInfo vi = cs.getViewInfo();
1422                 UiViewElementNode key = vi.getUiViewKey();
1423                 Node node = key.getXmlNode();
1424                 String t = getXmlTextFromEditor(mLayoutEditor, node);
1425                 if (t != null) {
1426                     if (sb.length() > 0) {
1427                         sb.append('\n');
1428                     }
1429                     sb.append(t);
1430                 }
1431             }
1432 
1433             return sb.toString();
1434         }
1435 
1436         /** Get the XML text directly from the editor. */
getXmlTextFromEditor(AndroidXmlEditor editor, Node xml_node)1437         private String getXmlTextFromEditor(AndroidXmlEditor editor, Node xml_node) {
1438             String data = null;
1439             IStructuredModel model = editor.getModelForRead();
1440             try {
1441                 IStructuredDocument sse_doc = editor.getStructuredDocument();
1442                 if (xml_node instanceof NodeContainer) {
1443                     // The easy way to get the source of an SSE XML node.
1444                     data = ((NodeContainer) xml_node).getSource();
1445                 } else  if (xml_node instanceof IndexedRegion && sse_doc != null) {
1446                     // Try harder.
1447                     IndexedRegion region = (IndexedRegion) xml_node;
1448                     int start = region.getStartOffset();
1449                     int end = region.getEndOffset();
1450 
1451                     if (end > start) {
1452                         data = sse_doc.get(start, end - start);
1453                     }
1454                 }
1455             } catch (BadLocationException e) {
1456                 // the region offset was invalid. ignore.
1457             } finally {
1458                 model.releaseFromRead();
1459             }
1460             return data;
1461         }
1462 
1463         /**
1464          * Callback invoked when the drop has been finished either way.
1465          * On a successful move, remove the originating elements.
1466          */
dragFinished(DragSourceEvent e)1467         public void dragFinished(DragSourceEvent e) {
1468             if (e.detail == DND.DROP_MOVE) {
1469                 // Remove from source. Since we know the selection, we'll simply
1470                 // create a cut operation on the existing drag selection.
1471                 AdtPlugin.printToConsole("CanvasDND", "dragFinished => MOVE");
1472 
1473                 // Create an undo wrapper, which takes a runnable
1474                 mLayoutEditor.wrapUndoRecording(
1475                         "Remove drag'n'drop source elements",
1476                         new Runnable() {
1477                             public void run() {
1478                                 // Create an edit-XML wrapper, which takes a runnable
1479                                 mLayoutEditor.editXmlModel(new Runnable() {
1480                                     public void run() {
1481                                         cutDragSelection();
1482                                     }
1483                                 });
1484                             }
1485                         });
1486             }
1487 
1488             // Clear the selection
1489             mDragSelection.clear();
1490             mDragElements = null;
1491             GlobalCanvasDragInfo.getInstance().stopDrag();
1492         }
1493 
cutDragSelection()1494         private void cutDragSelection() {
1495             List<UiElementNode> selected = new ArrayList<UiElementNode>();
1496 
1497             for (CanvasSelection cs : mDragSelection) {
1498                 selected.add(cs.getViewInfo().getUiViewKey());
1499             }
1500 
1501             CopyCutAction action = new CopyCutAction(
1502                     mLayoutEditor,
1503                     getClipboard(),
1504                     null, /* xml commit callback */
1505                     selected,
1506                     true /* cut */);
1507 
1508             action.run();
1509         }
1510 
1511     }
1512 }
1513