ADT: GraphicalEditorPart is the new GLE2.
This CL represents the base for the new "Graphical Editor Part".
First, GLE2 has been renamed in GraphicalEditorPart. That's the
final name, I swear I won't change it again (until next month that is.)
The editor part has 3 composites: the top ConfigConfiguration
(same as usual, reused as-is), a new PaletteComposite and a new
LayoutCanavas. This last one displays the rendering image and
will deal with interactivity.
The LayoutCanvas is actually stacked with a label which displays
the rendering error. After a rendering, either the error or the
canvas is visible, depending on the success of the operation.
That would make it easier to have a different mechanism, for example
the error could be next to the last known rendering, they don't
have to be mutually exclusive.
It is worth noting that GraphicalEditorPart is 95% similar to
the GLE1, reusing all the glue code that we had to handle
layout requestes, refresh requests, sdk/framework load listeners,
configuration and file input changes, etc.
Both PaletteComposite and LayoutCanvas are currently embryonic
at best, just to make sure the editor part is structured correctly.
Change-Id: I36c2ae4d85a68e68a349adc63a718f06375e12c5
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index 4aa3a80..f17b3c8 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -244,6 +244,7 @@
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
*/
+ @SuppressWarnings("deprecation")
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java
deleted file mode 100755
index e905990..0000000
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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;
-
-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 org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.gef.ui.parts.SelectionSynchronizer;
-import org.eclipse.swt.dnd.Clipboard;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.ui.IEditorInput;
-import org.eclipse.ui.IEditorSite;
-import org.eclipse.ui.PartInitException;
-import org.eclipse.ui.part.EditorPart;
-
-/**
- * Graphical layout editor part, version 2.
- *
- * @since GLE2
- */
-public class GLE2 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;
-
- public GLE2(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);
- setInput(input);
- }
-
- @Override
- public void dispose() {
- super.dispose();
- }
-
- @Override
- public void doSave(IProgressMonitor monitor) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void doSaveAs() {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean isDirty() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isSaveAsAllowed() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void createPartControl(Composite parent) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setFocus() {
- // TODO Auto-generated method stub
-
- }
-
- public void activated() {
- // TODO Auto-generated method stub
-
- }
-
- public void deactivated() {
- // TODO Auto-generated method stub
-
- }
-
- public void editNewFile(FolderConfiguration configuration) {
- // TODO Auto-generated method stub
-
- }
-
- public Clipboard getClipboard() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public LayoutEditor getLayoutEditor() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public UiDocumentNode getModel() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public SelectionSynchronizer getSelectionSynchronizer() {
- // TODO Auto-generated method stub
- return null;
- }
-
- public void onXmlModelChanged() {
- // TODO Auto-generated method stub
-
- }
-
- public void recomputeLayout() {
- // TODO Auto-generated method stub
-
- }
-
- public void reloadEditor() {
- // TODO Auto-generated method stub
-
- }
-
- public void reloadPalette() {
- // TODO Auto-generated method stub
-
- }
-
- public void selectModel(UiElementNode uiNodeModel) {
- // TODO Auto-generated method stub
-
- }
-
- public void reloadLayout(boolean codeChange, boolean rChange,
- boolean resChange) {
- // TODO Auto-generated method stub
-
- }
-
-}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
new file mode 100755
index 0000000..45a42e2
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java
@@ -0,0 +1,1078 @@
+/*
+ * 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;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+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.ConfigurationComposite.IConfigListener;
+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.configurations.PixelDensityQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density;
+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.layoutlib.api.ILayoutResult.ILayoutViewInfo;
+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.dialogs.Dialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.custom.StackLayout;
+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.Label;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.PartInitException;
+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.List;
+import java.util.Map;
+
+/**
+ * Graphical layout editor part, version 2.
+ *
+ * @since GLE2
+ *
+ * TODO List:
+ * - 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. */
+ 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 mSash;
+
+ /** The palette displayed on the left of the sash. */
+ private PaletteComposite mPalette;
+
+ /** The layout canvas displayed o the right of the sash. */
+ private LayoutCanvas mLayoutCanvas;
+
+ /** The {@link FolderConfiguration} being edited. */
+ private FolderConfiguration mEditedConfig;
+
+ 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 Composite mCanvasOrErrorStack;
+
+ private StackLayout mCanvasOrErrorStackLayout;
+
+ private Label mErrorLabel;
+
+ private ReloadListener mReloadListener;
+
+ 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);
+ }
+
+ if (mReloadListener == null) {
+ mReloadListener = new ReloadListener();
+ LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);
+ }
+ }
+
+ /**
+ * Reloads this editor, by getting the new model from the {@link LayoutEditor}.
+ */
+ public void reloadEditor() {
+ IEditorInput input = mLayoutEditor.getEditorInput();
+
+ try {
+ useNewEditorInput(input);
+ } catch (PartInitException e) {
+ // really this shouldn't happen! Log it in case it happens.
+ mEditedFile = null;
+ AdtPlugin.log(e, "Input is not of type FileEditorInput: %1$s", //$NON-NLS-1$
+ input == null ? "null" : input.toString()); //$NON-NLS-1$
+ }
+ }
+
+ 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$
+ }
+
+ FileEditorInput fileInput = (FileEditorInput)input;
+ mEditedFile = fileInput.getFile();
+ }
+
+ @Override
+ public void createPartControl(Composite parent) {
+
+ mClipboard = new Clipboard(parent.getDisplay());
+
+ GridLayout gl = new GridLayout(1, false);
+ parent.setLayout(gl);
+ gl.marginHeight = gl.marginWidth = 0;
+
+ // create the top part for the configuration control
+ mConfigListener = new ConfigListener();
+ mConfigComposite = new ConfigurationComposite(mConfigListener, parent, SWT.BORDER);
+ mConfigComposite.updateUIFromResources();
+
+ mSash = new SashForm(parent, SWT.HORIZONTAL);
+ mSash.setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ mPalette = new PaletteComposite(mSash);
+
+ mCanvasOrErrorStack = new Composite(mSash, SWT.NONE);
+ mCanvasOrErrorStackLayout = new StackLayout();
+ mCanvasOrErrorStack.setLayout(mCanvasOrErrorStackLayout);
+
+ mLayoutCanvas = new LayoutCanvas(mCanvasOrErrorStack);
+ mErrorLabel = new Label(mCanvasOrErrorStack, SWT.NONE);
+ mCanvasOrErrorStackLayout.topControl = mLayoutCanvas;
+
+ mSash.setWeights(new int[] { 20, 80 });
+
+ // Initialize the state
+ reloadPalette();
+ }
+
+ /** Switches the stack to display the canvas and hide the error label. */
+ private void displayCanvas() {
+ if (mCanvasOrErrorStackLayout.topControl != mLayoutCanvas) {
+ mCanvasOrErrorStackLayout.topControl = mLayoutCanvas;
+ mCanvasOrErrorStack.layout();
+ }
+ }
+
+ /**
+ * 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));
+ }
+ if (mCanvasOrErrorStackLayout.topControl != mErrorLabel) {
+ mCanvasOrErrorStackLayout.topControl = mErrorLabel;
+ mCanvasOrErrorStack.layout();
+ }
+ }
+
+ @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();
+ }
+
+ 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 || mEditedConfig == null) {
+ return;
+ }
+
+ // 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 {
+ 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.
+
+ // update the configuration icons with the new edited config.
+ setConfiguration(mEditedConfig, false /*force*/);
+
+ // enable the create button if the current and edited config are not equals
+ mConfigComposite.setEnabledCreate(
+ mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
+
+ // Even though the layout doesn't change, the config changed, and referenced
+ // resources need to be updated.
+ recomputeLayout();
+ } else {
+ // enable the Create button
+ mConfigComposite.setEnabledCreate(true);
+
+ // 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 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.setConfig(config);
+ 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();
+ }
+ }
+
+ private class TargetListener implements ITargetChangeListener {
+
+ public void onProjectTargetChange(IProject changedProject) {
+ if (changedProject == getLayoutEditor().getProject()) {
+ onTargetsLoaded();
+ }
+ }
+
+ public void onTargetsLoaded() {
+ // because the SDK changed we must reset the configured framework resource.
+ mConfiguredFrameworkRes = null;
+
+ mConfigComposite.updateUIFromResources();
+
+ // updateUiFromFramework will reset language/region combo, so we must call
+ // setConfiguration after, or the settext on language/region will be lost.
+ if (mEditedConfig != null) {
+ setConfiguration(mEditedConfig, false /*force*/);
+ }
+
+ // make sure we remove the custom view loader, since its parent class loader is the
+ // bridge class loader.
+ mProjectCallback = null;
+
+ recomputeLayout();
+ }
+ }
+
+ /**
+ * Update the UI controls state with a given {@link FolderConfiguration}.
+ * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
+ * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
+ * the UI control is not modified. However if the value in the control is not the default value,
+ * a warning icon is shown.
+ * @param config The {@link FolderConfiguration} to set.
+ * @param force Whether the UI should be changed to exactly match the received configuration.
+ */
+ void setConfiguration(FolderConfiguration config, boolean force) {
+ mEditedConfig = config;
+ mConfiguredFrameworkRes = mConfiguredProjectRes = null;
+
+ mConfigComposite.setConfiguration(config, force);
+
+ }
+
+ // ----------------
+
+ /**
+ * 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.
+ }
+
+ /**
+ * Sets the UI for the edition of a new file.
+ * @param configuration the configuration of the new file.
+ */
+ public void editNewFile(FolderConfiguration configuration) {
+ // update the configuration UI
+ setConfiguration(configuration, true /*force*/);
+
+ // enable the create button if the current and edited config are not equals
+ mConfigComposite.setEnabledCreate(
+ mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
+ }
+
+ 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.
+ if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) {
+ displayError("The project target (%s) was not properly loaded.",
+ target.getName());
+ }
+ 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
+ UiElementPullParser parser = new UiElementPullParser(getModel());
+ Rectangle rect = getBounds();
+ boolean isProjectTheme = mConfigComposite.isProjectTheme();
+
+ // FIXME pass the density/dpi from somewhere (resource config or skin).
+ // For now, get it from the config
+ int density = Density.MEDIUM.getDpiValue();
+ PixelDensityQualifier qual =
+ mConfigComposite.getCurrentConfig().getPixelDensityQualifier();
+ if (qual != null) {
+ int d = qual.getValue().getDpiValue();
+ if (d > 0) {
+ density = d;
+ }
+ }
+
+ ILayoutResult result = computeLayout(bridge, parser,
+ iProject /* projectKey */,
+ rect.width, rect.height, density, density, density,
+ theme, isProjectTheme,
+ configuredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
+
+ // update the UiElementNode with the layout info.
+ if (result.getSuccess() == ILayoutResult.SUCCESS) {
+
+ // Update the image and make sure we're displaying the canvas.
+ mLayoutCanvas.setImage(result.getImage());
+ displayCanvas();
+
+ updateNodeWithBounds(result.getRootView());
+ } else {
+ displayError(result.getErrorMessage());
+
+ // Reset the edit data for all the nodes.
+ resetNodeBounds(model);
+ }
+
+ 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.
+ */
+ @SuppressWarnings("deprecation")
+ private static ILayoutResult computeLayout(LayoutBridge bridge,
+ IXmlPullParser layoutDescription, Object projectKey,
+ int screenWidth, int screenHeight, 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, false /* renderFullHeight */,
+ 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();
+ }
+
+ private void resetNodeBounds(UiElementNode node) {
+ node.setEditData(null);
+
+ List<UiElementNode> children = node.getUiChildren();
+ for (UiElementNode child : children) {
+ resetNodeBounds(child);
+ }
+ }
+
+ private void updateNodeWithBounds(ILayoutViewInfo r) {
+ if (r != null) {
+ // update the node itself, as the viewKey is the XML node in this implementation.
+ Object viewKey = r.getViewKey();
+ if (viewKey instanceof UiElementNode) {
+ Rectangle bounds = new Rectangle(r.getLeft(), r.getTop(),
+ r.getRight()-r.getLeft(), r.getBottom() - r.getTop());
+
+ ((UiElementNode)viewKey).setEditData(bounds);
+ }
+
+ // and then its children.
+ ILayoutViewInfo[] children = r.getChildren();
+ if (children != null) {
+ for (ILayoutViewInfo child : children) {
+ updateNodeWithBounds(child);
+ }
+ }
+ }
+ }
+
+ 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 {
+ /*
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener#reloadLayout(boolean, boolean, boolean)
+ *
+ * Called when the file changes triggered a redraw of the layout
+ */
+ public void reloadLayout(boolean codeChange, boolean rChange, boolean resChange) {
+ boolean recompute = rChange;
+
+ if (resChange) {
+ 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());
+ }
+ }
+ }
+
+ mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ mConfigComposite.updateUIFromResources();
+ }
+ });
+ }
+
+ if (codeChange) {
+ // 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/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
index 45810af..c70be8d 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
@@ -19,6 +19,7 @@
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor.UiEditorActions;
+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.ConfigurationComposite.IConfigListener;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
@@ -115,7 +116,7 @@
* @since GLE1
*/
public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
- implements IGraphicalLayoutEditor, IConfigListener {
+ implements IGraphicalLayoutEditor, IConfigListener, ILayoutReloadListener {
/** Reference to the layout editor */
@@ -388,7 +389,7 @@
/**
* Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
- * created by {@link ElementCreateCommand#execute()}.
+ * created by {@link ElementCreateCommand#execute()}.
*
* @param uiNodeModel The {@link UiElementNode} to select.
*/
@@ -814,7 +815,6 @@
/**
* Recomputes the layout with the help of layoutlib.
*/
- @SuppressWarnings("deprecation")
public void recomputeLayout() {
doXmlReload(false /* force */);
try {
@@ -1339,4 +1339,5 @@
}
}
+
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
index 6414b20..4c9c714 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java
@@ -16,7 +16,6 @@
package com.android.ide.eclipse.adt.internal.editors.layout;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
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;
@@ -31,7 +30,7 @@
*
* @since GLE2
*/
-/*package*/ interface IGraphicalLayoutEditor extends IEditorPart, ILayoutReloadListener {
+/*package*/ interface IGraphicalLayoutEditor extends IEditorPart {
/**
* Sets the UI for the edition of a new file.
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
new file mode 100755
index 0000000..976554c
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+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.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Displays the image rendered by the {@link GraphicalEditorPart} and handles
+ * the interaction with the widgets.
+ * <p/>
+ *
+ * @since GLE2
+ *
+ * TODO list:
+ * - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)
+ * - handle selection (will need the model, aka the root node)/
+ * - 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)/
+ * - preserve selection during editor input change if applicable (e.g. when changing configuration.)
+ */
+public class LayoutCanvas extends Canvas {
+
+ private Image mImage;
+
+ public LayoutCanvas(Composite parent) {
+ super(parent, SWT.BORDER);
+
+ addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent e) {
+ paint(e);
+ }
+ });
+ }
+
+ public void setImage(BufferedImage awtImage) {
+ // Convert the AWT image into an SWT image.
+ 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);
+
+ redraw();
+ }
+
+ private void paint(PaintEvent e) {
+ if (mImage != null) {
+ GC gc = e.gc;
+ gc.drawImage(mImage, 0, 0);
+ }
+ }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
index 1176253..caa9658 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
@@ -132,7 +132,7 @@
if (mGraphicalEditor == null) {
if (System.getenv("USE_GLE2") != null) { //$NON-NLS-1$ //$NON-NLS-2$
- mGraphicalEditor = new GLE2(this);
+ mGraphicalEditor = new GraphicalEditorPart(this);
} else {
mGraphicalEditor = new GraphicalLayoutEditor(this);
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
index d9e81f5..507cb21 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -39,16 +40,16 @@
* Monitor for file changes triggering a layout redraw.
*/
public final class LayoutReloadMonitor implements IFileListener, IResourceEventListener {
-
+
// singleton, enforced by private constructor.
private final static LayoutReloadMonitor sThis = new LayoutReloadMonitor();
-
+
/**
* Map of listeners by IProject.
*/
private final Map<IProject, List<ILayoutReloadListener>> mListenerMap =
new HashMap<IProject, List<ILayoutReloadListener>>();
-
+
private final static int CHANGE_CODE = 0;
private final static int CHANGE_RESOURCES = 1;
private final static int CHANGE_R = 2;
@@ -60,7 +61,7 @@
* <li>CHANGE_R: R clas change flag</li></ul>
*/
private final Map<IProject, boolean[]> mChangedProjects = new HashMap<IProject, boolean[]>();
-
+
/**
* Classes which implement this interface provide a method to respond to resource changes
* triggering a layout redraw
@@ -72,22 +73,22 @@
* @param rChange The trigger happened due to a change in the R class.
* @param resChange The trigger happened due to a resource change.
*/
- void reloadLayout(boolean codeChange, boolean rChange, boolean resChange);
+ void reloadLayout(boolean codeChange, boolean rChange, boolean resChange);
}
-
+
/**
* Returns the single instance of {@link LayoutReloadMonitor}.
*/
public static LayoutReloadMonitor getMonitor() {
return sThis;
}
-
+
private LayoutReloadMonitor() {
ResourceMonitor monitor = ResourceMonitor.getMonitor();
monitor.addFileListener(this, IResourceDelta.ADDED | IResourceDelta.CHANGED);
monitor.addResourceEventListener(this);
}
-
+
/**
* Adds a listener for a given {@link IProject}.
* @param project
@@ -100,15 +101,13 @@
list = new ArrayList<ILayoutReloadListener>();
mListenerMap.put(project, list);
}
-
+
list.add(listener);
}
}
-
+
/**
* Removes a listener for a given {@link IProject}.
- * @param project
- * @param listener
*/
public void removeListener(IProject project, ILayoutReloadListener listener) {
synchronized (mListenerMap) {
@@ -119,10 +118,28 @@
}
}
+ /**
+ * Removes a listener, no matter which {@link IProject} it was associated with.
+ */
+ public void removeListener(ILayoutReloadListener listener) {
+ synchronized (mListenerMap) {
+
+ for (List<ILayoutReloadListener> list : mListenerMap.values()) {
+ Iterator<ILayoutReloadListener> it = list.iterator();
+ while (it.hasNext()) {
+ ILayoutReloadListener i = it.next();
+ if (i == listener) {
+ it.remove();
+ }
+ }
+ }
+ }
+ }
+
/*
* (non-Javadoc)
* @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener#fileChanged(org.eclipse.core.resources.IFile, org.eclipse.core.resources.IMarkerDelta[], int)
- *
+ *
* Callback for ResourceMonitor.IFileListener. Called when a file changed.
* This records the changes for each project, but does not notify listeners.
* @see #resourceChangeEventEnd
@@ -137,7 +154,7 @@
changeFlags[CHANGE_R]) {
return;
}
-
+
// now check that the file is *NOT* a layout file (those automatically trigger a layout
// reload and we don't want to do it twice.
ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
@@ -148,7 +165,7 @@
changeFlags = new boolean[CHANGE_COUNT];
mChangedProjects.put(project, changeFlags);
}
-
+
changeFlags[CHANGE_RESOURCES] = true;
}
} else if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) {
@@ -171,14 +188,14 @@
}
}
}
-
+
/*
* (non-Javadoc)
* @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventStart()
- *
+ *
* Callback for ResourceMonitor.IResourceEventListener. Called at the beginning of a resource
* change event. This is called once, while fileChanged can be called several times.
- *
+ *
*/
public void resourceChangeEventStart() {
// nothing to be done here, it all happens in the resourceChangeEventEnd
@@ -187,7 +204,7 @@
/*
* (non-Javadoc)
* @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventEnd()
- *
+ *
* Callback for ResourceMonitor.IResourceEventListener. Called at the end of a resource
* change event. This is where we notify the listeners.
*/
@@ -196,9 +213,9 @@
synchronized (mListenerMap) {
for (Entry<IProject, boolean[]> project : mChangedProjects.entrySet()) {
List<ILayoutReloadListener> listeners = mListenerMap.get(project.getKey());
-
+
boolean[] flags = project.getValue();
-
+
if (listeners != null) {
for (ILayoutReloadListener listener : listeners) {
listener.reloadLayout(flags[CHANGE_CODE], flags[CHANGE_R],
@@ -207,7 +224,7 @@
}
}
}
-
+
// empty the list.
mChangedProjects.clear();
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java
new file mode 100755
index 0000000..27398e9
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+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.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+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:
+ * - *Mandatory* for a first release:
+ * - Currently this displays elements as buttons. Eventually this needs to either be replaced
+ * by custom drawing right in here or we need to use a custom control.
+ * - Needs to be able to originate drag'n'drop from these controls onto the GEP.
+ * - Scroll the list.
+ * - For later releases:
+ * - Ability to collapse palettes or dockable palettes.
+ * - 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.
+ * - Would be nice to have context-sensitive tools items, e.g. selection arrow tool,
+ * group selection tool, alignment, etc.
+ */
+public class PaletteComposite extends Composite {
+
+ /**
+ * 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
+ }
+
+ /**
+ * Load or reload the palette elements by using the layour 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();
+ }
+
+ if (targetData != null) {
+ GridLayout gl = new GridLayout(1, false);
+ gl.horizontalSpacing = 0;
+ gl.verticalSpacing = 0;
+ gl.marginHeight = 2;
+ gl.marginBottom = 2;
+ gl.marginLeft = 2;
+ gl.marginRight = 2;
+ gl.marginTop = 2;
+ gl.marginBottom = 2;
+ setLayout(gl);
+
+ /* STOPSHIP */
+ Label l = new Label(this, SWT.NONE);
+ l.setText("*** PLACEHOLDER ***"); //$NON-NLS-1$
+ l.setToolTipText("Temporary mock for the palette. Needs to scroll, needs no buttons, needs to drag'n'drop."); //$NON-NLS-1$
+
+ addGroup("Layouts", targetData.getLayoutDescriptors().getLayoutDescriptors());
+ addGroup("Views", targetData.getLayoutDescriptors().getViewDescriptors());
+ }
+
+ layout(true);
+ }
+
+ private void addGroup(String uiName, List<ElementDescriptor> descriptors) {
+ Label label = new Label(this, SWT.NONE);
+ label.setText(uiName);
+
+ for (ElementDescriptor desc : descriptors) {
+ Button b = new Button(this, SWT.PUSH);
+ b.setText(desc.getUiName());
+ b.setImage(desc.getIcon());
+ b.setToolTipText(desc.getTooltip());
+ b.setData(desc);
+ }
+ }
+}