From 54ac27c96fb439b4d61e3182f3ef998ac1198515 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 26 Aug 2009 23:28:05 -0700 Subject: [PATCH] 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 --- .../src/com/android/ide/eclipse/adt/AdtPlugin.java | 1 + .../eclipse/adt/internal/editors/layout/GLE2.java | 174 ---- .../editors/layout/GraphicalEditorPart.java | 1078 ++++++++++++++++++++ .../editors/layout/GraphicalLayoutEditor.java | 7 +- .../editors/layout/IGraphicalLayoutEditor.java | 3 +- .../adt/internal/editors/layout/LayoutCanvas.java | 88 ++ .../adt/internal/editors/layout/LayoutEditor.java | 2 +- .../editors/layout/LayoutReloadMonitor.java | 61 +- .../internal/editors/layout/PaletteComposite.java | 118 +++ 9 files changed, 1330 insertions(+), 202 deletions(-) delete mode 100755 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java create mode 100755 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java create mode 100755 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java create mode 100755 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 4aa3a80a2..f17b3c82b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -244,6 +244,7 @@ public class AdtPlugin extends AbstractUIPlugin { * (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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GLE2.java deleted file mode 100755 index e9059904a..000000000 --- a/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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java new file mode 100755 index 000000000..45a42e283 --- /dev/null +++ b/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> mConfiguredFrameworkRes; + private Map> 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. + *

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> 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> 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}. + *

If force is set to true the UI will be changed to exactly reflect + * config, otherwise, if a qualifier is not present in config, + * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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> configuredProjectRes = + mConfigListener.getConfiguredProjectResources(); + + // get the framework resources + Map> 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> projectResources, + Map> 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 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java index 45810aff9..c70be8dc4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout; 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 @@ import java.util.Map; * @since GLE1 */ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette - implements IGraphicalLayoutEditor, IConfigListener { + implements IGraphicalLayoutEditor, IConfigListener, ILayoutReloadListener { /** Reference to the layout editor */ @@ -388,7 +389,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette /** * 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 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette /** * Recomputes the layout with the help of layoutlib. */ - @SuppressWarnings("deprecation") public void recomputeLayout() { doXmlReload(false /* force */); try { @@ -1339,4 +1339,5 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette } } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java index 6414b200d..4c9c7142a 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/IGraphicalLayoutEditor.java +++ b/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 @@ import org.eclipse.ui.IEditorPart; * * @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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCanvas.java new file mode 100755 index 000000000..976554c71 --- /dev/null +++ b/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. + *

+ * + * @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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java index 117625390..caa9658d6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java @@ -132,7 +132,7 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa 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/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java index d9e81f57b..507cb211c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java @@ -31,6 +31,7 @@ import org.eclipse.core.resources.IResourceDelta; 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 @@ import java.util.Map.Entry; * 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> mListenerMap = new HashMap>(); - + 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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL *

  • CHANGE_R: R clas change flag
  • */ private final Map mChangedProjects = new HashMap(); - + /** * Classes which implement this interface provide a method to respond to resource changes * triggering a layout redraw @@ -72,22 +73,22 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL * @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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL list = new ArrayList(); 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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL } } + /** + * Removes a listener, no matter which {@link IProject} it was associated with. + */ + public void removeListener(ILayoutReloadListener listener) { + synchronized (mListenerMap) { + + for (List list : mListenerMap.values()) { + Iterator 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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL 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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL 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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL } } } - + /* * (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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL /* * (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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL synchronized (mListenerMap) { for (Entry project : mChangedProjects.entrySet()) { List 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 @@ public final class LayoutReloadMonitor implements IFileListener, IResourceEventL } } } - + // empty the list. mChangedProjects.clear(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/PaletteComposite.java new file mode 100755 index 000000000..27398e96a --- /dev/null +++ b/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}. + *

    + * The palette contains several groups, each with a UI name (e.g. layouts and views) and each + * with a list of element descriptors. + *

    + * + * @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 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); + } + } +} -- 2.11.0