• 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.hierarchyviewerlib.HierarchyViewerDirector;
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 
25 import org.eclipse.swt.SWT;
26 import org.eclipse.swt.events.DisposeEvent;
27 import org.eclipse.swt.events.DisposeListener;
28 import org.eclipse.swt.events.MouseEvent;
29 import org.eclipse.swt.events.MouseListener;
30 import org.eclipse.swt.events.PaintEvent;
31 import org.eclipse.swt.events.PaintListener;
32 import org.eclipse.swt.graphics.GC;
33 import org.eclipse.swt.graphics.Rectangle;
34 import org.eclipse.swt.graphics.Transform;
35 import org.eclipse.swt.widgets.Canvas;
36 import org.eclipse.swt.widgets.Composite;
37 import org.eclipse.swt.widgets.Display;
38 import org.eclipse.swt.widgets.Event;
39 import org.eclipse.swt.widgets.Listener;
40 
41 import java.util.ArrayList;
42 
43 public class LayoutViewer extends Canvas implements ITreeChangeListener {
44 
45     private TreeViewModel mModel;
46 
47     private DrawableViewNode mTree;
48 
49     private DrawableViewNode mSelectedNode;
50 
51     private Transform mTransform;
52 
53     private Transform mInverse;
54 
55     private double mScale;
56 
57     private boolean mShowExtras = false;
58 
59     private boolean mOnBlack = true;
60 
LayoutViewer(Composite parent)61     public LayoutViewer(Composite parent) {
62         super(parent, SWT.NONE);
63         mModel = TreeViewModel.getModel();
64         mModel.addTreeChangeListener(this);
65 
66         addDisposeListener(mDisposeListener);
67         addPaintListener(mPaintListener);
68         addListener(SWT.Resize, mResizeListener);
69         addMouseListener(mMouseListener);
70 
71         mTransform = new Transform(Display.getDefault());
72         mInverse = new Transform(Display.getDefault());
73 
74         treeChanged();
75     }
76 
setShowExtras(boolean show)77     public void setShowExtras(boolean show) {
78         mShowExtras = show;
79         doRedraw();
80     }
81 
setOnBlack(boolean value)82     public void setOnBlack(boolean value) {
83         mOnBlack = value;
84         doRedraw();
85     }
86 
getOnBlack()87     public boolean getOnBlack() {
88         return mOnBlack;
89     }
90 
91     private DisposeListener mDisposeListener = new DisposeListener() {
92         @Override
93         public void widgetDisposed(DisposeEvent e) {
94             mModel.removeTreeChangeListener(LayoutViewer.this);
95             mTransform.dispose();
96             mInverse.dispose();
97             if (mSelectedNode != null) {
98                 mSelectedNode.viewNode.dereferenceImage();
99             }
100         }
101     };
102 
103     private Listener mResizeListener = new Listener() {
104         @Override
105         public void handleEvent(Event e) {
106             synchronized (this) {
107                 setTransform();
108             }
109         }
110     };
111 
112     private MouseListener mMouseListener = new MouseListener() {
113 
114         @Override
115         public void mouseDoubleClick(MouseEvent e) {
116             if (mSelectedNode != null) {
117                 HierarchyViewerDirector.getDirector()
118                         .showCapture(getShell(), mSelectedNode.viewNode);
119             }
120         }
121 
122         @Override
123         public void mouseDown(MouseEvent e) {
124             boolean selectionChanged = false;
125             DrawableViewNode newSelection = null;
126             synchronized (LayoutViewer.this) {
127                 if (mTree != null) {
128                     float[] pt = {
129                             e.x, e.y
130                     };
131                     mInverse.transform(pt);
132                     newSelection =
133                             updateSelection(mTree, pt[0], pt[1], 0, 0, 0, 0, mTree.viewNode.width,
134                                     mTree.viewNode.height);
135                     if (mSelectedNode != newSelection) {
136                         selectionChanged = true;
137                     }
138                 }
139             }
140             if (selectionChanged) {
141                 mModel.setSelection(newSelection);
142             }
143         }
144 
145         @Override
146         public void mouseUp(MouseEvent e) {
147             // pass
148         }
149     };
150 
updateSelection(DrawableViewNode node, float x, float y, int left, int top, int clipX, int clipY, int clipWidth, int clipHeight)151     private DrawableViewNode updateSelection(DrawableViewNode node, float x, float y, int left,
152             int top, int clipX, int clipY, int clipWidth, int clipHeight) {
153         if (!node.treeDrawn) {
154             return null;
155         }
156         // Update the clip
157         int x1 = Math.max(left, clipX);
158         int x2 = Math.min(left + node.viewNode.width, clipX + clipWidth);
159         int y1 = Math.max(top, clipY);
160         int y2 = Math.min(top + node.viewNode.height, clipY + clipHeight);
161         clipX = x1;
162         clipY = y1;
163         clipWidth = x2 - x1;
164         clipHeight = y2 - y1;
165         if (x < clipX || x > clipX + clipWidth || y < clipY || y > clipY + clipHeight) {
166             return null;
167         }
168         final int N = node.children.size();
169         for (int i = N - 1; i >= 0; i--) {
170             DrawableViewNode child = node.children.get(i);
171             DrawableViewNode ret =
172                     updateSelection(child, x, y,
173                             left + child.viewNode.left - node.viewNode.scrollX, top
174                                     + child.viewNode.top - node.viewNode.scrollY, clipX, clipY,
175                             clipWidth, clipHeight);
176             if (ret != null) {
177                 return ret;
178             }
179         }
180         return node;
181     }
182 
183     private PaintListener mPaintListener = new PaintListener() {
184         @Override
185         public void paintControl(PaintEvent e) {
186             synchronized (LayoutViewer.this) {
187                 if (mOnBlack) {
188                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
189                 } else {
190                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
191                 }
192                 e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
193                 if (mTree != null) {
194                     e.gc.setLineWidth((int) Math.ceil(0.3 / mScale));
195                     e.gc.setTransform(mTransform);
196                     if (mOnBlack) {
197                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
198                     } else {
199                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
200                     }
201                     Rectangle parentClipping = e.gc.getClipping();
202                     e.gc.setClipping(0, 0, mTree.viewNode.width + (int) Math.ceil(0.3 / mScale),
203                             mTree.viewNode.height + (int) Math.ceil(0.3 / mScale));
204                     paintRecursive(e.gc, mTree, 0, 0, true);
205 
206                     if (mSelectedNode != null) {
207                         e.gc.setClipping(parentClipping);
208 
209                         // w00t, let's be nice and display the whole path in
210                         // light red and the selected node in dark red.
211                         ArrayList<Point> rightLeftDistances = new ArrayList<Point>();
212                         int left = 0;
213                         int top = 0;
214                         DrawableViewNode currentNode = mSelectedNode;
215                         while (currentNode != mTree) {
216                             left += currentNode.viewNode.left;
217                             top += currentNode.viewNode.top;
218                             currentNode = currentNode.parent;
219                             left -= currentNode.viewNode.scrollX;
220                             top -= currentNode.viewNode.scrollY;
221                             rightLeftDistances.add(new Point(left, top));
222                         }
223                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED));
224                         currentNode = mSelectedNode.parent;
225                         final int N = rightLeftDistances.size();
226                         for (int i = 0; i < N; i++) {
227                             e.gc.drawRectangle((int) (left - rightLeftDistances.get(i).x),
228                                     (int) (top - rightLeftDistances.get(i).y),
229                                     currentNode.viewNode.width, currentNode.viewNode.height);
230                             currentNode = currentNode.parent;
231                         }
232 
233                         if (mShowExtras && mSelectedNode.viewNode.image != null) {
234                             e.gc.drawImage(mSelectedNode.viewNode.image, left, top);
235                             if (mOnBlack) {
236                                 e.gc.setForeground(Display.getDefault().getSystemColor(
237                                         SWT.COLOR_WHITE));
238                             } else {
239                                 e.gc.setForeground(Display.getDefault().getSystemColor(
240                                         SWT.COLOR_BLACK));
241                             }
242                             paintRecursive(e.gc, mSelectedNode, left, top, true);
243 
244                         }
245 
246                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
247                         e.gc.setLineWidth((int) Math.ceil(2 / mScale));
248                         e.gc.drawRectangle(left, top, mSelectedNode.viewNode.width,
249                                 mSelectedNode.viewNode.height);
250                     }
251                 }
252             }
253         }
254     };
255 
paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root)256     private void paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root) {
257         if (!node.treeDrawn) {
258             return;
259         }
260         // Don't shift the root
261         if (!root) {
262             left += node.viewNode.left;
263             top += node.viewNode.top;
264         }
265         Rectangle parentClipping = gc.getClipping();
266         int x1 = Math.max(parentClipping.x, left);
267         int x2 =
268                 Math.min(parentClipping.x + parentClipping.width, left + node.viewNode.width
269                         + (int) Math.ceil(0.3 / mScale));
270         int y1 = Math.max(parentClipping.y, top);
271         int y2 =
272                 Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height
273                         + (int) Math.ceil(0.3 / mScale));
274 
275         // Clipping is weird... You set it to -5 and it comes out 17 or
276         // something.
277         if (x2 <= x1 || y2 <= y1) {
278             return;
279         }
280         gc.setClipping(x1, y1, x2 - x1, y2 - y1);
281         final int N = node.children.size();
282         for (int i = 0; i < N; i++) {
283             paintRecursive(gc, node.children.get(i), left - node.viewNode.scrollX, top
284                     - node.viewNode.scrollY, false);
285         }
286         gc.setClipping(parentClipping);
287         if (!node.viewNode.willNotDraw) {
288             gc.drawRectangle(left, top, node.viewNode.width, node.viewNode.height);
289         }
290 
291     }
292 
doRedraw()293     private void doRedraw() {
294         Display.getDefault().syncExec(new Runnable() {
295             @Override
296             public void run() {
297                 redraw();
298             }
299         });
300     }
301 
setTransform()302     private void setTransform() {
303         if (mTree != null) {
304             Rectangle bounds = getBounds();
305             int leftRightPadding = bounds.width <= 30 ? 0 : 5;
306             int topBottomPadding = bounds.height <= 30 ? 0 : 5;
307             mScale =
308                     Math.min(1.0 * (bounds.width - leftRightPadding * 2) / mTree.viewNode.width, 1.0
309                             * (bounds.height - topBottomPadding * 2) / mTree.viewNode.height);
310             int scaledWidth = (int) Math.ceil(mTree.viewNode.width * mScale);
311             int scaledHeight = (int) Math.ceil(mTree.viewNode.height * mScale);
312 
313             mTransform.identity();
314             mInverse.identity();
315             mTransform.translate((bounds.width - scaledWidth) / 2.0f,
316                     (bounds.height - scaledHeight) / 2.0f);
317             mInverse.translate((bounds.width - scaledWidth) / 2.0f,
318                     (bounds.height - scaledHeight) / 2.0f);
319             mTransform.scale((float) mScale, (float) mScale);
320             mInverse.scale((float) mScale, (float) mScale);
321             if (bounds.width != 0 && bounds.height != 0) {
322                 mInverse.invert();
323             }
324         }
325     }
326 
327     @Override
selectionChanged()328     public void selectionChanged() {
329         synchronized (this) {
330             if (mSelectedNode != null) {
331                 mSelectedNode.viewNode.dereferenceImage();
332             }
333             mSelectedNode = mModel.getSelection();
334             if (mSelectedNode != null) {
335                 mSelectedNode.viewNode.referenceImage();
336             }
337         }
338         doRedraw();
339     }
340 
341     // Note the syncExec and then synchronized... It avoids deadlock
342     @Override
treeChanged()343     public void treeChanged() {
344         Display.getDefault().syncExec(new Runnable() {
345             @Override
346             public void run() {
347                 synchronized (this) {
348                     if (mSelectedNode != null) {
349                         mSelectedNode.viewNode.dereferenceImage();
350                     }
351                     mTree = mModel.getTree();
352                     mSelectedNode = mModel.getSelection();
353                     if (mSelectedNode != null) {
354                         mSelectedNode.viewNode.referenceImage();
355                     }
356                     setTransform();
357                 }
358             }
359         });
360         doRedraw();
361     }
362 
363     @Override
viewportChanged()364     public void viewportChanged() {
365         // pass
366     }
367 
368     @Override
zoomChanged()369     public void zoomChanged() {
370         // pass
371     }
372 }
373