• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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.hierarchyviewerlib.ui;
18 
19 import com.android.ddmuilib.ImageLoader;
20 import com.android.hierarchyviewerlib.models.TreeViewModel;
21 import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener;
22 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
23 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point;
24 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Rectangle;
25 
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.events.DisposeEvent;
28 import org.eclipse.swt.events.DisposeListener;
29 import org.eclipse.swt.events.MouseEvent;
30 import org.eclipse.swt.events.MouseListener;
31 import org.eclipse.swt.events.MouseMoveListener;
32 import org.eclipse.swt.events.PaintEvent;
33 import org.eclipse.swt.events.PaintListener;
34 import org.eclipse.swt.graphics.GC;
35 import org.eclipse.swt.graphics.Image;
36 import org.eclipse.swt.graphics.Path;
37 import org.eclipse.swt.graphics.Transform;
38 import org.eclipse.swt.widgets.Canvas;
39 import org.eclipse.swt.widgets.Composite;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.swt.widgets.Event;
42 import org.eclipse.swt.widgets.Listener;
43 
44 public class TreeViewOverview extends Canvas implements ITreeChangeListener {
45 
46     private TreeViewModel mModel;
47 
48     private DrawableViewNode mTree;
49 
50     private Rectangle mViewport;
51 
52     private Transform mTransform;
53 
54     private Transform mInverse;
55 
56     private Rectangle mBounds = new Rectangle();
57 
58     private double mScale;
59 
60     private boolean mDragging = false;
61 
62     private DrawableViewNode mSelectedNode;
63 
64     private static Image sNotSelectedImage;
65 
66     private static Image sSelectedImage;
67 
68     private static Image sFilteredImage;
69 
70     private static Image sFilteredSelectedImage;
71 
TreeViewOverview(Composite parent)72     public TreeViewOverview(Composite parent) {
73         super(parent, SWT.NONE);
74 
75         mModel = TreeViewModel.getModel();
76         mModel.addTreeChangeListener(this);
77 
78         loadResources();
79 
80         addPaintListener(mPaintListener);
81         addMouseListener(mMouseListener);
82         addMouseMoveListener(mMouseMoveListener);
83         addListener(SWT.Resize, mResizeListener);
84         addDisposeListener(mDisposeListener);
85 
86         mTransform = new Transform(Display.getDefault());
87         mInverse = new Transform(Display.getDefault());
88 
89         loadAllData();
90     }
91 
loadResources()92     private void loadResources() {
93         ImageLoader loader = ImageLoader.getLoader(this.getClass());
94         sNotSelectedImage = loader.loadImage("not-selected.png", Display.getDefault()); //$NON-NLS-1$
95         sSelectedImage = loader.loadImage("selected-small.png", Display.getDefault()); //$NON-NLS-1$
96         sFilteredImage = loader.loadImage("filtered.png", Display.getDefault()); //$NON-NLS-1$
97         sFilteredSelectedImage =
98                 loader.loadImage("selected-filtered-small.png", Display.getDefault()); //$NON-NLS-1$
99     }
100 
101     private DisposeListener mDisposeListener = new DisposeListener() {
102         @Override
103         public void widgetDisposed(DisposeEvent e) {
104             mModel.removeTreeChangeListener(TreeViewOverview.this);
105             mTransform.dispose();
106             mInverse.dispose();
107         }
108     };
109 
110     private MouseListener mMouseListener = new MouseListener() {
111 
112         @Override
113         public void mouseDoubleClick(MouseEvent e) {
114             // pass
115         }
116 
117         @Override
118         public void mouseDown(MouseEvent e) {
119             boolean redraw = false;
120             synchronized (TreeViewOverview.this) {
121                 if (mTree != null && mViewport != null) {
122                     mDragging = true;
123                     redraw = true;
124                     handleMouseEvent(transformPoint(e.x, e.y));
125                 }
126             }
127             if (redraw) {
128                 mModel.removeTreeChangeListener(TreeViewOverview.this);
129                 mModel.setViewport(mViewport);
130                 mModel.addTreeChangeListener(TreeViewOverview.this);
131                 doRedraw();
132             }
133         }
134 
135         @Override
136         public void mouseUp(MouseEvent e) {
137             boolean redraw = false;
138             synchronized (TreeViewOverview.this) {
139                 if (mTree != null && mViewport != null) {
140                     mDragging = false;
141                     redraw = true;
142                     handleMouseEvent(transformPoint(e.x, e.y));
143 
144                     // Update bounds and transform only on mouse up. That way,
145                     // you don't get confusing behaviour during mouse drag and
146                     // it snaps neatly at the end
147                     setBounds();
148                     setTransform();
149                 }
150             }
151             if (redraw) {
152                 mModel.removeTreeChangeListener(TreeViewOverview.this);
153                 mModel.setViewport(mViewport);
154                 mModel.addTreeChangeListener(TreeViewOverview.this);
155                 doRedraw();
156             }
157         }
158 
159     };
160 
161     private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
162         @Override
163         public void mouseMove(MouseEvent e) {
164             boolean moved = false;
165             synchronized (TreeViewOverview.this) {
166                 if (mDragging) {
167                     moved = true;
168                     handleMouseEvent(transformPoint(e.x, e.y));
169                 }
170             }
171             if (moved) {
172                 mModel.removeTreeChangeListener(TreeViewOverview.this);
173                 mModel.setViewport(mViewport);
174                 mModel.addTreeChangeListener(TreeViewOverview.this);
175                 doRedraw();
176             }
177         }
178     };
179 
handleMouseEvent(Point pt)180     private void handleMouseEvent(Point pt) {
181         mViewport.x = pt.x - mViewport.width / 2;
182         mViewport.y = pt.y - mViewport.height / 2;
183         if (mViewport.x < mBounds.x) {
184             mViewport.x = mBounds.x;
185         }
186         if (mViewport.y < mBounds.y) {
187             mViewport.y = mBounds.y;
188         }
189         if (mViewport.x + mViewport.width > mBounds.x + mBounds.width) {
190             mViewport.x = mBounds.x + mBounds.width - mViewport.width;
191         }
192         if (mViewport.y + mViewport.height > mBounds.y + mBounds.height) {
193             mViewport.y = mBounds.y + mBounds.height - mViewport.height;
194         }
195     }
196 
transformPoint(double x, double y)197     private Point transformPoint(double x, double y) {
198         float[] pt = {
199                 (float) x, (float) y
200         };
201         mInverse.transform(pt);
202         return new Point(pt[0], pt[1]);
203     }
204 
205     private Listener mResizeListener = new Listener() {
206         @Override
207         public void handleEvent(Event arg0) {
208             synchronized (TreeViewOverview.this) {
209                 setTransform();
210             }
211             doRedraw();
212         }
213     };
214 
215     private PaintListener mPaintListener = new PaintListener() {
216         @Override
217         public void paintControl(PaintEvent e) {
218             synchronized (TreeViewOverview.this) {
219                 if (mTree != null) {
220                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
221                     e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
222                     e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
223                     e.gc.setTransform(mTransform);
224                     e.gc.setLineWidth((int) Math.ceil(0.7 / mScale));
225                     Path connectionPath = new Path(Display.getDefault());
226                     paintRecursive(e.gc, mTree, connectionPath);
227                     e.gc.drawPath(connectionPath);
228                     connectionPath.dispose();
229 
230                     if (mViewport != null) {
231                         e.gc.setAlpha(50);
232                         e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
233                         e.gc.fillRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
234                                 .ceil(mViewport.width), (int) Math.ceil(mViewport.height));
235 
236                         e.gc.setAlpha(255);
237                         e.gc
238                                 .setForeground(Display.getDefault().getSystemColor(
239                                         SWT.COLOR_DARK_GRAY));
240                         e.gc.setLineWidth((int) Math.ceil(2 / mScale));
241                         e.gc.drawRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
242                                 .ceil(mViewport.width), (int) Math.ceil(mViewport.height));
243                     }
244                 }
245             }
246         }
247     };
248 
paintRecursive(GC gc, DrawableViewNode node, Path connectionPath)249     private void paintRecursive(GC gc, DrawableViewNode node, Path connectionPath) {
250         if (mSelectedNode == node && node.viewNode.filtered) {
251             gc.drawImage(sFilteredSelectedImage, node.left, (int) Math.round(node.top));
252         } else if (mSelectedNode == node) {
253             gc.drawImage(sSelectedImage, node.left, (int) Math.round(node.top));
254         } else if (node.viewNode.filtered) {
255             gc.drawImage(sFilteredImage, node.left, (int) Math.round(node.top));
256         } else {
257             gc.drawImage(sNotSelectedImage, node.left, (int) Math.round(node.top));
258         }
259         int N = node.children.size();
260         if (N == 0) {
261             return;
262         }
263         float childSpacing =
264                 (1.0f * (DrawableViewNode.NODE_HEIGHT - 2 * TreeView.LINE_PADDING)) / N;
265         for (int i = 0; i < N; i++) {
266             DrawableViewNode child = node.children.get(i);
267             paintRecursive(gc, child, connectionPath);
268             float x1 = node.left + DrawableViewNode.NODE_WIDTH;
269             float y1 =
270                     (float) node.top + TreeView.LINE_PADDING + childSpacing * i + childSpacing / 2;
271             float x2 = child.left;
272             float y2 = (float) child.top + DrawableViewNode.NODE_HEIGHT / 2.0f;
273             float cx1 = x1 + TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
274             float cy1 = y1;
275             float cx2 = x2 - TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
276             float cy2 = y2;
277             connectionPath.moveTo(x1, y1);
278             connectionPath.cubicTo(cx1, cy1, cx2, cy2, x2, y2);
279         }
280     }
281 
doRedraw()282     private void doRedraw() {
283         Display.getDefault().syncExec(new Runnable() {
284             @Override
285             public void run() {
286                 redraw();
287             }
288         });
289     }
290 
loadAllData()291     public void loadAllData() {
292         Display.getDefault().syncExec(new Runnable() {
293             @Override
294             public void run() {
295                 synchronized (this) {
296                     mTree = mModel.getTree();
297                     mSelectedNode = mModel.getSelection();
298                     mViewport = mModel.getViewport();
299                     setBounds();
300                     setTransform();
301                 }
302             }
303         });
304     }
305 
306     // Note the syncExec and then synchronized... It avoids deadlock
307     @Override
treeChanged()308     public void treeChanged() {
309         Display.getDefault().syncExec(new Runnable() {
310             @Override
311             public void run() {
312                 synchronized (this) {
313                     mTree = mModel.getTree();
314                     mSelectedNode = mModel.getSelection();
315                     mViewport = mModel.getViewport();
316                     setBounds();
317                     setTransform();
318                 }
319             }
320         });
321         doRedraw();
322     }
323 
setBounds()324     private void setBounds() {
325         if (mViewport != null && mTree != null) {
326             mBounds.x = Math.min(mViewport.x, mTree.bounds.x);
327             mBounds.y = Math.min(mViewport.y, mTree.bounds.y);
328             mBounds.width =
329                     Math.max(mViewport.x + mViewport.width, mTree.bounds.x + mTree.bounds.width)
330                             - mBounds.x;
331             mBounds.height =
332                     Math.max(mViewport.y + mViewport.height, mTree.bounds.y + mTree.bounds.height)
333                             - mBounds.y;
334         } else if (mTree != null) {
335             mBounds.x = mTree.bounds.x;
336             mBounds.y = mTree.bounds.y;
337             mBounds.width = mTree.bounds.x + mTree.bounds.width - mBounds.x;
338             mBounds.height = mTree.bounds.y + mTree.bounds.height - mBounds.y;
339         }
340     }
341 
setTransform()342     private void setTransform() {
343         if (mTree != null) {
344 
345             mTransform.identity();
346             mInverse.identity();
347             final Point size = new Point();
348             size.x = getBounds().width;
349             size.y = getBounds().height;
350             if (mBounds.width == 0 || mBounds.height == 0 || size.x == 0 || size.y == 0) {
351                 mScale = 1;
352             } else {
353                 mScale = Math.min(size.x / mBounds.width, size.y / mBounds.height);
354             }
355             mTransform.scale((float) mScale, (float) mScale);
356             mInverse.scale((float) mScale, (float) mScale);
357             mTransform.translate((float) -mBounds.x, (float) -mBounds.y);
358             mInverse.translate((float) -mBounds.x, (float) -mBounds.y);
359             if (size.x / mBounds.width < size.y / mBounds.height) {
360                 mTransform.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
361                 mInverse.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
362             } else {
363                 mTransform.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
364                 mInverse.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
365             }
366             mInverse.invert();
367         }
368     }
369 
370     @Override
viewportChanged()371     public void viewportChanged() {
372         Display.getDefault().syncExec(new Runnable() {
373             @Override
374             public void run() {
375                 synchronized (this) {
376                     mViewport = mModel.getViewport();
377                     setBounds();
378                     setTransform();
379                 }
380             }
381         });
382         doRedraw();
383     }
384 
385     @Override
zoomChanged()386     public void zoomChanged() {
387         viewportChanged();
388     }
389 
390     @Override
selectionChanged()391     public void selectionChanged() {
392         synchronized (this) {
393             mSelectedNode = mModel.getSelection();
394         }
395         doRedraw();
396     }
397 }
398