diff options
author | Raphael <raphael@google.com> | 2010-01-14 16:13:26 -0800 |
---|---|---|
committer | Raphael <raphael@google.com> | 2010-01-14 16:33:37 -0800 |
commit | 1a9d30498bb489a8a7704a86eeb6effd92f35f3b (patch) | |
tree | c1b16eb02716d3b5348f38df3ea739756c700270 | |
parent | 784f932f81764c275a961bf8faf286b32c209437 (diff) | |
download | sdk-1a9d30498bb489a8a7704a86eeb6effd92f35f3b.zip sdk-1a9d30498bb489a8a7704a86eeb6effd92f35f3b.tar.gz sdk-1a9d30498bb489a8a7704a86eeb6effd92f35f3b.tar.bz2 |
ADT GRE: Move gscripts package.
Moving the public API from com.android.ide.eclispse.adt.gscripts
to ...adt.editors.layout.gscripts.
Change-Id: Idf5b979d47dbbbe2514cce8cc3c688eb273bcce6
15 files changed, 2357 insertions, 2358 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy index cbaa73c..ceab48f 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy +++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy @@ -16,11 +16,11 @@ package com.android.adt.gscripts; -import com.android.ide.eclipse.adt.gscripts.IViewRule; -import com.android.ide.eclipse.adt.gscripts.INodeProxy; -import com.android.ide.eclipse.adt.gscripts.DropZone; -import com.android.ide.eclipse.adt.gscripts.Rect; -import com.android.ide.eclipse.adt.gscripts.Point; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy; +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; import java.util.Map; import java.util.ArrayList; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy index a94005b..2884af2 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy +++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.AbsoluteLayout.groovy @@ -16,11 +16,11 @@ package com.android.adt.gscripts; -import com.android.ide.eclipse.adt.gscripts.IViewRule; -import com.android.ide.eclipse.adt.gscripts.INodeProxy; -import com.android.ide.eclipse.adt.gscripts.DropZone; -import com.android.ide.eclipse.adt.gscripts.Rect; -import com.android.ide.eclipse.adt.gscripts.Point; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy; +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; import java.util.Map; import java.util.ArrayList; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy index 5df24eb..3ce2bb3 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy +++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.LinearLayout.groovy @@ -16,11 +16,11 @@ package com.android.adt.gscripts; -import com.android.ide.eclipse.adt.gscripts.IViewRule; -import com.android.ide.eclipse.adt.gscripts.INodeProxy; -import com.android.ide.eclipse.adt.gscripts.DropZone; -import com.android.ide.eclipse.adt.gscripts.Rect; -import com.android.ide.eclipse.adt.gscripts.Point; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy; +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; import java.util.Map; import java.util.ArrayList; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy index 8a78824..17713aa 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy +++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.widget.ListView.groovy @@ -16,11 +16,11 @@ package com.android.adt.gscripts; -import com.android.ide.eclipse.adt.gscripts.IViewRule; -import com.android.ide.eclipse.adt.gscripts.INodeProxy; -import com.android.ide.eclipse.adt.gscripts.DropZone; -import com.android.ide.eclipse.adt.gscripts.Rect; -import com.android.ide.eclipse.adt.gscripts.Point; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy; +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; import java.util.Map; import java.util.ArrayList; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/DropZone.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropZone.java index 1651d71..7bb8716 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/DropZone.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/DropZone.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.gscripts; +package com.android.ide.eclipse.adt.editors.layout.gscripts; /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/INodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INodeProxy.java index 42a1b2d..3e6cb47 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/INodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INodeProxy.java @@ -15,7 +15,7 @@ */ -package com.android.ide.eclipse.adt.gscripts; +package com.android.ide.eclipse.adt.editors.layout.gscripts; import groovy.lang.Closure; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/IViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java index df36ee5..0adf3c5 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/IViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.gscripts; +package com.android.ide.eclipse.adt.editors.layout.gscripts; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.Map; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/Point.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Point.java index 50d371d..2e57912 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/Point.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Point.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.gscripts; +package com.android.ide.eclipse.adt.editors.layout.gscripts; /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/Rect.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java index d2ca640..8d0c08c 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/gscripts/Rect.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.gscripts; +package com.android.ide.eclipse.adt.editors.layout.gscripts; /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java index 36a80fb..f070811 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasDropListener.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import org.eclipse.swt.dnd.DND; @@ -163,7 +163,7 @@ import java.util.ArrayList; Point p = eventToCanvasPoint(event); mCanvas.getRulesEngine().dropFinish(viewFqcn, mTargetNode, mCurrentZone, - new com.android.ide.eclipse.adt.gscripts.Point(p.x, p.y)); + new com.android.ide.eclipse.adt.editors.layout.gscripts.Point(p.x, p.y)); clearDropInfo(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 5c16b93..c277c25 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -1,1191 +1,1191 @@ -/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.ide.eclipse.adt.internal.editors.layout.gle2;
-
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
-import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
-import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
-import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
-import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
-import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
-import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.layoutlib.api.ILayoutBridge;
-import com.android.layoutlib.api.ILayoutLog;
-import com.android.layoutlib.api.ILayoutResult;
-import com.android.layoutlib.api.IProjectCallback;
-import com.android.layoutlib.api.IResourceValue;
-import com.android.layoutlib.api.IXmlPullParser;
-import com.android.sdklib.IAndroidTarget;
-
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.draw2d.geometry.Rectangle;
-import org.eclipse.gef.ui.parts.SelectionSynchronizer;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.dialogs.Dialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.custom.StyledText;
-import org.eclipse.swt.dnd.Clipboard;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.ui.IActionBars;
-import org.eclipse.ui.IEditorInput;
-import org.eclipse.ui.IEditorSite;
-import org.eclipse.ui.PartInitException;
-import org.eclipse.ui.actions.ActionFactory;
-import org.eclipse.ui.ide.IDE;
-import org.eclipse.ui.part.EditorPart;
-import org.eclipse.ui.part.FileEditorInput;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-import java.util.Map;
-
-/**
- * Graphical layout editor part, version 2.
- *
- * @since GLE2
- *
- * TODO List:
- * - display error icon
- * - finish palette (see palette's todo list)
- * - finish canvas (see canva's todo list)
- * - completly rethink the property panel
- * - link to the existing outline editor (prolly reuse/adapt for simplier model and will need
- * to adapt the selection synchronizer.)
- */
-public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutEditor {
-
- /*
- * Useful notes:
- * To understand Drag'n'drop:
- * http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
- */
-
- /** Reference to the layout editor */
- private final LayoutEditor mLayoutEditor;
-
- /** reference to the file being edited. Can also be used to access the {@link IProject}. */
- private IFile mEditedFile;
-
- /** The current clipboard. Must be disposed later. */
- private Clipboard mClipboard;
-
- /** The configuration composite at the top of the layout editor. */
- private ConfigurationComposite mConfigComposite;
-
- /** The sash that splits the palette from the canvas. */
- private SashForm mSashPalette;
- private SashForm mSashError;
-
- /** The palette displayed on the left of the sash. */
- private PaletteComposite mPalette;
-
- /** The layout canvas displayed to the right of the sash. */
- private LayoutCanvas mLayoutCanvas;
-
- /** The Groovy Rules Engine associated with this editor. It is project-specific. */
- private RulesEngine mRulesEngine;
-
- private StyledText mErrorLabel;
-
- private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes;
- private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes;
- private ProjectCallback mProjectCallback;
- private ILayoutLog mLogger;
-
- private boolean mNeedsXmlReload = false;
- private boolean mNeedsRecompute = false;
-
- private TargetListener mTargetListener;
-
- private ConfigListener mConfigListener;
-
- private ReloadListener mReloadListener;
-
- protected boolean mUseExplodeMode;
-
-
- public GraphicalEditorPart(LayoutEditor layoutEditor) {
- mLayoutEditor = layoutEditor;
- setPartName("Graphical Layout");
- }
-
- // ------------------------------------
- // Methods overridden from base classes
- //------------------------------------
-
- /**
- * Initializes the editor part with a site and input.
- * {@inheritDoc}
- */
- @Override
- public void init(IEditorSite site, IEditorInput input) throws PartInitException {
- setSite(site);
- useNewEditorInput(input);
-
- if (mTargetListener == null) {
- mTargetListener = new TargetListener();
- AdtPlugin.getDefault().addTargetListener(mTargetListener);
- }
- }
-
- private void useNewEditorInput(IEditorInput input) throws PartInitException {
- // The contract of init() mentions we need to fail if we can't understand the input.
- if (!(input instanceof FileEditorInput)) {
- throw new PartInitException("Input is not of type FileEditorInput: " + //$NON-NLS-1$
- input == null ? "null" : input.toString()); //$NON-NLS-1$
- }
- }
-
- @Override
- public void createPartControl(Composite parent) {
-
- Display d = parent.getDisplay();
- mClipboard = new Clipboard(d);
-
- GridLayout gl = new GridLayout(1, false);
- parent.setLayout(gl);
- gl.marginHeight = gl.marginWidth = 0;
-
- // create the top part for the configuration control
-
- CustomToggle[] toggles = new CustomToggle[] {
- new CustomToggle(
- "Explode",
- null, //image
- "Displays extra margins in the layout."
- ) {
- @Override
- public void onSelected(boolean newState) {
- mUseExplodeMode = newState;
- recomputeLayout();
- }
- },
- new CustomToggle(
- "Outline",
- null, //image
- "Shows the of all views in the layout."
- ) {
- @Override
- public void onSelected(boolean newState) {
- mLayoutCanvas.setShowOutline(newState);
- }
- }
- };
-
- mConfigListener = new ConfigListener();
- mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER);
-
- mSashPalette = new SashForm(parent, SWT.HORIZONTAL);
- mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));
-
- mPalette = new PaletteComposite(mSashPalette);
-
- mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER);
- mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
-
- mLayoutCanvas = new LayoutCanvas(mRulesEngine, mSashError, SWT.NONE);
-
- mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY);
- mErrorLabel.setEditable(false);
- mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
- mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
-
- mSashPalette.setWeights(new int[] { 20, 80 });
- mSashError.setWeights(new int[] { 80, 20 });
- mSashError.setMaximizedControl(mLayoutCanvas);
-
- setupEditActions();
-
- // Initialize the state
- reloadPalette();
- }
-
- private void setupEditActions() {
-
- IActionBars actionBars = getEditorSite().getActionBars();
-
- actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), new Action("Copy") {
- @Override
- public void run() {
- // TODO enable copy only when there's a selection
- mLayoutCanvas.onCopy(mClipboard);
- }
- });
-
- actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), new Action("Cut") {
- @Override
- public void run() {
- // TODO enable cut only when there's a selection
- mLayoutCanvas.onCut(mClipboard);
- }
- });
-
- actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new Action("Paste") {
- @Override
- public void run() {
- mLayoutCanvas.onPaste(mClipboard);
- }
- });
-
- actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
- new Action("Select All") {
- @Override
- public void run() {
- mLayoutCanvas.onSelectAll();
- }
- });
- }
-
- /**
- * Switches the stack to display the error label and hide the canvas.
- * @param errorFormat The new error to display if not null.
- * @param parameters String.format parameters for the error format.
- */
- private void displayError(String errorFormat, Object...parameters) {
- if (errorFormat != null) {
- mErrorLabel.setText(String.format(errorFormat, parameters));
- }
- mSashError.setMaximizedControl(null);
- }
-
- /** Displays the canvas and hides the error label. */
- private void hideError() {
- mSashError.setMaximizedControl(mLayoutCanvas);
- }
-
- @Override
- public void dispose() {
- if (mTargetListener != null) {
- AdtPlugin.getDefault().removeTargetListener(mTargetListener);
- mTargetListener = null;
- }
-
- if (mReloadListener != null) {
- LayoutReloadMonitor.getMonitor().removeListener(mReloadListener);
- mReloadListener = null;
- }
-
- if (mClipboard != null) {
- mClipboard.dispose();
- mClipboard = null;
- }
-
- super.dispose();
- }
-
- /**
- * Listens to changes from the Configuration UI banner and triggers layout rendering when
- * changed. Also provide the Configuration UI with the list of resources/layout to display.
- */
- private class ConfigListener implements IConfigListener {
-
- /**
- * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
- * <p/>If there is no match, notify the user.
- */
- public void onConfigurationChange() {
- mConfiguredFrameworkRes = mConfiguredProjectRes = null;
-
- if (mEditedFile == null || mConfigComposite.getEditedConfig() == null) {
- return;
- }
-
- // Before doing the normal process, test for the following case.
- // - the editor is being opened (or reset for a new input)
- // - the file being opened is not the best match for any possible configuration
- // - another random compatible config was chosen in the config composite.
- // The result is that 'match' will not be the file being edited, but because this is not
- // due to a config change, we should not trigger opening the actual best match (also,
- // because the editor is still opening the MatchingStrategy woudln't answer true
- // and the best match file would open in a different editor).
- // So the solution is that if the editor is being created, we just call recomputeLayout
- // without looking for a better matching layout file.
- if (mLayoutEditor.isCreatingPages()) {
- recomputeLayout();
- } else {
- // get the resources of the file's project.
- ProjectResources resources = ResourceManager.getInstance().getProjectResources(
- mEditedFile.getProject());
-
- // from the resources, look for a matching file
- ResourceFile match = null;
- if (resources != null) {
- match = resources.getMatchingFile(mEditedFile.getName(),
- ResourceFolderType.LAYOUT,
- mConfigComposite.getCurrentConfig());
- }
-
- if (match != null) {
- if (match.getFile().equals(mEditedFile) == false) {
- try {
- // tell the editor that the next replacement file is due to a config
- // change.
- mLayoutEditor.setNewFileOnConfigChange(true);
-
- // ask the IDE to open the replacement file.
- IDE.openEditor(
- getSite().getWorkbenchWindow().getActivePage(),
- match.getFile().getIFile());
-
- // we're done!
- return;
- } catch (PartInitException e) {
- // FIXME: do something!
- }
- }
-
- // at this point, we have not opened a new file.
-
- // Even though the layout doesn't change, the config changed, and referenced
- // resources need to be updated.
- recomputeLayout();
- } else {
- // display the error.
- FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
- displayError(
- "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.",
- currentConfig.toDisplayString(),
- currentConfig.getFolderName(ResourceFolderType.LAYOUT,
- Sdk.getCurrent().getTarget(mEditedFile.getProject())),
- mEditedFile.getName());
- }
- }
- }
-
- public void onThemeChange() {
- recomputeLayout();
- }
-
- public void onClippingChange() {
- recomputeLayout();
- }
-
- public void onCreate() {
- LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(),
- mEditedFile.getName(),
- Sdk.getCurrent().getTarget(mEditedFile.getProject()),
- mConfigComposite.getCurrentConfig());
- if (dialog.open() == Dialog.OK) {
- final FolderConfiguration config = new FolderConfiguration();
- dialog.getConfiguration(config);
-
- createAlternateLayout(config);
- }
- }
-
- public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {
- if (mConfiguredFrameworkRes == null && mConfigComposite != null) {
- ProjectResources frameworkRes = getFrameworkResources();
-
- if (frameworkRes == null) {
- AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
- } else {
- // get the framework resource values based on the current config
- mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
- mConfigComposite.getCurrentConfig());
- }
- }
-
- return mConfiguredFrameworkRes;
- }
-
- public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {
- if (mConfiguredProjectRes == null && mConfigComposite != null) {
- ProjectResources project = getProjectResources();
-
- // make sure they are loaded
- project.loadAll();
-
- // get the project resource values based on the current config
- mConfiguredProjectRes = project.getConfiguredResources(
- mConfigComposite.getCurrentConfig());
- }
-
- return mConfiguredProjectRes;
- }
-
- /**
- * Returns a {@link ProjectResources} for the framework resources.
- * @return the framework resources or null if not found.
- */
- public ProjectResources getFrameworkResources() {
- if (mEditedFile != null) {
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
-
- if (target != null) {
- AndroidTargetData data = currentSdk.getTargetData(target);
-
- if (data != null) {
- return data.getFrameworkResources();
- }
- }
- }
- }
-
- return null;
- }
-
- public ProjectResources getProjectResources() {
- if (mEditedFile != null) {
- ResourceManager manager = ResourceManager.getInstance();
- return manager.getProjectResources(mEditedFile.getProject());
- }
-
- return null;
- }
-
- /**
- * Creates a new layout file from the specified {@link FolderConfiguration}.
- */
- private void createAlternateLayout(final FolderConfiguration config) {
- new Job("Create Alternate Resource") {
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- // get the folder name
- String folderName = config.getFolderName(ResourceFolderType.LAYOUT,
- Sdk.getCurrent().getTarget(mEditedFile.getProject()));
- try {
-
- // look to see if it exists.
- // get the res folder
- IFolder res = (IFolder)mEditedFile.getParent().getParent();
- String path = res.getLocation().toOSString();
-
- File newLayoutFolder = new File(path + File.separator + folderName);
- if (newLayoutFolder.isFile()) {
- // this should not happen since aapt would have complained
- // before, but if one disable the automatic build, this could
- // happen.
- String message = String.format("File 'res/%1$s' is in the way!",
- folderName);
-
- AdtPlugin.displayError("Layout Creation", message);
-
- return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
- } else if (newLayoutFolder.exists() == false) {
- // create it.
- newLayoutFolder.mkdir();
- }
-
- // now create the file
- File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +
- File.separator + mEditedFile.getName());
-
- newLayoutFile.createNewFile();
-
- InputStream input = mEditedFile.getContents();
-
- FileOutputStream fos = new FileOutputStream(newLayoutFile);
-
- byte[] data = new byte[512];
- int count;
- while ((count = input.read(data)) != -1) {
- fos.write(data, 0, count);
- }
-
- input.close();
- fos.close();
-
- // refreshes the res folder to show up the new
- // layout folder (if needed) and the file.
- // We use a progress monitor to catch the end of the refresh
- // to trigger the edit of the new file.
- res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {
- public void done() {
- mConfigComposite.getDisplay().asyncExec(new Runnable() {
- public void run() {
- onConfigurationChange();
- }
- });
- }
-
- public void beginTask(String name, int totalWork) {
- // pass
- }
-
- public void internalWorked(double work) {
- // pass
- }
-
- public boolean isCanceled() {
- // pass
- return false;
- }
-
- public void setCanceled(boolean value) {
- // pass
- }
-
- public void setTaskName(String name) {
- // pass
- }
-
- public void subTask(String name) {
- // pass
- }
-
- public void worked(int work) {
- // pass
- }
- });
- } catch (IOException e2) {
- String message = String.format(
- "Failed to create File 'res/%1$s/%2$s' : %3$s",
- folderName, mEditedFile.getName(), e2.getMessage());
-
- AdtPlugin.displayError("Layout Creation", message);
-
- return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
- message, e2);
- } catch (CoreException e2) {
- String message = String.format(
- "Failed to create File 'res/%1$s/%2$s' : %3$s",
- folderName, mEditedFile.getName(), e2.getMessage());
-
- AdtPlugin.displayError("Layout Creation", message);
-
- return e2.getStatus();
- }
-
- return Status.OK_STATUS;
-
- }
- }.schedule();
- }
- }
-
- /**
- * Listens to target changed in the current project, to trigger a new layout rendering.
- */
- private class TargetListener implements ITargetChangeListener {
-
- public void onProjectTargetChange(IProject changedProject) {
- if (changedProject != null && changedProject.equals(getProject())) {
- updateEditor();
- }
- }
-
- public void onTargetLoaded(IAndroidTarget target) {
- IProject project = getProject();
- if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {
- updateEditor();
- }
- }
-
- public void onSdkLoaded() {
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
- if (target != null) {
- mConfigComposite.onSdkLoaded(target);
- mConfigListener.onConfigurationChange();
- }
- }
- }
-
- private void updateEditor() {
- mLayoutEditor.commitPages(false /* onSave */);
-
- // because the target changed we must reset the configured resources.
- mConfiguredFrameworkRes = mConfiguredProjectRes = null;
-
- // make sure we remove the custom view loader, since its parent class loader is the
- // bridge class loader.
- mProjectCallback = null;
-
- // recreate the ui root node always, this will also call onTargetChange
- // on the config composite
- mLayoutEditor.initUiRootNode(true /*force*/);
- }
-
- private IProject getProject() {
- return getLayoutEditor().getProject();
- }
- }
-
- // ----------------
-
- /**
- * Save operation in the Graphical Editor Part.
- * <p/>
- * In our workflow, the model is owned by the Structured XML Editor.
- * The graphical layout editor just displays it -- thus we don't really
- * save anything here.
- * <p/>
- * This must NOT call the parent editor part. At the contrary, the parent editor
- * part will call this *after* having done the actual save operation.
- * <p/>
- * The only action this editor must do is mark the undo command stack as
- * being no longer dirty.
- */
- @Override
- public void doSave(IProgressMonitor monitor) {
- // TODO implement a command stack
-// getCommandStack().markSaveLocation();
-// firePropertyChange(PROP_DIRTY);
- }
-
- /**
- * Save operation in the Graphical Editor Part.
- * <p/>
- * In our workflow, the model is owned by the Structured XML Editor.
- * The graphical layout editor just displays it -- thus we don't really
- * save anything here.
- */
- @Override
- public void doSaveAs() {
- // pass
- }
-
- /**
- * In our workflow, the model is owned by the Structured XML Editor.
- * The graphical layout editor just displays it -- thus we don't really
- * save anything here.
- */
- @Override
- public boolean isDirty() {
- return false;
- }
-
- /**
- * In our workflow, the model is owned by the Structured XML Editor.
- * The graphical layout editor just displays it -- thus we don't really
- * save anything here.
- */
- @Override
- public boolean isSaveAsAllowed() {
- return false;
- }
-
- @Override
- public void setFocus() {
- // TODO Auto-generated method stub
-
- }
-
- /**
- * Responds to a page change that made the Graphical editor page the activated page.
- */
- public void activated() {
- if (mNeedsRecompute || mNeedsXmlReload) {
- recomputeLayout();
- }
- }
-
- /**
- * Responds to a page change that made the Graphical editor page the deactivated page
- */
- public void deactivated() {
- // nothing to be done here for now.
- }
-
- /**
- * Opens and initialize the editor with a new file.
- * @param file the file being edited.
- */
- public void openFile(IFile file) {
- mEditedFile = file;
- mConfigComposite.openFile(mEditedFile);
-
- if (mReloadListener == null) {
- mReloadListener = new ReloadListener();
- LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);
- }
-
- if (mRulesEngine == null) {
- mRulesEngine = new RulesEngine(mEditedFile.getProject());
- if (mLayoutCanvas != null) {
- mLayoutCanvas.setRulesEngine(mRulesEngine);
- }
- }
- }
-
- /**
- * Resets the editor with a replacement file.
- * @param file the replacement file.
- */
- public void replaceFile(IFile file) {
- mEditedFile = file;
- mConfigComposite.replaceFile(mEditedFile);
- }
-
- /**
- * Resets the editor with a replacement file coming from a config change in the config
- * selector.
- * @param file the replacement file.
- */
- public void changeFileOnNewConfig(IFile file) {
- mEditedFile = file;
- mConfigComposite.changeFileOnNewConfig(mEditedFile);
- }
-
- public void onTargetChange() {
- mConfigComposite.onTargetChange();
- mConfigListener.onConfigurationChange();
- }
-
- public void onSdkChange() {
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
- if (target != null) {
- mConfigComposite.onSdkLoaded(target);
- mConfigListener.onConfigurationChange();
- }
- }
- }
-
- public Clipboard getClipboard() {
- return mClipboard;
- }
-
- public LayoutEditor getLayoutEditor() {
- return mLayoutEditor;
- }
-
- public UiDocumentNode getModel() {
- return mLayoutEditor.getUiRootNode();
- }
-
- public SelectionSynchronizer getSelectionSynchronizer() {
- // TODO Auto-generated method stub
- return null;
- }
-
- /**
- * Callback for XML model changed. Only update/recompute the layout if the editor is visible
- */
- public void onXmlModelChanged() {
- if (mLayoutEditor.isGraphicalEditorActive()) {
- doXmlReload(true /* force */);
- recomputeLayout();
- } else {
- mNeedsXmlReload = true;
- }
- }
-
- /**
- * Actually performs the XML reload
- * @see #onXmlModelChanged()
- */
- private void doXmlReload(boolean force) {
- if (force || mNeedsXmlReload) {
-
- // TODO : update the mLayoutCanvas, preserving the current selection if possible.
-
-// GraphicalViewer viewer = getGraphicalViewer();
-//
-// // try to preserve the selection before changing the content
-// SelectionManager selMan = viewer.getSelectionManager();
-// ISelection selection = selMan.getSelection();
-//
-// try {
-// viewer.setContents(getModel());
-// } finally {
-// selMan.setSelection(selection);
-// }
-
- mNeedsXmlReload = false;
- }
- }
-
- public void recomputeLayout() {
- doXmlReload(false /* force */);
- try {
- // check that the resource exists. If the file is opened but the project is closed
- // or deleted for some reason (changed from outside of eclipse), then this will
- // return false;
- if (mEditedFile.exists() == false) {
- displayError("Resource '%1$s' does not exist.",
- mEditedFile.getFullPath().toString());
- return;
- }
-
- IProject iProject = mEditedFile.getProject();
-
- if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {
- String message = String.format("%1$s is out of sync. Please refresh.",
- mEditedFile.getName());
-
- displayError(message);
-
- // also print it in the error console.
- AdtPlugin.printErrorToConsole(iProject.getName(), message);
- return;
- }
-
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
- if (target == null) {
- displayError("The project target is not set.");
- return;
- }
-
- AndroidTargetData data = currentSdk.getTargetData(target);
- if (data == null) {
- // It can happen that the workspace refreshes while the SDK is loading its
- // data, which could trigger a redraw of the opened layout if some resources
- // changed while Eclipse is closed.
- // In this case data could be null, but this is not an error.
- // We can just silently return, as all the opened editors are automatically
- // refreshed once the SDK finishes loading.
- LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);
- switch (targetLoadStatus) {
- case LOADING:
- displayError("The project target (%1$s) is still loading.\n%2$s will refresh automatically once the process is finished.",
- target.getName(), mEditedFile.getName());
-
- break;
- case FAILED: // known failure
- case LOADED: // success but data isn't loaded?!?!
- displayError("The project target (%s) was not properly loaded.",
- target.getName());
- break;
- }
-
- return;
- }
-
- // check there is actually a model (maybe the file is empty).
- UiDocumentNode model = getModel();
-
- if (model.getUiChildren().size() == 0) {
- displayError("No Xml content. Go to the Outline view and add nodes.");
- return;
- }
-
- LayoutBridge bridge = data.getLayoutBridge();
-
- if (bridge.bridge != null) { // bridge can never be null.
- ResourceManager resManager = ResourceManager.getInstance();
-
- ProjectResources projectRes = resManager.getProjectResources(iProject);
- if (projectRes == null) {
- displayError("Missing project resources.");
- return;
- }
-
- // get the resources of the file's project.
- Map<String, Map<String, IResourceValue>> configuredProjectRes =
- mConfigListener.getConfiguredProjectResources();
-
- // get the framework resources
- Map<String, Map<String, IResourceValue>> frameworkResources =
- mConfigListener.getConfiguredFrameworkResources();
-
- if (configuredProjectRes != null && frameworkResources != null) {
- if (mProjectCallback == null) {
- mProjectCallback = new ProjectCallback(
- bridge.classLoader, projectRes, iProject);
- }
-
- if (mLogger == null) {
- mLogger = new ILayoutLog() {
- public void error(String message) {
- AdtPlugin.printErrorToConsole(mEditedFile.getName(), message);
- }
-
- public void error(Throwable error) {
- String message = error.getMessage();
- if (message == null) {
- message = error.getClass().getName();
- }
-
- PrintStream ps = new PrintStream(AdtPlugin.getErrorStream());
- error.printStackTrace(ps);
- }
-
- public void warning(String message) {
- AdtPlugin.printToConsole(mEditedFile.getName(), message);
- }
- };
- }
-
- // get the selected theme
- String theme = mConfigComposite.getTheme();
- if (theme != null) {
- // Compute the layout
- Rectangle rect = getBounds();
-
- int width = rect.width;
- int height = rect.height;
- if (mUseExplodeMode) {
- // compute how many padding in x and y will bump the screen size
- ExplodedRenderingHelper helper = new ExplodedRenderingHelper(
- getModel(), iProject);
-
- // there are 2 paddings for each view
- // left and right, or top and bottom.
- int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;
-
- width += helper.getWidthPadding() * paddingValue;
- height += helper.getHeightPadding() * paddingValue;
- }
-
- int density = mConfigComposite.getDensity().getDpiValue();
- float xdpi = mConfigComposite.getXDpi();
- float ydpi = mConfigComposite.getYDpi();
- boolean isProjectTheme = mConfigComposite.isProjectTheme();
-
- UiElementPullParser parser = new UiElementPullParser(getModel(),
- mUseExplodeMode, density, xdpi, iProject);
-
- ILayoutResult result = computeLayout(bridge, parser,
- iProject /* projectKey */,
- width, height, !mConfigComposite.getClipping(),
- density, xdpi, ydpi,
- theme, isProjectTheme,
- configuredProjectRes, frameworkResources, mProjectCallback,
- mLogger);
-
- mLayoutCanvas.setResult(result);
-
- // update the UiElementNode with the layout info.
- if (result.getSuccess() == ILayoutResult.SUCCESS) {
- hideError();
- } else {
- displayError(result.getErrorMessage());
- }
-
- model.refreshUi();
- }
- }
- } else {
- // SDK is loaded but not the layout library!
-
- // check whether the bridge managed to load, or not
- if (bridge.status == LoadStatus.LOADING) {
- displayError("Eclipse is loading framework information and the layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.",
- mEditedFile.getName());
- } else {
- displayError("Eclipse failed to load the framework information and the layout library!");
- }
- }
- } else {
- displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",
- mEditedFile.getName());
- }
- } finally {
- // no matter the result, we are done doing the recompute based on the latest
- // resource/code change.
- mNeedsRecompute = false;
- }
- }
-
- /**
- * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
- * the implementation API level.
- *
- * Implementation detail: the bridge's computeLayout() method already returns a newly
- * allocated ILayoutResult.
- */
- @SuppressWarnings("deprecation")
- private static ILayoutResult computeLayout(LayoutBridge bridge,
- IXmlPullParser layoutDescription, Object projectKey,
- int screenWidth, int screenHeight, boolean renderFullSize,
- int density, float xdpi, float ydpi,
- String themeName, boolean isProjectTheme,
- Map<String, Map<String, IResourceValue>> projectResources,
- Map<String, Map<String, IResourceValue>> frameworkResources,
- IProjectCallback projectCallback, ILayoutLog logger) {
-
- if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) {
- // newest API with support for "render full height"
- // TODO: link boolean to UI.
- return bridge.bridge.computeLayout(layoutDescription,
- projectKey, screenWidth, screenHeight, renderFullSize,
- density, xdpi, ydpi,
- themeName, isProjectTheme,
- projectResources, frameworkResources, projectCallback,
- logger);
- } else if (bridge.apiLevel == 3) {
- // newer api with density support.
- return bridge.bridge.computeLayout(layoutDescription,
- projectKey, screenWidth, screenHeight, density, xdpi, ydpi,
- themeName, isProjectTheme,
- projectResources, frameworkResources, projectCallback,
- logger);
- } else if (bridge.apiLevel == 2) {
- // api with boolean for separation of project/framework theme
- return bridge.bridge.computeLayout(layoutDescription,
- projectKey, screenWidth, screenHeight, themeName, isProjectTheme,
- projectResources, frameworkResources, projectCallback,
- logger);
- } else {
- // oldest api with no density/dpi, and project theme boolean mixed
- // into the theme name.
-
- // change the string if it's a custom theme to make sure we can
- // differentiate them
- if (isProjectTheme) {
- themeName = "*" + themeName; //$NON-NLS-1$
- }
-
- return bridge.bridge.computeLayout(layoutDescription,
- projectKey, screenWidth, screenHeight, themeName,
- projectResources, frameworkResources, projectCallback,
- logger);
- }
- }
-
- public Rectangle getBounds() {
- return mConfigComposite.getScreenBounds();
- }
-
- public void reloadPalette() {
- if (mPalette != null) {
- mPalette.reloadPalette(mLayoutEditor.getTargetData());
- }
- }
-
- /**
- * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
- * created by {@link ElementCreateCommand#execute()}.
- *
- * @param uiNodeModel The {@link UiElementNode} to select.
- */
- public void selectModel(UiElementNode uiNodeModel) {
-
- // TODO this method was useful for GLE1. We may not need it anymore now.
-
-// GraphicalViewer viewer = getGraphicalViewer();
-//
-// // Give focus to the graphical viewer (in case the outline has it)
-// viewer.getControl().forceFocus();
-//
-// Object editPart = viewer.getEditPartRegistry().get(uiNodeModel);
-//
-// if (editPart instanceof EditPart) {
-// viewer.select((EditPart)editPart);
-// }
- }
-
- private class ReloadListener implements ILayoutReloadListener {
- /*
- * Called when the file changes triggered a redraw of the layout
- */
- public void reloadLayout(ChangeFlags flags) {
- boolean recompute = false;
-
- if (flags.rClass) {
- recompute = true;
- if (mEditedFile != null) {
- ProjectResources projectRes = ResourceManager.getInstance().getProjectResources(
- mEditedFile.getProject());
-
- if (projectRes != null) {
- projectRes.resetDynamicIds();
- }
- }
- }
-
- if (flags.localeList) {
- // the locale list *potentially* changed so we update the locale in the
- // config composite.
- // However there's no recompute, as it could not be needed
- // (for instance a new layout)
- // If a resource that's not a layout changed this will trigger a recompute anyway.
- mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
- public void run() {
- mConfigComposite.updateLocales();
- }
- });
- }
-
- if (flags.resources) {
- recompute = true;
-
- // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache.
-
- // force a reparse in case a value XML file changed.
- mConfiguredProjectRes = null;
-
- // clear the cache in the bridge in case a bitmap/9-patch changed.
- IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());
- if (target != null) {
-
- AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
- if (data != null) {
- LayoutBridge bridge = data.getLayoutBridge();
-
- if (bridge.bridge != null) {
- bridge.bridge.clearCaches(mEditedFile.getProject());
- }
- }
- }
- }
-
- if (flags.code) {
- // only recompute if the custom view loader was used to load some code.
- if (mProjectCallback != null && mProjectCallback.isUsed()) {
- mProjectCallback = null;
- recompute = true;
- }
- }
-
- if (recompute) {
- mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
- public void run() {
- if (mLayoutEditor.isGraphicalEditorActive()) {
- recomputeLayout();
- } else {
- mNeedsRecompute = true;
- }
- }
- });
- }
- }
- }
-}
+/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.internal.editors.layout.gle2; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper; +import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor; +import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback; +import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; +import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.LoadStatus; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge; +import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; +import com.android.layoutlib.api.ILayoutBridge; +import com.android.layoutlib.api.ILayoutLog; +import com.android.layoutlib.api.ILayoutResult; +import com.android.layoutlib.api.IProjectCallback; +import com.android.layoutlib.api.IResourceValue; +import com.android.layoutlib.api.IXmlPullParser; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.gef.ui.parts.SelectionSynchronizer; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.EditorPart; +import org.eclipse.ui.part.FileEditorInput; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Map; + +/** + * Graphical layout editor part, version 2. + * + * @since GLE2 + * + * TODO List: + * - display error icon + * - finish palette (see palette's todo list) + * - finish canvas (see canva's todo list) + * - completly rethink the property panel + * - link to the existing outline editor (prolly reuse/adapt for simplier model and will need + * to adapt the selection synchronizer.) + */ +public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutEditor { + + /* + * Useful notes: + * To understand Drag'n'drop: + * http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html + */ + + /** Reference to the layout editor */ + private final LayoutEditor mLayoutEditor; + + /** reference to the file being edited. Can also be used to access the {@link IProject}. */ + private IFile mEditedFile; + + /** The current clipboard. Must be disposed later. */ + private Clipboard mClipboard; + + /** The configuration composite at the top of the layout editor. */ + private ConfigurationComposite mConfigComposite; + + /** The sash that splits the palette from the canvas. */ + private SashForm mSashPalette; + private SashForm mSashError; + + /** The palette displayed on the left of the sash. */ + private PaletteComposite mPalette; + + /** The layout canvas displayed to the right of the sash. */ + private LayoutCanvas mLayoutCanvas; + + /** The Groovy Rules Engine associated with this editor. It is project-specific. */ + private RulesEngine mRulesEngine; + + private StyledText mErrorLabel; + + private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes; + private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes; + private ProjectCallback mProjectCallback; + private ILayoutLog mLogger; + + private boolean mNeedsXmlReload = false; + private boolean mNeedsRecompute = false; + + private TargetListener mTargetListener; + + private ConfigListener mConfigListener; + + private ReloadListener mReloadListener; + + protected boolean mUseExplodeMode; + + + public GraphicalEditorPart(LayoutEditor layoutEditor) { + mLayoutEditor = layoutEditor; + setPartName("Graphical Layout"); + } + + // ------------------------------------ + // Methods overridden from base classes + //------------------------------------ + + /** + * Initializes the editor part with a site and input. + * {@inheritDoc} + */ + @Override + public void init(IEditorSite site, IEditorInput input) throws PartInitException { + setSite(site); + useNewEditorInput(input); + + if (mTargetListener == null) { + mTargetListener = new TargetListener(); + AdtPlugin.getDefault().addTargetListener(mTargetListener); + } + } + + private void useNewEditorInput(IEditorInput input) throws PartInitException { + // The contract of init() mentions we need to fail if we can't understand the input. + if (!(input instanceof FileEditorInput)) { + throw new PartInitException("Input is not of type FileEditorInput: " + //$NON-NLS-1$ + input == null ? "null" : input.toString()); //$NON-NLS-1$ + } + } + + @Override + public void createPartControl(Composite parent) { + + Display d = parent.getDisplay(); + mClipboard = new Clipboard(d); + + GridLayout gl = new GridLayout(1, false); + parent.setLayout(gl); + gl.marginHeight = gl.marginWidth = 0; + + // create the top part for the configuration control + + CustomToggle[] toggles = new CustomToggle[] { + new CustomToggle( + "Explode", + null, //image + "Displays extra margins in the layout." + ) { + @Override + public void onSelected(boolean newState) { + mUseExplodeMode = newState; + recomputeLayout(); + } + }, + new CustomToggle( + "Outline", + null, //image + "Shows the of all views in the layout." + ) { + @Override + public void onSelected(boolean newState) { + mLayoutCanvas.setShowOutline(newState); + } + } + }; + + mConfigListener = new ConfigListener(); + mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER); + + mSashPalette = new SashForm(parent, SWT.HORIZONTAL); + mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH)); + + mPalette = new PaletteComposite(mSashPalette); + + mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER); + mSashError.setLayoutData(new GridData(GridData.FILL_BOTH)); + + mLayoutCanvas = new LayoutCanvas(mRulesEngine, mSashError, SWT.NONE); + + mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY); + mErrorLabel.setEditable(false); + mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + + mSashPalette.setWeights(new int[] { 20, 80 }); + mSashError.setWeights(new int[] { 80, 20 }); + mSashError.setMaximizedControl(mLayoutCanvas); + + setupEditActions(); + + // Initialize the state + reloadPalette(); + } + + private void setupEditActions() { + + IActionBars actionBars = getEditorSite().getActionBars(); + + actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), new Action("Copy") { + @Override + public void run() { + // TODO enable copy only when there's a selection + mLayoutCanvas.onCopy(mClipboard); + } + }); + + actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), new Action("Cut") { + @Override + public void run() { + // TODO enable cut only when there's a selection + mLayoutCanvas.onCut(mClipboard); + } + }); + + actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new Action("Paste") { + @Override + public void run() { + mLayoutCanvas.onPaste(mClipboard); + } + }); + + actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), + new Action("Select All") { + @Override + public void run() { + mLayoutCanvas.onSelectAll(); + } + }); + } + + /** + * Switches the stack to display the error label and hide the canvas. + * @param errorFormat The new error to display if not null. + * @param parameters String.format parameters for the error format. + */ + private void displayError(String errorFormat, Object...parameters) { + if (errorFormat != null) { + mErrorLabel.setText(String.format(errorFormat, parameters)); + } + mSashError.setMaximizedControl(null); + } + + /** Displays the canvas and hides the error label. */ + private void hideError() { + mSashError.setMaximizedControl(mLayoutCanvas); + } + + @Override + public void dispose() { + if (mTargetListener != null) { + AdtPlugin.getDefault().removeTargetListener(mTargetListener); + mTargetListener = null; + } + + if (mReloadListener != null) { + LayoutReloadMonitor.getMonitor().removeListener(mReloadListener); + mReloadListener = null; + } + + if (mClipboard != null) { + mClipboard.dispose(); + mClipboard = null; + } + + super.dispose(); + } + + /** + * Listens to changes from the Configuration UI banner and triggers layout rendering when + * changed. Also provide the Configuration UI with the list of resources/layout to display. + */ + private class ConfigListener implements IConfigListener { + + /** + * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it. + * <p/>If there is no match, notify the user. + */ + public void onConfigurationChange() { + mConfiguredFrameworkRes = mConfiguredProjectRes = null; + + if (mEditedFile == null || mConfigComposite.getEditedConfig() == null) { + return; + } + + // Before doing the normal process, test for the following case. + // - the editor is being opened (or reset for a new input) + // - the file being opened is not the best match for any possible configuration + // - another random compatible config was chosen in the config composite. + // The result is that 'match' will not be the file being edited, but because this is not + // due to a config change, we should not trigger opening the actual best match (also, + // because the editor is still opening the MatchingStrategy woudln't answer true + // and the best match file would open in a different editor). + // So the solution is that if the editor is being created, we just call recomputeLayout + // without looking for a better matching layout file. + if (mLayoutEditor.isCreatingPages()) { + recomputeLayout(); + } else { + // get the resources of the file's project. + ProjectResources resources = ResourceManager.getInstance().getProjectResources( + mEditedFile.getProject()); + + // from the resources, look for a matching file + ResourceFile match = null; + if (resources != null) { + match = resources.getMatchingFile(mEditedFile.getName(), + ResourceFolderType.LAYOUT, + mConfigComposite.getCurrentConfig()); + } + + if (match != null) { + if (match.getFile().equals(mEditedFile) == false) { + try { + // tell the editor that the next replacement file is due to a config + // change. + mLayoutEditor.setNewFileOnConfigChange(true); + + // ask the IDE to open the replacement file. + IDE.openEditor( + getSite().getWorkbenchWindow().getActivePage(), + match.getFile().getIFile()); + + // we're done! + return; + } catch (PartInitException e) { + // FIXME: do something! + } + } + + // at this point, we have not opened a new file. + + // Even though the layout doesn't change, the config changed, and referenced + // resources need to be updated. + recomputeLayout(); + } else { + // display the error. + FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig(); + displayError( + "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.", + currentConfig.toDisplayString(), + currentConfig.getFolderName(ResourceFolderType.LAYOUT, + Sdk.getCurrent().getTarget(mEditedFile.getProject())), + mEditedFile.getName()); + } + } + } + + public void onThemeChange() { + recomputeLayout(); + } + + public void onClippingChange() { + recomputeLayout(); + } + + public void onCreate() { + LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(), + mEditedFile.getName(), + Sdk.getCurrent().getTarget(mEditedFile.getProject()), + mConfigComposite.getCurrentConfig()); + if (dialog.open() == Dialog.OK) { + final FolderConfiguration config = new FolderConfiguration(); + dialog.getConfiguration(config); + + createAlternateLayout(config); + } + } + + public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() { + if (mConfiguredFrameworkRes == null && mConfigComposite != null) { + ProjectResources frameworkRes = getFrameworkResources(); + + if (frameworkRes == null) { + AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework"); + } else { + // get the framework resource values based on the current config + mConfiguredFrameworkRes = frameworkRes.getConfiguredResources( + mConfigComposite.getCurrentConfig()); + } + } + + return mConfiguredFrameworkRes; + } + + public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() { + if (mConfiguredProjectRes == null && mConfigComposite != null) { + ProjectResources project = getProjectResources(); + + // make sure they are loaded + project.loadAll(); + + // get the project resource values based on the current config + mConfiguredProjectRes = project.getConfiguredResources( + mConfigComposite.getCurrentConfig()); + } + + return mConfiguredProjectRes; + } + + /** + * Returns a {@link ProjectResources} for the framework resources. + * @return the framework resources or null if not found. + */ + public ProjectResources getFrameworkResources() { + if (mEditedFile != null) { + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); + + if (target != null) { + AndroidTargetData data = currentSdk.getTargetData(target); + + if (data != null) { + return data.getFrameworkResources(); + } + } + } + } + + return null; + } + + public ProjectResources getProjectResources() { + if (mEditedFile != null) { + ResourceManager manager = ResourceManager.getInstance(); + return manager.getProjectResources(mEditedFile.getProject()); + } + + return null; + } + + /** + * Creates a new layout file from the specified {@link FolderConfiguration}. + */ + private void createAlternateLayout(final FolderConfiguration config) { + new Job("Create Alternate Resource") { + @Override + protected IStatus run(IProgressMonitor monitor) { + // get the folder name + String folderName = config.getFolderName(ResourceFolderType.LAYOUT, + Sdk.getCurrent().getTarget(mEditedFile.getProject())); + try { + + // look to see if it exists. + // get the res folder + IFolder res = (IFolder)mEditedFile.getParent().getParent(); + String path = res.getLocation().toOSString(); + + File newLayoutFolder = new File(path + File.separator + folderName); + if (newLayoutFolder.isFile()) { + // this should not happen since aapt would have complained + // before, but if one disable the automatic build, this could + // happen. + String message = String.format("File 'res/%1$s' is in the way!", + folderName); + + AdtPlugin.displayError("Layout Creation", message); + + return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message); + } else if (newLayoutFolder.exists() == false) { + // create it. + newLayoutFolder.mkdir(); + } + + // now create the file + File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() + + File.separator + mEditedFile.getName()); + + newLayoutFile.createNewFile(); + + InputStream input = mEditedFile.getContents(); + + FileOutputStream fos = new FileOutputStream(newLayoutFile); + + byte[] data = new byte[512]; + int count; + while ((count = input.read(data)) != -1) { + fos.write(data, 0, count); + } + + input.close(); + fos.close(); + + // refreshes the res folder to show up the new + // layout folder (if needed) and the file. + // We use a progress monitor to catch the end of the refresh + // to trigger the edit of the new file. + res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() { + public void done() { + mConfigComposite.getDisplay().asyncExec(new Runnable() { + public void run() { + onConfigurationChange(); + } + }); + } + + public void beginTask(String name, int totalWork) { + // pass + } + + public void internalWorked(double work) { + // pass + } + + public boolean isCanceled() { + // pass + return false; + } + + public void setCanceled(boolean value) { + // pass + } + + public void setTaskName(String name) { + // pass + } + + public void subTask(String name) { + // pass + } + + public void worked(int work) { + // pass + } + }); + } catch (IOException e2) { + String message = String.format( + "Failed to create File 'res/%1$s/%2$s' : %3$s", + folderName, mEditedFile.getName(), e2.getMessage()); + + AdtPlugin.displayError("Layout Creation", message); + + return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + message, e2); + } catch (CoreException e2) { + String message = String.format( + "Failed to create File 'res/%1$s/%2$s' : %3$s", + folderName, mEditedFile.getName(), e2.getMessage()); + + AdtPlugin.displayError("Layout Creation", message); + + return e2.getStatus(); + } + + return Status.OK_STATUS; + + } + }.schedule(); + } + } + + /** + * Listens to target changed in the current project, to trigger a new layout rendering. + */ + private class TargetListener implements ITargetChangeListener { + + public void onProjectTargetChange(IProject changedProject) { + if (changedProject != null && changedProject.equals(getProject())) { + updateEditor(); + } + } + + public void onTargetLoaded(IAndroidTarget target) { + IProject project = getProject(); + if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) { + updateEditor(); + } + } + + public void onSdkLoaded() { + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); + if (target != null) { + mConfigComposite.onSdkLoaded(target); + mConfigListener.onConfigurationChange(); + } + } + } + + private void updateEditor() { + mLayoutEditor.commitPages(false /* onSave */); + + // because the target changed we must reset the configured resources. + mConfiguredFrameworkRes = mConfiguredProjectRes = null; + + // make sure we remove the custom view loader, since its parent class loader is the + // bridge class loader. + mProjectCallback = null; + + // recreate the ui root node always, this will also call onTargetChange + // on the config composite + mLayoutEditor.initUiRootNode(true /*force*/); + } + + private IProject getProject() { + return getLayoutEditor().getProject(); + } + } + + // ---------------- + + /** + * Save operation in the Graphical Editor Part. + * <p/> + * In our workflow, the model is owned by the Structured XML Editor. + * The graphical layout editor just displays it -- thus we don't really + * save anything here. + * <p/> + * This must NOT call the parent editor part. At the contrary, the parent editor + * part will call this *after* having done the actual save operation. + * <p/> + * The only action this editor must do is mark the undo command stack as + * being no longer dirty. + */ + @Override + public void doSave(IProgressMonitor monitor) { + // TODO implement a command stack +// getCommandStack().markSaveLocation(); +// firePropertyChange(PROP_DIRTY); + } + + /** + * Save operation in the Graphical Editor Part. + * <p/> + * In our workflow, the model is owned by the Structured XML Editor. + * The graphical layout editor just displays it -- thus we don't really + * save anything here. + */ + @Override + public void doSaveAs() { + // pass + } + + /** + * In our workflow, the model is owned by the Structured XML Editor. + * The graphical layout editor just displays it -- thus we don't really + * save anything here. + */ + @Override + public boolean isDirty() { + return false; + } + + /** + * In our workflow, the model is owned by the Structured XML Editor. + * The graphical layout editor just displays it -- thus we don't really + * save anything here. + */ + @Override + public boolean isSaveAsAllowed() { + return false; + } + + @Override + public void setFocus() { + // TODO Auto-generated method stub + + } + + /** + * Responds to a page change that made the Graphical editor page the activated page. + */ + public void activated() { + if (mNeedsRecompute || mNeedsXmlReload) { + recomputeLayout(); + } + } + + /** + * Responds to a page change that made the Graphical editor page the deactivated page + */ + public void deactivated() { + // nothing to be done here for now. + } + + /** + * Opens and initialize the editor with a new file. + * @param file the file being edited. + */ + public void openFile(IFile file) { + mEditedFile = file; + mConfigComposite.openFile(mEditedFile); + + if (mReloadListener == null) { + mReloadListener = new ReloadListener(); + LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener); + } + + if (mRulesEngine == null) { + mRulesEngine = new RulesEngine(mEditedFile.getProject()); + if (mLayoutCanvas != null) { + mLayoutCanvas.setRulesEngine(mRulesEngine); + } + } + } + + /** + * Resets the editor with a replacement file. + * @param file the replacement file. + */ + public void replaceFile(IFile file) { + mEditedFile = file; + mConfigComposite.replaceFile(mEditedFile); + } + + /** + * Resets the editor with a replacement file coming from a config change in the config + * selector. + * @param file the replacement file. + */ + public void changeFileOnNewConfig(IFile file) { + mEditedFile = file; + mConfigComposite.changeFileOnNewConfig(mEditedFile); + } + + public void onTargetChange() { + mConfigComposite.onTargetChange(); + mConfigListener.onConfigurationChange(); + } + + public void onSdkChange() { + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); + if (target != null) { + mConfigComposite.onSdkLoaded(target); + mConfigListener.onConfigurationChange(); + } + } + } + + public Clipboard getClipboard() { + return mClipboard; + } + + public LayoutEditor getLayoutEditor() { + return mLayoutEditor; + } + + public UiDocumentNode getModel() { + return mLayoutEditor.getUiRootNode(); + } + + public SelectionSynchronizer getSelectionSynchronizer() { + // TODO Auto-generated method stub + return null; + } + + /** + * Callback for XML model changed. Only update/recompute the layout if the editor is visible + */ + public void onXmlModelChanged() { + if (mLayoutEditor.isGraphicalEditorActive()) { + doXmlReload(true /* force */); + recomputeLayout(); + } else { + mNeedsXmlReload = true; + } + } + + /** + * Actually performs the XML reload + * @see #onXmlModelChanged() + */ + private void doXmlReload(boolean force) { + if (force || mNeedsXmlReload) { + + // TODO : update the mLayoutCanvas, preserving the current selection if possible. + +// GraphicalViewer viewer = getGraphicalViewer(); +// +// // try to preserve the selection before changing the content +// SelectionManager selMan = viewer.getSelectionManager(); +// ISelection selection = selMan.getSelection(); +// +// try { +// viewer.setContents(getModel()); +// } finally { +// selMan.setSelection(selection); +// } + + mNeedsXmlReload = false; + } + } + + public void recomputeLayout() { + doXmlReload(false /* force */); + try { + // check that the resource exists. If the file is opened but the project is closed + // or deleted for some reason (changed from outside of eclipse), then this will + // return false; + if (mEditedFile.exists() == false) { + displayError("Resource '%1$s' does not exist.", + mEditedFile.getFullPath().toString()); + return; + } + + IProject iProject = mEditedFile.getProject(); + + if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) { + String message = String.format("%1$s is out of sync. Please refresh.", + mEditedFile.getName()); + + displayError(message); + + // also print it in the error console. + AdtPlugin.printErrorToConsole(iProject.getName(), message); + return; + } + + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); + if (target == null) { + displayError("The project target is not set."); + return; + } + + AndroidTargetData data = currentSdk.getTargetData(target); + if (data == null) { + // It can happen that the workspace refreshes while the SDK is loading its + // data, which could trigger a redraw of the opened layout if some resources + // changed while Eclipse is closed. + // In this case data could be null, but this is not an error. + // We can just silently return, as all the opened editors are automatically + // refreshed once the SDK finishes loading. + LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null); + switch (targetLoadStatus) { + case LOADING: + displayError("The project target (%1$s) is still loading.\n%2$s will refresh automatically once the process is finished.", + target.getName(), mEditedFile.getName()); + + break; + case FAILED: // known failure + case LOADED: // success but data isn't loaded?!?! + displayError("The project target (%s) was not properly loaded.", + target.getName()); + break; + } + + return; + } + + // check there is actually a model (maybe the file is empty). + UiDocumentNode model = getModel(); + + if (model.getUiChildren().size() == 0) { + displayError("No Xml content. Go to the Outline view and add nodes."); + return; + } + + LayoutBridge bridge = data.getLayoutBridge(); + + if (bridge.bridge != null) { // bridge can never be null. + ResourceManager resManager = ResourceManager.getInstance(); + + ProjectResources projectRes = resManager.getProjectResources(iProject); + if (projectRes == null) { + displayError("Missing project resources."); + return; + } + + // get the resources of the file's project. + Map<String, Map<String, IResourceValue>> configuredProjectRes = + mConfigListener.getConfiguredProjectResources(); + + // get the framework resources + Map<String, Map<String, IResourceValue>> frameworkResources = + mConfigListener.getConfiguredFrameworkResources(); + + if (configuredProjectRes != null && frameworkResources != null) { + if (mProjectCallback == null) { + mProjectCallback = new ProjectCallback( + bridge.classLoader, projectRes, iProject); + } + + if (mLogger == null) { + mLogger = new ILayoutLog() { + public void error(String message) { + AdtPlugin.printErrorToConsole(mEditedFile.getName(), message); + } + + public void error(Throwable error) { + String message = error.getMessage(); + if (message == null) { + message = error.getClass().getName(); + } + + PrintStream ps = new PrintStream(AdtPlugin.getErrorStream()); + error.printStackTrace(ps); + } + + public void warning(String message) { + AdtPlugin.printToConsole(mEditedFile.getName(), message); + } + }; + } + + // get the selected theme + String theme = mConfigComposite.getTheme(); + if (theme != null) { + // Compute the layout + Rectangle rect = getBounds(); + + int width = rect.width; + int height = rect.height; + if (mUseExplodeMode) { + // compute how many padding in x and y will bump the screen size + ExplodedRenderingHelper helper = new ExplodedRenderingHelper( + getModel(), iProject); + + // there are 2 paddings for each view + // left and right, or top and bottom. + int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2; + + width += helper.getWidthPadding() * paddingValue; + height += helper.getHeightPadding() * paddingValue; + } + + int density = mConfigComposite.getDensity().getDpiValue(); + float xdpi = mConfigComposite.getXDpi(); + float ydpi = mConfigComposite.getYDpi(); + boolean isProjectTheme = mConfigComposite.isProjectTheme(); + + UiElementPullParser parser = new UiElementPullParser(getModel(), + mUseExplodeMode, density, xdpi, iProject); + + ILayoutResult result = computeLayout(bridge, parser, + iProject /* projectKey */, + width, height, !mConfigComposite.getClipping(), + density, xdpi, ydpi, + theme, isProjectTheme, + configuredProjectRes, frameworkResources, mProjectCallback, + mLogger); + + mLayoutCanvas.setResult(result); + + // update the UiElementNode with the layout info. + if (result.getSuccess() == ILayoutResult.SUCCESS) { + hideError(); + } else { + displayError(result.getErrorMessage()); + } + + model.refreshUi(); + } + } + } else { + // SDK is loaded but not the layout library! + + // check whether the bridge managed to load, or not + if (bridge.status == LoadStatus.LOADING) { + displayError("Eclipse is loading framework information and the layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.", + mEditedFile.getName()); + } else { + displayError("Eclipse failed to load the framework information and the layout library!"); + } + } + } else { + displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.", + mEditedFile.getName()); + } + } finally { + // no matter the result, we are done doing the recompute based on the latest + // resource/code change. + mNeedsRecompute = false; + } + } + + /** + * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on + * the implementation API level. + * + * Implementation detail: the bridge's computeLayout() method already returns a newly + * allocated ILayoutResult. + */ + @SuppressWarnings("deprecation") + private static ILayoutResult computeLayout(LayoutBridge bridge, + IXmlPullParser layoutDescription, Object projectKey, + int screenWidth, int screenHeight, boolean renderFullSize, + int density, float xdpi, float ydpi, + String themeName, boolean isProjectTheme, + Map<String, Map<String, IResourceValue>> projectResources, + Map<String, Map<String, IResourceValue>> frameworkResources, + IProjectCallback projectCallback, ILayoutLog logger) { + + if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) { + // newest API with support for "render full height" + // TODO: link boolean to UI. + return bridge.bridge.computeLayout(layoutDescription, + projectKey, screenWidth, screenHeight, renderFullSize, + density, xdpi, ydpi, + themeName, isProjectTheme, + projectResources, frameworkResources, projectCallback, + logger); + } else if (bridge.apiLevel == 3) { + // newer api with density support. + return bridge.bridge.computeLayout(layoutDescription, + projectKey, screenWidth, screenHeight, density, xdpi, ydpi, + themeName, isProjectTheme, + projectResources, frameworkResources, projectCallback, + logger); + } else if (bridge.apiLevel == 2) { + // api with boolean for separation of project/framework theme + return bridge.bridge.computeLayout(layoutDescription, + projectKey, screenWidth, screenHeight, themeName, isProjectTheme, + projectResources, frameworkResources, projectCallback, + logger); + } else { + // oldest api with no density/dpi, and project theme boolean mixed + // into the theme name. + + // change the string if it's a custom theme to make sure we can + // differentiate them + if (isProjectTheme) { + themeName = "*" + themeName; //$NON-NLS-1$ + } + + return bridge.bridge.computeLayout(layoutDescription, + projectKey, screenWidth, screenHeight, themeName, + projectResources, frameworkResources, projectCallback, + logger); + } + } + + public Rectangle getBounds() { + return mConfigComposite.getScreenBounds(); + } + + public void reloadPalette() { + if (mPalette != null) { + mPalette.reloadPalette(mLayoutEditor.getTargetData()); + } + } + + /** + * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node + * created by {@link ElementCreateCommand#execute()}. + * + * @param uiNodeModel The {@link UiElementNode} to select. + */ + public void selectModel(UiElementNode uiNodeModel) { + + // TODO this method was useful for GLE1. We may not need it anymore now. + +// GraphicalViewer viewer = getGraphicalViewer(); +// +// // Give focus to the graphical viewer (in case the outline has it) +// viewer.getControl().forceFocus(); +// +// Object editPart = viewer.getEditPartRegistry().get(uiNodeModel); +// +// if (editPart instanceof EditPart) { +// viewer.select((EditPart)editPart); +// } + } + + private class ReloadListener implements ILayoutReloadListener { + /* + * Called when the file changes triggered a redraw of the layout + */ + public void reloadLayout(ChangeFlags flags) { + boolean recompute = false; + + if (flags.rClass) { + recompute = true; + if (mEditedFile != null) { + ProjectResources projectRes = ResourceManager.getInstance().getProjectResources( + mEditedFile.getProject()); + + if (projectRes != null) { + projectRes.resetDynamicIds(); + } + } + } + + if (flags.localeList) { + // the locale list *potentially* changed so we update the locale in the + // config composite. + // However there's no recompute, as it could not be needed + // (for instance a new layout) + // If a resource that's not a layout changed this will trigger a recompute anyway. + mLayoutCanvas.getDisplay().asyncExec(new Runnable() { + public void run() { + mConfigComposite.updateLocales(); + } + }); + } + + if (flags.resources) { + recompute = true; + + // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache. + + // force a reparse in case a value XML file changed. + mConfiguredProjectRes = null; + + // clear the cache in the bridge in case a bitmap/9-patch changed. + IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject()); + if (target != null) { + + AndroidTargetData data = Sdk.getCurrent().getTargetData(target); + if (data != null) { + LayoutBridge bridge = data.getLayoutBridge(); + + if (bridge.bridge != null) { + bridge.bridge.clearCaches(mEditedFile.getProject()); + } + } + } + } + + if (flags.code) { + // only recompute if the custom view loader was used to load some code. + if (mProjectCallback != null && mProjectCallback.isUsed()) { + mProjectCallback = null; + recompute = true; + } + } + + if (recompute) { + mLayoutCanvas.getDisplay().asyncExec(new Runnable() { + public void run() { + if (mLayoutEditor.isGraphicalEditorActive()) { + recomputeLayout(); + } else { + mNeedsRecompute = true; + } + } + }); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index 1463e87..0751492 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -1,775 +1,775 @@ -/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.ide.eclipse.adt.internal.editors.layout.gle2;
-
-import com.android.ide.eclipse.adt.gscripts.DropZone;
-import com.android.ide.eclipse.adt.gscripts.Rect;
-import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
-import com.android.layoutlib.api.ILayoutResult;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.SWTException;
-import org.eclipse.swt.dnd.Clipboard;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.MouseMoveListener;
-import org.eclipse.swt.events.PaintEvent;
-import org.eclipse.swt.events.PaintListener;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.FontMetrics;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.PaletteData;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-
-import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferInt;
-import java.awt.image.Raster;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-
-/**
- * Displays the image rendered by the {@link GraphicalEditorPart} and handles
- * the interaction with the widgets.
- * <p/>
- *
- * @since GLE2
- *
- * TODO list:
- * - gray on error, keep select but disable d'n'd.
- * - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)
- * - handle drop target (from palette).
- * - handle drag'n'drop (internal, for moving/duplicating).
- * - handle context menu (depending on selection).
- * - selection synchronization with the outline (both ways).
- */
-/* package */ class LayoutCanvas extends Canvas {
-
- /**
- * Margin around the rendered image. Should be enough space to display the layout
- * width and height pseudo widgets.
- */
- static final int IMAGE_MARGIN = 5;
-
-
- /** The Groovy Rules Engine, associated with the current project. */
- private RulesEngine mRulesEngine;
-
- /*
- * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}.
- * This can be null.
- * When non null, {@link #mLastValidViewInfoRoot} is guaranteed to be non-null too.
- */
- private ILayoutResult mLastValidResult;
-
- /**
- * The CanvasViewInfo root created for the last update of {@link #mLastValidResult}.
- * This is null when {@link #mLastValidResult} is null.
- * When non null, {@link #mLastValidResult} is guaranteed to be non-null too.
- */
- private CanvasViewInfo mLastValidViewInfoRoot;
-
- /**
- * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}
- * in which case it is also available in {@link #mLastValidResult}.
- * When false this means the canvas is displaying an out-dated result image & bounds and some
- * features should be disabled accordingly such a drag'n'drop.
- * <p/>
- * When this is false, {@link #mLastValidResult} can be non-null and points to an older
- * layout result.
- */
- private boolean mIsResultValid;
-
- /** Current background image. Null when there's no image. */
- private Image mImage;
-
- /** The current selection list. The list is never null, however it can be empty. */
- private final LinkedList<CanvasSelection> mSelections = new LinkedList<CanvasSelection>();
-
- /** CanvasSelection border color. Do not dispose, it's a system color. */
- private Color mSelectionFgColor;
-
- /** CanvasSelection name font. Do not dispose, it's a system font. */
- private Font mSelectionFont;
-
- /** Pixel height of the font displaying the selection name. Initially set to 0 and only
- * initialized in onPaint() when we have a GC. */
- private int mSelectionFontHeight;
-
- /** Current hover view info. Null when no mouse hover. */
- private CanvasViewInfo mHoverViewInfo;
-
- /** Current mouse hover border rectangle. Null when there's no mouse hover. */
- private Rectangle mHoverRect;
-
- /** Hover border color. Must be disposed, it's NOT a system color. */
- private Color mHoverFgColor;
-
- /** Outline color. Do not dispose, it's a system color. */
- private Color mOutlineColor;
-
- /**
- * The <em>current</em> alternate selection, if any, which changes when the Alt key is
- * used during a selection. Can be null.
- */
- private CanvasAlternateSelection mAltSelection;
-
- /** When true, always display the outline of all views. */
- private boolean mShowOutline;
-
- /** Drop target associated with this composite. */
- private DropTarget mDropTarget;
-
- /** Drop listener, with feedback from current drop */
- private CanvasDropListener mDropListener;
-
- /** Drop color. Do not dispose, it's a system color. */
- private Color mDropFgColor;
-
-
- public LayoutCanvas(RulesEngine rulesEngine, Composite parent, int style) {
- super(parent, style | SWT.DOUBLE_BUFFERED);
- mRulesEngine = rulesEngine;
-
- Display d = getDisplay();
- mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
- mHoverFgColor = new Color(d, 0xFF, 0x99, 0x00); // orange
- mOutlineColor = d.getSystemColor(SWT.COLOR_GREEN);
- mSelectionFont = d.getSystemFont();
- mDropFgColor = d.getSystemColor(SWT.COLOR_YELLOW);
-
- addPaintListener(new PaintListener() {
- public void paintControl(PaintEvent e) {
- onPaint(e);
- }
- });
-
- addMouseMoveListener(new MouseMoveListener() {
- public void mouseMove(MouseEvent e) {
- onMouseMove(e);
- }
- });
-
- addMouseListener(new MouseListener() {
- public void mouseUp(MouseEvent e) {
- onMouseUp(e);
- }
-
- public void mouseDown(MouseEvent e) {
- onMouseDown(e);
- }
-
- public void mouseDoubleClick(MouseEvent e) {
- onDoubleClick(e);
- }
- });
-
- mDropTarget = new DropTarget(this, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT);
- mDropTarget.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });
- mDropListener = new CanvasDropListener(this);
- mDropTarget.addDropListener(mDropListener);
- }
-
- @Override
- public void dispose() {
- super.dispose();
-
- if (mHoverFgColor != null) {
- mHoverFgColor.dispose();
- mHoverFgColor = null;
- }
-
- if (mDropTarget != null) {
- mDropTarget.dispose();
- mDropTarget = null;
- }
-
- if (mRulesEngine != null) {
- mRulesEngine.dispose();
- mRulesEngine = null;
- }
- }
-
- /**
- * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid
- * {@link ILayoutResult} in which case it is also available in {@link #mLastValidResult}.
- * When false this means the canvas is displaying an out-dated result image & bounds and some
- * features should be disabled accordingly such a drag'n'drop.
- * <p/>
- * When this is false, {@link #mLastValidResult} can be non-null and points to an older
- * layout result.
- */
- /* package */ boolean isResultValid() {
- return mIsResultValid;
- }
-
- /** Returns the Groovy Rules Engine, associated with the current project. */
- /* package */ RulesEngine getRulesEngine() {
- return mRulesEngine;
- }
-
- /** Sets the Groovy Rules Engine, associated with the current project. */
- /* package */ void setRulesEngine(RulesEngine rulesEngine) {
- mRulesEngine = rulesEngine;
- }
-
- /**
- * Sets the result of the layout rendering. The result object indicates if the layout
- * rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
- *
- * Implementation detail: the bridge's computeLayout() method already returns a newly
- * allocated ILayourResult. That means we can keep this result and hold on to it
- * when it is valid.
- *
- * @param result The new rendering result, either valid or not.
- */
- public void setResult(ILayoutResult result) {
- // disable any hover
- mHoverRect = null;
-
- mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS);
-
- if (mIsResultValid && result != null) {
- mLastValidResult = result;
- mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView());
- setImage(result.getImage());
-
- // Check if the selection is still the same (based on the object keys)
- // and eventually recompute their bounds.
- for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
- CanvasSelection s = it.next();
-
- // Check the if the selected object still exists
- Object key = s.getViewInfo().getUiViewKey();
- CanvasViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot);
-
- // Remove the previous selection -- if the selected object still exists
- // we need to recompute its bounds in case it moved so we'll insert a new one
- // at the same place.
- it.remove();
- if (vi != null) {
- it.add(new CanvasSelection(vi, mRulesEngine));
- }
- }
-
- // remove the current alternate selection views
- mAltSelection = null;
- }
-
- redraw();
- }
-
- public void setShowOutline(boolean newState) {
- mShowOutline = newState;
- redraw();
- }
-
- /**
- * Called by the {@link GraphicalEditorPart} when the Copy action is requested.
- *
- * @param clipboard The shared clipboard. Must not be disposed.
- */
- public void onCopy(Clipboard clipboard) {
- // TODO implement copy to clipbard. Also will need to provide feedback to enable
- // copy only when there's a selection.
- }
-
- /**
- * Called by the {@link GraphicalEditorPart} when the Cut action is requested.
- *
- * @param clipboard The shared clipboard. Must not be disposed.
- */
- public void onCut(Clipboard clipboard) {
- // TODO implement copy to clipbard. Also will need to provide feedback to enable
- // cut only when there's a selection.
- }
-
- /**
- * Called by the {@link GraphicalEditorPart} when the Paste action is requested.
- *
- * @param clipboard The shared clipboard. Must not be disposed.
- */
- public void onPaste(Clipboard clipboard) {
-
- }
-
- /**
- * Called by the {@link GraphicalEditorPart} when the Select All action is requested.
- */
- public void onSelectAll() {
- // First clear the current selection, if any.
- mSelections.clear();
- mAltSelection = null;
-
- // Now select everything if there's a valid layout
- if (mIsResultValid && mLastValidResult != null) {
- selectAllViewInfos(mLastValidViewInfoRoot);
- redraw();
- }
- }
-
- /**
- * Delete action
- */
- public void onDelete() {
- // TODO not implemented yet, not even hooked in yet!
- }
-
- //---
-
- /**
- * Sets the image of the last *successful* rendering.
- * Converts the AWT image into an SWT image.
- */
- private void setImage(BufferedImage awtImage) {
- int width = awtImage.getWidth();
- int height = awtImage.getHeight();
-
- Raster raster = awtImage.getData(new java.awt.Rectangle(width, height));
- int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();
-
- ImageData imageData = new ImageData(width, height, 32,
- new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
-
- imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
-
- mImage = new Image(getDisplay(), imageData);
- }
-
- /**
- * Sets the alpha for the given GC.
- * <p/>
- * Alpha may not work on all platforms and may fail with an exception.
- *
- * @param gc the GC to change
- * @param alpha the new alpha, 0 for transparent, 255 for opaque.
- * @return True if the operation worked, false if it failed with an exception.
- *
- * @see GC#setAlpha(int)
- */
- private boolean gc_setAlpha(GC gc, int alpha) {
- try {
- gc.setAlpha(alpha);
- return true;
- } catch (SWTException e) {
- return false;
- }
- }
-
- /**
- * Paints the canvas in response to paint events.
- */
- private void onPaint(PaintEvent e) {
- GC gc = e.gc;
-
- if (mImage != null) {
- if (!mIsResultValid) {
- gc_setAlpha(gc, 128); // half-transparent
- }
-
- gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN);
-
- if (!mIsResultValid) {
- gc_setAlpha(gc, 255); // opaque
- }
- }
-
- if (mShowOutline) {
- gc.setForeground(mOutlineColor);
- gc.setLineStyle(SWT.LINE_DOT);
- drawOutline(gc, mLastValidViewInfoRoot);
- }
-
- if (mHoverRect != null) {
- gc.setForeground(mHoverFgColor);
- gc.setLineStyle(SWT.LINE_DOT);
- gc.drawRectangle(mHoverRect);
- }
-
- // initialize the selection font height once. We need the GC to do that.
- if (mSelectionFontHeight == 0) {
- gc.setFont(mSelectionFont);
- FontMetrics fm = gc.getFontMetrics();
- mSelectionFontHeight = fm.getHeight();
- }
-
- for (CanvasSelection s : mSelections) {
- drawSelection(gc, s);
- }
-
- drawDropZones(gc);
- }
-
- private void drawOutline(GC gc, CanvasViewInfo info) {
-
- Rectangle r = info.getAbsRect();
- gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
-
- for (CanvasViewInfo vi : info.getChildren()) {
- drawOutline(gc, vi);
- }
- }
-
- private void drawSelection(GC gc, CanvasSelection s) {
- Rectangle r = s.getRect();
-
- gc.setForeground(mSelectionFgColor);
- gc.setLineStyle(SWT.LINE_SOLID);
- gc.drawRectangle(s.getRect());
-
- String name = s.getName();
-
- if (name != null) {
- int xs = r.x + 2;
- int ys = r.y - mSelectionFontHeight;
- if (ys < 0) {
- ys = r.y + r.height;
- }
- gc.drawString(name, xs, ys, true /*transparent*/);
- }
- }
-
- private void drawDropZones(GC gc) {
- if (mDropListener == null) {
- return;
- }
-
- CanvasViewInfo vi = mDropListener.getTargetView();
- if (vi == null) {
- return;
- }
-
- gc.setForeground(mDropFgColor);
-
- ArrayList<DropZone> zones = mDropListener.getZones();
- if (zones != null) {
-
- gc.setLineStyle(SWT.LINE_SOLID);
- gc.setLineWidth(1);
-
- DropZone curr = mDropListener.getCurrentZone();
-
- for (DropZone zone : zones) {
- Rect r = zone.bounds;
- if (r != null && r.w > 0 && r.h > 0) {
- int x = r.x + IMAGE_MARGIN;
- int y = r.y + IMAGE_MARGIN;
-
- int alpha = 128; // half-transparent
- if (zone == curr) {
- alpha = 192;
- }
-
- if (gc_setAlpha(gc, alpha)) {
- gc.fillRectangle(x, y, r.w, r.h);
- gc_setAlpha(gc, 255); // opaque
- }
-
- gc.drawRectangle(x, y, r.w, r.h);
- }
- }
-
- }
-
- gc.setLineStyle(SWT.LINE_DOT);
- gc.setLineWidth(3);
- Rectangle r = vi.getAbsRect();
- gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
- gc.setLineWidth(1);
- }
-
- /**
- * Hover on top of a known child.
- */
- private void onMouseMove(MouseEvent e) {
- if (mLastValidResult != null) {
- CanvasViewInfo root = mLastValidViewInfoRoot;
- CanvasViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN);
-
- // We don't hover on the root since it's not a widget per see and it is always there.
- if (vi == root) {
- vi = null;
- }
-
- boolean needsUpdate = vi != mHoverViewInfo;
- mHoverViewInfo = vi;
-
- if (vi == null) {
- mHoverRect = null;
- } else {
- Rectangle r = vi.getSelectionRect();
- mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN,
- r.width, r.height);
- }
-
- if (needsUpdate) {
- redraw();
- }
- }
- }
-
- private void onMouseDown(MouseEvent e) {
- // pass, not used yet.
- }
-
- /**
- * Performs selection on mouse up (not mouse down).
- * <p/>
- * Shift key is used to toggle in multi-selection.
- * Alt key is used to cycle selection through objects at the same level than the one
- * pointed at (i.e. click on an object then alt-click to cycle).
- */
- private void onMouseUp(MouseEvent e) {
- if (mLastValidResult != null) {
-
- boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
- boolean isAlt = (e.stateMask & SWT.ALT) != 0;
-
- int x = e.x - IMAGE_MARGIN;
- int y = e.y - IMAGE_MARGIN;
- CanvasViewInfo vi = findViewInfoAt(x, y);
-
- if (isShift && !isAlt) {
- // Case where shift is pressed: pointed object is toggled.
-
- // reset alternate selection if any
- mAltSelection = null;
-
- // If nothing has been found at the cursor, assume it might be a user error
- // and avoid clearing the existing selection.
-
- if (vi != null) {
- // toggle this selection on-off: remove it if already selected
- if (deselect(vi)) {
- redraw();
- return;
- }
-
- // otherwise add it.
- mSelections.add(new CanvasSelection(vi, mRulesEngine));
- redraw();
- }
-
- } else if (isAlt) {
- // Case where alt is pressed: select or cycle the object pointed at.
-
- // Note: if shift and alt are pressed, shift is ignored. The alternate selection
- // mechanism does not reset the current multiple selection unless they intersect.
-
- // We need to remember the "origin" of the alternate selection, to be
- // able to continue cycling through it later. If there's no alternate selection,
- // create one. If there's one but not for the same origin object, create a new
- // one too.
- if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) {
- mAltSelection = new CanvasAlternateSelection(vi, findAltViewInfoAt(
- x, y, mLastValidViewInfoRoot, null));
-
- // deselect them all, in case they were partially selected
- deselectAll(mAltSelection.getAltViews());
-
- // select the current one
- CanvasViewInfo vi2 = mAltSelection.getCurrent();
- if (vi2 != null) {
- mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine));
- }
- } else {
- // We're trying to cycle through the current alternate selection.
- // First remove the current object.
- CanvasViewInfo vi2 = mAltSelection.getCurrent();
- deselect(vi2);
-
- // Now select the next one.
- vi2 = mAltSelection.getNext();
- if (vi2 != null) {
- mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine));
- }
- }
- redraw();
-
- } else {
- // Case where no modifier is pressed: either select or reset the selection.
-
- // reset alternate selection if any
- mAltSelection = null;
-
- // reset (multi)selection if any
- if (mSelections.size() > 0) {
- if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {
- // CanvasSelection remains the same, don't touch it.
- return;
- }
- mSelections.clear();
- }
-
- if (vi != null) {
- mSelections.add(new CanvasSelection(vi, mRulesEngine));
- }
- redraw();
- }
- }
- }
-
- /** Deselects a view info. Returns true if the object was actually selected. */
- private boolean deselect(CanvasViewInfo canvasViewInfo) {
- if (canvasViewInfo == null) {
- return false;
- }
-
- for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
- CanvasSelection s = it.next();
- if (canvasViewInfo == s.getViewInfo()) {
- it.remove();
- return true;
- }
- }
-
- return false;
- }
-
- /** Deselects multiple view infos, */
- private void deselectAll(List<CanvasViewInfo> canvasViewInfos) {
- for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) {
- CanvasSelection s = it.next();
- if (canvasViewInfos.contains(s.getViewInfo())) {
- it.remove();
- }
- }
- }
-
- private void onDoubleClick(MouseEvent e) {
- // pass, not used yet.
- }
-
- /**
- * Tries to find a child with the same view key in the view info sub-tree.
- * Returns null if not found.
- */
- private CanvasViewInfo findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo) {
- if (canvasViewInfo.getUiViewKey() == viewKey) {
- return canvasViewInfo;
- }
-
- // try to find a matching child
- for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
- CanvasViewInfo v = findViewInfoKey(viewKey, child);
- if (v != null) {
- return v;
- }
- }
-
- return null;
- }
-
-
- /**
- * Tries to find the inner most child matching the given x,y coordinates in the view
- * info sub-tree, starting at the last know view info root.
- * This uses the potentially-expanded selection bounds.
- *
- * Returns null if not found or if there's view info root.
- */
- /* package */ CanvasViewInfo findViewInfoAt(int x, int y) {
- if (mLastValidViewInfoRoot == null) {
- return null;
- } else {
- return findViewInfoAt(x, y, mLastValidViewInfoRoot);
- }
- }
-
- /**
- * Tries to find the inner most child matching the given x,y coordinates in the view
- * info sub-tree. This uses the potentially-expanded selection bounds.
- *
- * Returns null if not found.
- */
- private CanvasViewInfo findViewInfoAt(int x, int y, CanvasViewInfo canvasViewInfo) {
- Rectangle r = canvasViewInfo.getSelectionRect();
- if (r.contains(x, y)) {
-
- // try to find a matching child first
- for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
- CanvasViewInfo v = findViewInfoAt(x, y, child);
- if (v != null) {
- return v;
- }
- }
-
- // if no children matched, this is the view that we're looking for
- return canvasViewInfo;
- }
-
- return null;
- }
-
- private ArrayList<CanvasViewInfo> findAltViewInfoAt(
- int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList) {
- Rectangle r;
-
- if (outList == null) {
- outList = new ArrayList<CanvasViewInfo>();
-
- // add the parent root only once
- r = parent.getSelectionRect();
- if (r.contains(x, y)) {
- outList.add(parent);
- }
- }
-
- if (parent.getChildren().size() > 0) {
- // then add all children that match the position
- for (CanvasViewInfo child : parent.getChildren()) {
- r = child.getSelectionRect();
- if (r.contains(x, y)) {
- outList.add(child);
- }
- }
-
- // finally recurse in the children
- for (CanvasViewInfo child : parent.getChildren()) {
- r = child.getSelectionRect();
- if (r.contains(x, y)) {
- findAltViewInfoAt(x, y, child, outList);
- }
- }
- }
-
- return outList;
- }
-
- /**
- * Used by {@link #onSelectAll()} to add all current view infos to the selection list.
- *
- * @param canvasViewInfo The root to add. This info and all its children will be added to the
- * selection list.
- */
- private void selectAllViewInfos(CanvasViewInfo canvasViewInfo) {
- mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine));
- for (CanvasViewInfo vi : canvasViewInfo.getChildren()) {
- selectAllViewInfos(vi);
- }
- }
-}
+/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.internal.editors.layout.gle2; + +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; +import com.android.layoutlib.api.ILayoutResult; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.Raster; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** + * Displays the image rendered by the {@link GraphicalEditorPart} and handles + * the interaction with the widgets. + * <p/> + * + * @since GLE2 + * + * TODO list: + * - gray on error, keep select but disable d'n'd. + * - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.) + * - handle drop target (from palette). + * - handle drag'n'drop (internal, for moving/duplicating). + * - handle context menu (depending on selection). + * - selection synchronization with the outline (both ways). + */ +/* package */ class LayoutCanvas extends Canvas { + + /** + * Margin around the rendered image. Should be enough space to display the layout + * width and height pseudo widgets. + */ + static final int IMAGE_MARGIN = 5; + + + /** The Groovy Rules Engine, associated with the current project. */ + private RulesEngine mRulesEngine; + + /* + * The last valid ILayoutResult passed to {@link #setResult(ILayoutResult)}. + * This can be null. + * When non null, {@link #mLastValidViewInfoRoot} is guaranteed to be non-null too. + */ + private ILayoutResult mLastValidResult; + + /** + * The CanvasViewInfo root created for the last update of {@link #mLastValidResult}. + * This is null when {@link #mLastValidResult} is null. + * When non null, {@link #mLastValidResult} is guaranteed to be non-null too. + */ + private CanvasViewInfo mLastValidViewInfoRoot; + + /** + * True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult} + * in which case it is also available in {@link #mLastValidResult}. + * When false this means the canvas is displaying an out-dated result image & bounds and some + * features should be disabled accordingly such a drag'n'drop. + * <p/> + * When this is false, {@link #mLastValidResult} can be non-null and points to an older + * layout result. + */ + private boolean mIsResultValid; + + /** Current background image. Null when there's no image. */ + private Image mImage; + + /** The current selection list. The list is never null, however it can be empty. */ + private final LinkedList<CanvasSelection> mSelections = new LinkedList<CanvasSelection>(); + + /** CanvasSelection border color. Do not dispose, it's a system color. */ + private Color mSelectionFgColor; + + /** CanvasSelection name font. Do not dispose, it's a system font. */ + private Font mSelectionFont; + + /** Pixel height of the font displaying the selection name. Initially set to 0 and only + * initialized in onPaint() when we have a GC. */ + private int mSelectionFontHeight; + + /** Current hover view info. Null when no mouse hover. */ + private CanvasViewInfo mHoverViewInfo; + + /** Current mouse hover border rectangle. Null when there's no mouse hover. */ + private Rectangle mHoverRect; + + /** Hover border color. Must be disposed, it's NOT a system color. */ + private Color mHoverFgColor; + + /** Outline color. Do not dispose, it's a system color. */ + private Color mOutlineColor; + + /** + * The <em>current</em> alternate selection, if any, which changes when the Alt key is + * used during a selection. Can be null. + */ + private CanvasAlternateSelection mAltSelection; + + /** When true, always display the outline of all views. */ + private boolean mShowOutline; + + /** Drop target associated with this composite. */ + private DropTarget mDropTarget; + + /** Drop listener, with feedback from current drop */ + private CanvasDropListener mDropListener; + + /** Drop color. Do not dispose, it's a system color. */ + private Color mDropFgColor; + + + public LayoutCanvas(RulesEngine rulesEngine, Composite parent, int style) { + super(parent, style | SWT.DOUBLE_BUFFERED); + mRulesEngine = rulesEngine; + + Display d = getDisplay(); + mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED); + mHoverFgColor = new Color(d, 0xFF, 0x99, 0x00); // orange + mOutlineColor = d.getSystemColor(SWT.COLOR_GREEN); + mSelectionFont = d.getSystemFont(); + mDropFgColor = d.getSystemColor(SWT.COLOR_YELLOW); + + addPaintListener(new PaintListener() { + public void paintControl(PaintEvent e) { + onPaint(e); + } + }); + + addMouseMoveListener(new MouseMoveListener() { + public void mouseMove(MouseEvent e) { + onMouseMove(e); + } + }); + + addMouseListener(new MouseListener() { + public void mouseUp(MouseEvent e) { + onMouseUp(e); + } + + public void mouseDown(MouseEvent e) { + onMouseDown(e); + } + + public void mouseDoubleClick(MouseEvent e) { + onDoubleClick(e); + } + }); + + mDropTarget = new DropTarget(this, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT); + mDropTarget.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() }); + mDropListener = new CanvasDropListener(this); + mDropTarget.addDropListener(mDropListener); + } + + @Override + public void dispose() { + super.dispose(); + + if (mHoverFgColor != null) { + mHoverFgColor.dispose(); + mHoverFgColor = null; + } + + if (mDropTarget != null) { + mDropTarget.dispose(); + mDropTarget = null; + } + + if (mRulesEngine != null) { + mRulesEngine.dispose(); + mRulesEngine = null; + } + } + + /** + * Returns true when the last {@link #setResult(ILayoutResult)} provided a valid + * {@link ILayoutResult} in which case it is also available in {@link #mLastValidResult}. + * When false this means the canvas is displaying an out-dated result image & bounds and some + * features should be disabled accordingly such a drag'n'drop. + * <p/> + * When this is false, {@link #mLastValidResult} can be non-null and points to an older + * layout result. + */ + /* package */ boolean isResultValid() { + return mIsResultValid; + } + + /** Returns the Groovy Rules Engine, associated with the current project. */ + /* package */ RulesEngine getRulesEngine() { + return mRulesEngine; + } + + /** Sets the Groovy Rules Engine, associated with the current project. */ + /* package */ void setRulesEngine(RulesEngine rulesEngine) { + mRulesEngine = rulesEngine; + } + + /** + * Sets the result of the layout rendering. The result object indicates if the layout + * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. + * + * Implementation detail: the bridge's computeLayout() method already returns a newly + * allocated ILayourResult. That means we can keep this result and hold on to it + * when it is valid. + * + * @param result The new rendering result, either valid or not. + */ + public void setResult(ILayoutResult result) { + // disable any hover + mHoverRect = null; + + mIsResultValid = (result != null && result.getSuccess() == ILayoutResult.SUCCESS); + + if (mIsResultValid && result != null) { + mLastValidResult = result; + mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView()); + setImage(result.getImage()); + + // Check if the selection is still the same (based on the object keys) + // and eventually recompute their bounds. + for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) { + CanvasSelection s = it.next(); + + // Check the if the selected object still exists + Object key = s.getViewInfo().getUiViewKey(); + CanvasViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot); + + // Remove the previous selection -- if the selected object still exists + // we need to recompute its bounds in case it moved so we'll insert a new one + // at the same place. + it.remove(); + if (vi != null) { + it.add(new CanvasSelection(vi, mRulesEngine)); + } + } + + // remove the current alternate selection views + mAltSelection = null; + } + + redraw(); + } + + public void setShowOutline(boolean newState) { + mShowOutline = newState; + redraw(); + } + + /** + * Called by the {@link GraphicalEditorPart} when the Copy action is requested. + * + * @param clipboard The shared clipboard. Must not be disposed. + */ + public void onCopy(Clipboard clipboard) { + // TODO implement copy to clipbard. Also will need to provide feedback to enable + // copy only when there's a selection. + } + + /** + * Called by the {@link GraphicalEditorPart} when the Cut action is requested. + * + * @param clipboard The shared clipboard. Must not be disposed. + */ + public void onCut(Clipboard clipboard) { + // TODO implement copy to clipbard. Also will need to provide feedback to enable + // cut only when there's a selection. + } + + /** + * Called by the {@link GraphicalEditorPart} when the Paste action is requested. + * + * @param clipboard The shared clipboard. Must not be disposed. + */ + public void onPaste(Clipboard clipboard) { + + } + + /** + * Called by the {@link GraphicalEditorPart} when the Select All action is requested. + */ + public void onSelectAll() { + // First clear the current selection, if any. + mSelections.clear(); + mAltSelection = null; + + // Now select everything if there's a valid layout + if (mIsResultValid && mLastValidResult != null) { + selectAllViewInfos(mLastValidViewInfoRoot); + redraw(); + } + } + + /** + * Delete action + */ + public void onDelete() { + // TODO not implemented yet, not even hooked in yet! + } + + //--- + + /** + * Sets the image of the last *successful* rendering. + * Converts the AWT image into an SWT image. + */ + private void setImage(BufferedImage awtImage) { + int width = awtImage.getWidth(); + int height = awtImage.getHeight(); + + Raster raster = awtImage.getData(new java.awt.Rectangle(width, height)); + int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData(); + + ImageData imageData = new ImageData(width, height, 32, + new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF)); + + imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); + + mImage = new Image(getDisplay(), imageData); + } + + /** + * Sets the alpha for the given GC. + * <p/> + * Alpha may not work on all platforms and may fail with an exception. + * + * @param gc the GC to change + * @param alpha the new alpha, 0 for transparent, 255 for opaque. + * @return True if the operation worked, false if it failed with an exception. + * + * @see GC#setAlpha(int) + */ + private boolean gc_setAlpha(GC gc, int alpha) { + try { + gc.setAlpha(alpha); + return true; + } catch (SWTException e) { + return false; + } + } + + /** + * Paints the canvas in response to paint events. + */ + private void onPaint(PaintEvent e) { + GC gc = e.gc; + + if (mImage != null) { + if (!mIsResultValid) { + gc_setAlpha(gc, 128); // half-transparent + } + + gc.drawImage(mImage, IMAGE_MARGIN, IMAGE_MARGIN); + + if (!mIsResultValid) { + gc_setAlpha(gc, 255); // opaque + } + } + + if (mShowOutline) { + gc.setForeground(mOutlineColor); + gc.setLineStyle(SWT.LINE_DOT); + drawOutline(gc, mLastValidViewInfoRoot); + } + + if (mHoverRect != null) { + gc.setForeground(mHoverFgColor); + gc.setLineStyle(SWT.LINE_DOT); + gc.drawRectangle(mHoverRect); + } + + // initialize the selection font height once. We need the GC to do that. + if (mSelectionFontHeight == 0) { + gc.setFont(mSelectionFont); + FontMetrics fm = gc.getFontMetrics(); + mSelectionFontHeight = fm.getHeight(); + } + + for (CanvasSelection s : mSelections) { + drawSelection(gc, s); + } + + drawDropZones(gc); + } + + private void drawOutline(GC gc, CanvasViewInfo info) { + + Rectangle r = info.getAbsRect(); + gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height); + + for (CanvasViewInfo vi : info.getChildren()) { + drawOutline(gc, vi); + } + } + + private void drawSelection(GC gc, CanvasSelection s) { + Rectangle r = s.getRect(); + + gc.setForeground(mSelectionFgColor); + gc.setLineStyle(SWT.LINE_SOLID); + gc.drawRectangle(s.getRect()); + + String name = s.getName(); + + if (name != null) { + int xs = r.x + 2; + int ys = r.y - mSelectionFontHeight; + if (ys < 0) { + ys = r.y + r.height; + } + gc.drawString(name, xs, ys, true /*transparent*/); + } + } + + private void drawDropZones(GC gc) { + if (mDropListener == null) { + return; + } + + CanvasViewInfo vi = mDropListener.getTargetView(); + if (vi == null) { + return; + } + + gc.setForeground(mDropFgColor); + + ArrayList<DropZone> zones = mDropListener.getZones(); + if (zones != null) { + + gc.setLineStyle(SWT.LINE_SOLID); + gc.setLineWidth(1); + + DropZone curr = mDropListener.getCurrentZone(); + + for (DropZone zone : zones) { + Rect r = zone.bounds; + if (r != null && r.w > 0 && r.h > 0) { + int x = r.x + IMAGE_MARGIN; + int y = r.y + IMAGE_MARGIN; + + int alpha = 128; // half-transparent + if (zone == curr) { + alpha = 192; + } + + if (gc_setAlpha(gc, alpha)) { + gc.fillRectangle(x, y, r.w, r.h); + gc_setAlpha(gc, 255); // opaque + } + + gc.drawRectangle(x, y, r.w, r.h); + } + } + + } + + gc.setLineStyle(SWT.LINE_DOT); + gc.setLineWidth(3); + Rectangle r = vi.getAbsRect(); + gc.drawRectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height); + gc.setLineWidth(1); + } + + /** + * Hover on top of a known child. + */ + private void onMouseMove(MouseEvent e) { + if (mLastValidResult != null) { + CanvasViewInfo root = mLastValidViewInfoRoot; + CanvasViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN); + + // We don't hover on the root since it's not a widget per see and it is always there. + if (vi == root) { + vi = null; + } + + boolean needsUpdate = vi != mHoverViewInfo; + mHoverViewInfo = vi; + + if (vi == null) { + mHoverRect = null; + } else { + Rectangle r = vi.getSelectionRect(); + mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, + r.width, r.height); + } + + if (needsUpdate) { + redraw(); + } + } + } + + private void onMouseDown(MouseEvent e) { + // pass, not used yet. + } + + /** + * Performs selection on mouse up (not mouse down). + * <p/> + * Shift key is used to toggle in multi-selection. + * Alt key is used to cycle selection through objects at the same level than the one + * pointed at (i.e. click on an object then alt-click to cycle). + */ + private void onMouseUp(MouseEvent e) { + if (mLastValidResult != null) { + + boolean isShift = (e.stateMask & SWT.SHIFT) != 0; + boolean isAlt = (e.stateMask & SWT.ALT) != 0; + + int x = e.x - IMAGE_MARGIN; + int y = e.y - IMAGE_MARGIN; + CanvasViewInfo vi = findViewInfoAt(x, y); + + if (isShift && !isAlt) { + // Case where shift is pressed: pointed object is toggled. + + // reset alternate selection if any + mAltSelection = null; + + // If nothing has been found at the cursor, assume it might be a user error + // and avoid clearing the existing selection. + + if (vi != null) { + // toggle this selection on-off: remove it if already selected + if (deselect(vi)) { + redraw(); + return; + } + + // otherwise add it. + mSelections.add(new CanvasSelection(vi, mRulesEngine)); + redraw(); + } + + } else if (isAlt) { + // Case where alt is pressed: select or cycle the object pointed at. + + // Note: if shift and alt are pressed, shift is ignored. The alternate selection + // mechanism does not reset the current multiple selection unless they intersect. + + // We need to remember the "origin" of the alternate selection, to be + // able to continue cycling through it later. If there's no alternate selection, + // create one. If there's one but not for the same origin object, create a new + // one too. + if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) { + mAltSelection = new CanvasAlternateSelection(vi, findAltViewInfoAt( + x, y, mLastValidViewInfoRoot, null)); + + // deselect them all, in case they were partially selected + deselectAll(mAltSelection.getAltViews()); + + // select the current one + CanvasViewInfo vi2 = mAltSelection.getCurrent(); + if (vi2 != null) { + mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine)); + } + } else { + // We're trying to cycle through the current alternate selection. + // First remove the current object. + CanvasViewInfo vi2 = mAltSelection.getCurrent(); + deselect(vi2); + + // Now select the next one. + vi2 = mAltSelection.getNext(); + if (vi2 != null) { + mSelections.addFirst(new CanvasSelection(vi2, mRulesEngine)); + } + } + redraw(); + + } else { + // Case where no modifier is pressed: either select or reset the selection. + + // reset alternate selection if any + mAltSelection = null; + + // reset (multi)selection if any + if (mSelections.size() > 0) { + if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) { + // CanvasSelection remains the same, don't touch it. + return; + } + mSelections.clear(); + } + + if (vi != null) { + mSelections.add(new CanvasSelection(vi, mRulesEngine)); + } + redraw(); + } + } + } + + /** Deselects a view info. Returns true if the object was actually selected. */ + private boolean deselect(CanvasViewInfo canvasViewInfo) { + if (canvasViewInfo == null) { + return false; + } + + for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) { + CanvasSelection s = it.next(); + if (canvasViewInfo == s.getViewInfo()) { + it.remove(); + return true; + } + } + + return false; + } + + /** Deselects multiple view infos, */ + private void deselectAll(List<CanvasViewInfo> canvasViewInfos) { + for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) { + CanvasSelection s = it.next(); + if (canvasViewInfos.contains(s.getViewInfo())) { + it.remove(); + } + } + } + + private void onDoubleClick(MouseEvent e) { + // pass, not used yet. + } + + /** + * Tries to find a child with the same view key in the view info sub-tree. + * Returns null if not found. + */ + private CanvasViewInfo findViewInfoKey(Object viewKey, CanvasViewInfo canvasViewInfo) { + if (canvasViewInfo.getUiViewKey() == viewKey) { + return canvasViewInfo; + } + + // try to find a matching child + for (CanvasViewInfo child : canvasViewInfo.getChildren()) { + CanvasViewInfo v = findViewInfoKey(viewKey, child); + if (v != null) { + return v; + } + } + + return null; + } + + + /** + * Tries to find the inner most child matching the given x,y coordinates in the view + * info sub-tree, starting at the last know view info root. + * This uses the potentially-expanded selection bounds. + * + * Returns null if not found or if there's view info root. + */ + /* package */ CanvasViewInfo findViewInfoAt(int x, int y) { + if (mLastValidViewInfoRoot == null) { + return null; + } else { + return findViewInfoAt(x, y, mLastValidViewInfoRoot); + } + } + + /** + * Tries to find the inner most child matching the given x,y coordinates in the view + * info sub-tree. This uses the potentially-expanded selection bounds. + * + * Returns null if not found. + */ + private CanvasViewInfo findViewInfoAt(int x, int y, CanvasViewInfo canvasViewInfo) { + Rectangle r = canvasViewInfo.getSelectionRect(); + if (r.contains(x, y)) { + + // try to find a matching child first + for (CanvasViewInfo child : canvasViewInfo.getChildren()) { + CanvasViewInfo v = findViewInfoAt(x, y, child); + if (v != null) { + return v; + } + } + + // if no children matched, this is the view that we're looking for + return canvasViewInfo; + } + + return null; + } + + private ArrayList<CanvasViewInfo> findAltViewInfoAt( + int x, int y, CanvasViewInfo parent, ArrayList<CanvasViewInfo> outList) { + Rectangle r; + + if (outList == null) { + outList = new ArrayList<CanvasViewInfo>(); + + // add the parent root only once + r = parent.getSelectionRect(); + if (r.contains(x, y)) { + outList.add(parent); + } + } + + if (parent.getChildren().size() > 0) { + // then add all children that match the position + for (CanvasViewInfo child : parent.getChildren()) { + r = child.getSelectionRect(); + if (r.contains(x, y)) { + outList.add(child); + } + } + + // finally recurse in the children + for (CanvasViewInfo child : parent.getChildren()) { + r = child.getSelectionRect(); + if (r.contains(x, y)) { + findAltViewInfoAt(x, y, child, outList); + } + } + } + + return outList; + } + + /** + * Used by {@link #onSelectAll()} to add all current view infos to the selection list. + * + * @param canvasViewInfo The root to add. This info and all its children will be added to the + * selection list. + */ + private void selectAllViewInfos(CanvasViewInfo canvasViewInfo) { + mSelections.add(new CanvasSelection(canvasViewInfo, mRulesEngine)); + for (CanvasViewInfo vi : canvasViewInfo.getChildren()) { + selectAllViewInfos(vi); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java index e780595..7a64789 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteComposite.java @@ -1,358 +1,358 @@ -/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
- *
- * 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.ide.eclipse.adt.internal.editors.layout.gle2;
-
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.CLabel;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DragSource;
-import org.eclipse.swt.dnd.DragSourceEvent;
-import org.eclipse.swt.dnd.DragSourceListener;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.MouseTrackListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ScrollBar;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A palette composite for the {@link GraphicalEditorPart}.
- * <p/>
- * The palette contains several groups, each with a UI name (e.g. layouts and views) and each
- * with a list of element descriptors.
- * <p/>
- *
- * @since GLE2
- *
- * TODO list:
- * - The available items should depend on the actual GLE2 Canvas selection. Selected android
- * views should force filtering on what they accept can be dropped on them (e.g. TabHost,
- * TableLayout). Should enable/disable them, not hide them, to avoid shuffling around.
- * - Optional: a text filter
- * - Optional: have icons that depict the element and/or automatically rendered icons
- * based on a rendering of the widget.
- * - Optional: have context-sensitive tools items, e.g. selection arrow tool,
- * group selection tool, alignment, etc.
- * - Different view strategies: big icon, small icons, text vs no text, compact grid.
- * - This would only be useful with meaningful icons. Out current 1-letter icons are not enough
- * to get rid of text labels.
- */
-public class PaletteComposite extends Composite {
-
-
- /** The parent grid layout that contains all the {@link Toggle} and {@link Item} widgets. */
- private Composite mRoot;
-
- /**
- * Create the composite.
- * @param parent The parent composite.
- */
- public PaletteComposite(Composite parent) {
- super(parent, SWT.BORDER | SWT.V_SCROLL);
- }
-
- @Override
- protected void checkSubclass() {
- // Disable the check that prevents subclassing of SWT components
- }
-
- /**
- * Loads or reloads the palette elements by using the layout and view descriptors from the
- * given target data.
- *
- * @param targetData The target data that contains the descriptors. If null or empty,
- * no groups will be created.
- */
- public void reloadPalette(AndroidTargetData targetData) {
-
- for (Control c : getChildren()) {
- c.dispose();
- }
-
- setGridLayout(this, 2);
-
- mRoot = new Composite(this, SWT.NONE);
- setGridLayout(mRoot, 0);
-
- if (targetData != null) {
- addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors());
- addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());
- }
-
- layout(true);
-
- final ScrollBar vbar = getVerticalBar();
-
- vbar.setMaximum(getSize().y);
-
- for (Listener listener : vbar.getListeners(SWT.Selection)) {
- vbar.removeListener(SWT.Selection, listener);
- }
-
- vbar.addListener(SWT.Selection, new Listener() {
- public void handleEvent(Event event) {
- Point p = mRoot.getLocation();
- p.y = - vbar.getSelection();
- mRoot.setLocation(p);
- }
- });
- }
-
- private void setGridLayout(Composite parent, int spacing) {
- GridLayout gl = new GridLayout(1, false);
- gl.horizontalSpacing = 0;
- gl.verticalSpacing = 0;
- gl.marginHeight = spacing;
- gl.marginBottom = spacing;
- gl.marginLeft = spacing;
- gl.marginRight = spacing;
- gl.marginTop = spacing;
- gl.marginBottom = spacing;
- parent.setLayout(gl);
- }
-
- private void addGroup(Composite parent,
- String uiName,
- List<ElementDescriptor> descriptors) {
-
- Composite group = new Composite(parent, SWT.NONE);
- setGridLayout(group, 0);
-
- Toggle toggle = new Toggle(group, uiName);
-
- for (ElementDescriptor desc : descriptors) {
- Item item = new Item(group, desc);
- toggle.addItem(item);
- GridData gd = new GridData();
- item.setLayoutData(gd);
- }
- }
-
- /**
- * A Toggle widget is a row that is the head of a group.
- * <p/>
- * When clicked, the toggle will show/hide all the {@link Item} widgets that have been
- * added to it using {@link #addItem(Item)}.
- */
- private static class Toggle extends CLabel implements MouseTrackListener, MouseListener {
- private boolean mMouseIn;
- private DragSource mSource;
- private ArrayList<Item> mItems = new ArrayList<Item>();
-
- public Toggle(Composite parent, String groupName) {
- super(parent, SWT.NONE);
- mMouseIn = false;
-
- setData(null);
-
- String s = String.format("-= %s =-", groupName);
- setText(s);
- setToolTipText(s);
- //TODO use triangle icon and swap it -- setImage(desc.getIcon());
- addMouseTrackListener(this);
- addMouseListener(this);
- }
-
- public void addItem(Item item) {
- mItems.add(item);
- }
-
- @Override
- public void dispose() {
- if (mSource != null) {
- mSource.dispose();
- mSource = null;
- }
- super.dispose();
- }
-
- @Override
- public int getStyle() {
- int style = super.getStyle();
- if (mMouseIn) {
- style |= SWT.SHADOW_IN;
- }
- return style;
- }
-
- // -- MouseTrackListener callbacks
-
- public void mouseEnter(MouseEvent e) {
- if (!mMouseIn) {
- mMouseIn = true;
- redraw();
- }
- }
-
- public void mouseExit(MouseEvent e) {
- if (mMouseIn) {
- mMouseIn = false;
- redraw();
- }
- }
-
- public void mouseHover(MouseEvent e) {
- // pass
- }
-
- // -- MouseListener callbacks
-
- public void mouseDoubleClick(MouseEvent arg0) {
- // pass
- }
-
- public void mouseDown(MouseEvent arg0) {
- // pass
- }
-
- public void mouseUp(MouseEvent arg0) {
- for (Item i : mItems) {
- if (i.isVisible()) {
- Object ld = i.getLayoutData();
- if (ld instanceof GridData) {
- GridData gd = (GridData) ld;
-
- i.setData(gd.heightHint != SWT.DEFAULT ?
- Integer.valueOf(gd.heightHint) :
- null);
- gd.heightHint = 0;
- }
- } else {
- Object ld = i.getLayoutData();
- if (ld instanceof GridData) {
- GridData gd = (GridData) ld;
-
- Object d = i.getData();
- if (d instanceof Integer) {
- gd.heightHint = ((Integer) d).intValue();
- } else {
- gd.heightHint = SWT.DEFAULT;
- }
- }
- }
- i.setVisible(!i.isVisible());
- }
-
- getParent().getParent().layout(true /*changed*/);
- }
- }
-
- /**
- * An Item widget represents one {@link ElementDescriptor} that can be dropped on the
- * GLE2 canvas using drag'n'drop.
- */
- private static class Item extends CLabel implements MouseTrackListener {
-
- private boolean mMouseIn;
- private DragSource mSource;
-
- public Item(Composite parent, ElementDescriptor desc) {
- super(parent, SWT.NONE);
- mMouseIn = false;
-
- setText(desc.getUiName());
- setImage(desc.getIcon());
- setToolTipText(desc.getTooltip());
- addMouseTrackListener(this);
-
- // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html
- mSource = new DragSource(this, DND.DROP_COPY);
- mSource.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() });
- mSource.addDragListener(new DescDragSourceListener(desc));
- }
-
- @Override
- public void dispose() {
- if (mSource != null) {
- mSource.dispose();
- mSource = null;
- }
- super.dispose();
- }
-
- @Override
- public int getStyle() {
- int style = super.getStyle();
- if (mMouseIn) {
- style |= SWT.SHADOW_IN;
- }
- return style;
- }
-
- public void mouseEnter(MouseEvent e) {
- if (!mMouseIn) {
- mMouseIn = true;
- redraw();
- }
- }
-
- public void mouseExit(MouseEvent e) {
- if (mMouseIn) {
- mMouseIn = false;
- redraw();
- }
- }
-
- public void mouseHover(MouseEvent e) {
- // pass
- }
- }
-
- /**
- * A {@link DragSourceListener} that deals with drag'n'drop of
- * {@link ElementDescriptor}s.
- */
- private static class DescDragSourceListener implements DragSourceListener {
-
- private final ElementDescriptor mDesc;
-
- public DescDragSourceListener(ElementDescriptor desc) {
- mDesc = desc;
- }
-
- public void dragStart(DragSourceEvent e) {
- if (mDesc == null) {
- e.doit = false;
- }
- }
-
-
- public void dragSetData(DragSourceEvent e) {
- // Provide the data for the drop when requested by the other side.
- if (ElementDescTransfer.getInstance().isSupportedType(e.dataType)) {
- e.data = mDesc;
- }
- }
-
- public void dragFinished(DragSourceEvent e) {
- // Nothing to do here.
- }
- }
-
-}
+/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.adt.internal.editors.layout.gle2; + +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DragSource; +import org.eclipse.swt.dnd.DragSourceEvent; +import org.eclipse.swt.dnd.DragSourceListener; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; + +import java.util.ArrayList; +import java.util.List; + +/** + * A palette composite for the {@link GraphicalEditorPart}. + * <p/> + * The palette contains several groups, each with a UI name (e.g. layouts and views) and each + * with a list of element descriptors. + * <p/> + * + * @since GLE2 + * + * TODO list: + * - The available items should depend on the actual GLE2 Canvas selection. Selected android + * views should force filtering on what they accept can be dropped on them (e.g. TabHost, + * TableLayout). Should enable/disable them, not hide them, to avoid shuffling around. + * - Optional: a text filter + * - Optional: have icons that depict the element and/or automatically rendered icons + * based on a rendering of the widget. + * - Optional: have context-sensitive tools items, e.g. selection arrow tool, + * group selection tool, alignment, etc. + * - Different view strategies: big icon, small icons, text vs no text, compact grid. + * - This would only be useful with meaningful icons. Out current 1-letter icons are not enough + * to get rid of text labels. + */ +public class PaletteComposite extends Composite { + + + /** The parent grid layout that contains all the {@link Toggle} and {@link Item} widgets. */ + private Composite mRoot; + + /** + * Create the composite. + * @param parent The parent composite. + */ + public PaletteComposite(Composite parent) { + super(parent, SWT.BORDER | SWT.V_SCROLL); + } + + @Override + protected void checkSubclass() { + // Disable the check that prevents subclassing of SWT components + } + + /** + * Loads or reloads the palette elements by using the layout and view descriptors from the + * given target data. + * + * @param targetData The target data that contains the descriptors. If null or empty, + * no groups will be created. + */ + public void reloadPalette(AndroidTargetData targetData) { + + for (Control c : getChildren()) { + c.dispose(); + } + + setGridLayout(this, 2); + + mRoot = new Composite(this, SWT.NONE); + setGridLayout(mRoot, 0); + + if (targetData != null) { + addGroup(mRoot, "Views", targetData.getLayoutDescriptors().getViewDescriptors()); + addGroup(mRoot, "Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors()); + } + + layout(true); + + final ScrollBar vbar = getVerticalBar(); + + vbar.setMaximum(getSize().y); + + for (Listener listener : vbar.getListeners(SWT.Selection)) { + vbar.removeListener(SWT.Selection, listener); + } + + vbar.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + Point p = mRoot.getLocation(); + p.y = - vbar.getSelection(); + mRoot.setLocation(p); + } + }); + } + + private void setGridLayout(Composite parent, int spacing) { + GridLayout gl = new GridLayout(1, false); + gl.horizontalSpacing = 0; + gl.verticalSpacing = 0; + gl.marginHeight = spacing; + gl.marginBottom = spacing; + gl.marginLeft = spacing; + gl.marginRight = spacing; + gl.marginTop = spacing; + gl.marginBottom = spacing; + parent.setLayout(gl); + } + + private void addGroup(Composite parent, + String uiName, + List<ElementDescriptor> descriptors) { + + Composite group = new Composite(parent, SWT.NONE); + setGridLayout(group, 0); + + Toggle toggle = new Toggle(group, uiName); + + for (ElementDescriptor desc : descriptors) { + Item item = new Item(group, desc); + toggle.addItem(item); + GridData gd = new GridData(); + item.setLayoutData(gd); + } + } + + /** + * A Toggle widget is a row that is the head of a group. + * <p/> + * When clicked, the toggle will show/hide all the {@link Item} widgets that have been + * added to it using {@link #addItem(Item)}. + */ + private static class Toggle extends CLabel implements MouseTrackListener, MouseListener { + private boolean mMouseIn; + private DragSource mSource; + private ArrayList<Item> mItems = new ArrayList<Item>(); + + public Toggle(Composite parent, String groupName) { + super(parent, SWT.NONE); + mMouseIn = false; + + setData(null); + + String s = String.format("-= %s =-", groupName); + setText(s); + setToolTipText(s); + //TODO use triangle icon and swap it -- setImage(desc.getIcon()); + addMouseTrackListener(this); + addMouseListener(this); + } + + public void addItem(Item item) { + mItems.add(item); + } + + @Override + public void dispose() { + if (mSource != null) { + mSource.dispose(); + mSource = null; + } + super.dispose(); + } + + @Override + public int getStyle() { + int style = super.getStyle(); + if (mMouseIn) { + style |= SWT.SHADOW_IN; + } + return style; + } + + // -- MouseTrackListener callbacks + + public void mouseEnter(MouseEvent e) { + if (!mMouseIn) { + mMouseIn = true; + redraw(); + } + } + + public void mouseExit(MouseEvent e) { + if (mMouseIn) { + mMouseIn = false; + redraw(); + } + } + + public void mouseHover(MouseEvent e) { + // pass + } + + // -- MouseListener callbacks + + public void mouseDoubleClick(MouseEvent arg0) { + // pass + } + + public void mouseDown(MouseEvent arg0) { + // pass + } + + public void mouseUp(MouseEvent arg0) { + for (Item i : mItems) { + if (i.isVisible()) { + Object ld = i.getLayoutData(); + if (ld instanceof GridData) { + GridData gd = (GridData) ld; + + i.setData(gd.heightHint != SWT.DEFAULT ? + Integer.valueOf(gd.heightHint) : + null); + gd.heightHint = 0; + } + } else { + Object ld = i.getLayoutData(); + if (ld instanceof GridData) { + GridData gd = (GridData) ld; + + Object d = i.getData(); + if (d instanceof Integer) { + gd.heightHint = ((Integer) d).intValue(); + } else { + gd.heightHint = SWT.DEFAULT; + } + } + } + i.setVisible(!i.isVisible()); + } + + getParent().getParent().layout(true /*changed*/); + } + } + + /** + * An Item widget represents one {@link ElementDescriptor} that can be dropped on the + * GLE2 canvas using drag'n'drop. + */ + private static class Item extends CLabel implements MouseTrackListener { + + private boolean mMouseIn; + private DragSource mSource; + + public Item(Composite parent, ElementDescriptor desc) { + super(parent, SWT.NONE); + mMouseIn = false; + + setText(desc.getUiName()); + setImage(desc.getIcon()); + setToolTipText(desc.getTooltip()); + addMouseTrackListener(this); + + // DND Reference: http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html + mSource = new DragSource(this, DND.DROP_COPY); + mSource.setTransfer(new Transfer[] { ElementDescTransfer.getInstance() }); + mSource.addDragListener(new DescDragSourceListener(desc)); + } + + @Override + public void dispose() { + if (mSource != null) { + mSource.dispose(); + mSource = null; + } + super.dispose(); + } + + @Override + public int getStyle() { + int style = super.getStyle(); + if (mMouseIn) { + style |= SWT.SHADOW_IN; + } + return style; + } + + public void mouseEnter(MouseEvent e) { + if (!mMouseIn) { + mMouseIn = true; + redraw(); + } + } + + public void mouseExit(MouseEvent e) { + if (mMouseIn) { + mMouseIn = false; + redraw(); + } + } + + public void mouseHover(MouseEvent e) { + // pass + } + } + + /** + * A {@link DragSourceListener} that deals with drag'n'drop of + * {@link ElementDescriptor}s. + */ + private static class DescDragSourceListener implements DragSourceListener { + + private final ElementDescriptor mDesc; + + public DescDragSourceListener(ElementDescriptor desc) { + mDesc = desc; + } + + public void dragStart(DragSourceEvent e) { + if (mDesc == null) { + e.doit = false; + } + } + + + public void dragSetData(DragSourceEvent e) { + // Provide the data for the drop when requested by the other side. + if (ElementDescTransfer.getInstance().isSupportedType(e.dataType)) { + e.data = mDesc; + } + } + + public void dragFinished(DragSourceEvent e) { + // Nothing to do here. + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java index d639fb9..fd0f475 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java @@ -17,8 +17,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.gscripts.INodeProxy; -import com.android.ide.eclipse.adt.gscripts.Rect; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; import com.android.ide.eclipse.adt.internal.editors.AndroidEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java index 48986b6..0149096 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java @@ -18,10 +18,10 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; -import com.android.ide.eclipse.adt.gscripts.DropZone; -import com.android.ide.eclipse.adt.gscripts.INodeProxy; -import com.android.ide.eclipse.adt.gscripts.IViewRule; -import com.android.ide.eclipse.adt.gscripts.Point; +import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INodeProxy; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; |