/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.hierarchyviewerlib.ui; import com.android.hierarchyviewerlib.HierarchyViewerDirector; import com.android.hierarchyviewerlib.models.TreeViewModel; import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener; import com.android.hierarchyviewerlib.ui.util.DrawableViewNode; import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import java.util.ArrayList; public class LayoutViewer extends Canvas implements ITreeChangeListener { private TreeViewModel mModel; private DrawableViewNode mTree; private DrawableViewNode mSelectedNode; private Transform mTransform; private Transform mInverse; private double mScale; private boolean mShowExtras = false; private boolean mOnBlack = true; public LayoutViewer(Composite parent) { super(parent, SWT.NONE); mModel = TreeViewModel.getModel(); mModel.addTreeChangeListener(this); addDisposeListener(mDisposeListener); addPaintListener(mPaintListener); addListener(SWT.Resize, mResizeListener); addMouseListener(mMouseListener); mTransform = new Transform(Display.getDefault()); mInverse = new Transform(Display.getDefault()); treeChanged(); } public void setShowExtras(boolean show) { mShowExtras = show; doRedraw(); } public void setOnBlack(boolean value) { mOnBlack = value; doRedraw(); } public boolean getOnBlack() { return mOnBlack; } private DisposeListener mDisposeListener = new DisposeListener() { public void widgetDisposed(DisposeEvent e) { mModel.removeTreeChangeListener(LayoutViewer.this); mTransform.dispose(); mInverse.dispose(); if (mSelectedNode != null) { mSelectedNode.viewNode.dereferenceImage(); } } }; private Listener mResizeListener = new Listener() { public void handleEvent(Event e) { synchronized (this) { setTransform(); } } }; private MouseListener mMouseListener = new MouseListener() { public void mouseDoubleClick(MouseEvent e) { if (mSelectedNode != null) { HierarchyViewerDirector.getDirector() .showCapture(getShell(), mSelectedNode.viewNode); } } public void mouseDown(MouseEvent e) { boolean selectionChanged = false; DrawableViewNode newSelection = null; synchronized (LayoutViewer.this) { if (mTree != null) { float[] pt = { e.x, e.y }; mInverse.transform(pt); newSelection = updateSelection(mTree, pt[0], pt[1], 0, 0, 0, 0, mTree.viewNode.width, mTree.viewNode.height); if (mSelectedNode != newSelection) { selectionChanged = true; } } } if (selectionChanged) { mModel.setSelection(newSelection); } } public void mouseUp(MouseEvent e) { // pass } }; private DrawableViewNode updateSelection(DrawableViewNode node, float x, float y, int left, int top, int clipX, int clipY, int clipWidth, int clipHeight) { if (!node.treeDrawn) { return null; } // Update the clip int x1 = Math.max(left, clipX); int x2 = Math.min(left + node.viewNode.width, clipX + clipWidth); int y1 = Math.max(top, clipY); int y2 = Math.min(top + node.viewNode.height, clipY + clipHeight); clipX = x1; clipY = y1; clipWidth = x2 - x1; clipHeight = y2 - y1; if (x < clipX || x > clipX + clipWidth || y < clipY || y > clipY + clipHeight) { return null; } final int N = node.children.size(); for (int i = N - 1; i >= 0; i--) { DrawableViewNode child = node.children.get(i); DrawableViewNode ret = updateSelection(child, x, y, left + child.viewNode.left - node.viewNode.scrollX, top + child.viewNode.top - node.viewNode.scrollY, clipX, clipY, clipWidth, clipHeight); if (ret != null) { return ret; } } return node; } private PaintListener mPaintListener = new PaintListener() { public void paintControl(PaintEvent e) { synchronized (LayoutViewer.this) { if (mOnBlack) { e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); } else { e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); } e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height); if (mTree != null) { e.gc.setLineWidth((int) Math.ceil(0.3 / mScale)); e.gc.setTransform(mTransform); if (mOnBlack) { e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); } else { e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); } Rectangle parentClipping = e.gc.getClipping(); e.gc.setClipping(0, 0, mTree.viewNode.width + (int) Math.ceil(0.3 / mScale), mTree.viewNode.height + (int) Math.ceil(0.3 / mScale)); paintRecursive(e.gc, mTree, 0, 0, true); if (mSelectedNode != null) { e.gc.setClipping(parentClipping); // w00t, let's be nice and display the whole path in // light red and the selected node in dark red. ArrayList<Point> rightLeftDistances = new ArrayList<Point>(); int left = 0; int top = 0; DrawableViewNode currentNode = mSelectedNode; while (currentNode != mTree) { left += currentNode.viewNode.left; top += currentNode.viewNode.top; currentNode = currentNode.parent; left -= currentNode.viewNode.scrollX; top -= currentNode.viewNode.scrollY; rightLeftDistances.add(new Point(left, top)); } e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED)); currentNode = mSelectedNode.parent; final int N = rightLeftDistances.size(); for (int i = 0; i < N; i++) { e.gc.drawRectangle((int) (left - rightLeftDistances.get(i).x), (int) (top - rightLeftDistances.get(i).y), currentNode.viewNode.width, currentNode.viewNode.height); currentNode = currentNode.parent; } if (mShowExtras && mSelectedNode.viewNode.image != null) { e.gc.drawImage(mSelectedNode.viewNode.image, left, top); if (mOnBlack) { e.gc.setForeground(Display.getDefault().getSystemColor( SWT.COLOR_WHITE)); } else { e.gc.setForeground(Display.getDefault().getSystemColor( SWT.COLOR_BLACK)); } paintRecursive(e.gc, mSelectedNode, left, top, true); } e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); e.gc.setLineWidth((int) Math.ceil(2 / mScale)); e.gc.drawRectangle(left, top, mSelectedNode.viewNode.width, mSelectedNode.viewNode.height); } } } } }; private void paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root) { if (!node.treeDrawn) { return; } // Don't shift the root if (!root) { left += node.viewNode.left; top += node.viewNode.top; } Rectangle parentClipping = gc.getClipping(); int x1 = Math.max(parentClipping.x, left); int x2 = Math.min(parentClipping.x + parentClipping.width, left + node.viewNode.width + (int) Math.ceil(0.3 / mScale)); int y1 = Math.max(parentClipping.y, top); int y2 = Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height + (int) Math.ceil(0.3 / mScale)); // Clipping is weird... You set it to -5 and it comes out 17 or // something. if (x2 <= x1 || y2 <= y1) { return; } gc.setClipping(x1, y1, x2 - x1, y2 - y1); final int N = node.children.size(); for (int i = 0; i < N; i++) { paintRecursive(gc, node.children.get(i), left - node.viewNode.scrollX, top - node.viewNode.scrollY, false); } gc.setClipping(parentClipping); if (!node.viewNode.willNotDraw) { gc.drawRectangle(left, top, node.viewNode.width, node.viewNode.height); } } private void doRedraw() { Display.getDefault().syncExec(new Runnable() { public void run() { redraw(); } }); } private void setTransform() { if (mTree != null) { Rectangle bounds = getBounds(); int leftRightPadding = bounds.width <= 30 ? 0 : 5; int topBottomPadding = bounds.height <= 30 ? 0 : 5; mScale = Math.min(1.0 * (bounds.width - leftRightPadding * 2) / mTree.viewNode.width, 1.0 * (bounds.height - topBottomPadding * 2) / mTree.viewNode.height); int scaledWidth = (int) Math.ceil(mTree.viewNode.width * mScale); int scaledHeight = (int) Math.ceil(mTree.viewNode.height * mScale); mTransform.identity(); mInverse.identity(); mTransform.translate((bounds.width - scaledWidth) / 2.0f, (bounds.height - scaledHeight) / 2.0f); mInverse.translate((bounds.width - scaledWidth) / 2.0f, (bounds.height - scaledHeight) / 2.0f); mTransform.scale((float) mScale, (float) mScale); mInverse.scale((float) mScale, (float) mScale); if (bounds.width != 0 && bounds.height != 0) { mInverse.invert(); } } } public void selectionChanged() { synchronized (this) { if (mSelectedNode != null) { mSelectedNode.viewNode.dereferenceImage(); } mSelectedNode = mModel.getSelection(); if (mSelectedNode != null) { mSelectedNode.viewNode.referenceImage(); } } doRedraw(); } // Note the syncExec and then synchronized... It avoids deadlock public void treeChanged() { Display.getDefault().syncExec(new Runnable() { public void run() { synchronized (this) { if (mSelectedNode != null) { mSelectedNode.viewNode.dereferenceImage(); } mTree = mModel.getTree(); mSelectedNode = mModel.getSelection(); if (mSelectedNode != null) { mSelectedNode.viewNode.referenceImage(); } setTransform(); } } }); doRedraw(); } public void viewportChanged() { // pass } public void zoomChanged() { // pass } }