2 * Copyright (C) 2009 The Android Open Source Project
\r
4 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.eclipse.org/org/documents/epl-v10.php
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
\r
19 import com.android.ide.eclipse.adt.AdtPlugin;
\r
20 import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
\r
21 import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor;
\r
22 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
\r
23 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
\r
24 import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
\r
25 import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
\r
26 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
\r
27 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
\r
28 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
\r
29 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle;
\r
30 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
\r
31 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
\r
32 import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;
\r
33 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
\r
34 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
\r
35 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
\r
36 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
\r
37 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;
\r
38 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
\r
39 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
\r
40 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
\r
41 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
\r
42 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
\r
43 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
\r
44 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
\r
45 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
\r
46 import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
\r
47 import com.android.layoutlib.api.ILayoutBridge;
\r
48 import com.android.layoutlib.api.ILayoutLog;
\r
49 import com.android.layoutlib.api.ILayoutResult;
\r
50 import com.android.layoutlib.api.IProjectCallback;
\r
51 import com.android.layoutlib.api.IResourceValue;
\r
52 import com.android.layoutlib.api.IXmlPullParser;
\r
53 import com.android.sdklib.IAndroidTarget;
\r
55 import org.eclipse.core.resources.IFile;
\r
56 import org.eclipse.core.resources.IFolder;
\r
57 import org.eclipse.core.resources.IProject;
\r
58 import org.eclipse.core.resources.IResource;
\r
59 import org.eclipse.core.runtime.CoreException;
\r
60 import org.eclipse.core.runtime.IProgressMonitor;
\r
61 import org.eclipse.core.runtime.IStatus;
\r
62 import org.eclipse.core.runtime.Status;
\r
63 import org.eclipse.core.runtime.jobs.Job;
\r
64 import org.eclipse.draw2d.geometry.Rectangle;
\r
65 import org.eclipse.gef.ui.parts.SelectionSynchronizer;
\r
66 import org.eclipse.jface.action.Action;
\r
67 import org.eclipse.jface.dialogs.Dialog;
\r
68 import org.eclipse.swt.SWT;
\r
69 import org.eclipse.swt.custom.SashForm;
\r
70 import org.eclipse.swt.custom.StyledText;
\r
71 import org.eclipse.swt.dnd.Clipboard;
\r
72 import org.eclipse.swt.layout.GridData;
\r
73 import org.eclipse.swt.layout.GridLayout;
\r
74 import org.eclipse.swt.widgets.Composite;
\r
75 import org.eclipse.swt.widgets.Display;
\r
76 import org.eclipse.ui.IActionBars;
\r
77 import org.eclipse.ui.IEditorInput;
\r
78 import org.eclipse.ui.IEditorSite;
\r
79 import org.eclipse.ui.PartInitException;
\r
80 import org.eclipse.ui.actions.ActionFactory;
\r
81 import org.eclipse.ui.ide.IDE;
\r
82 import org.eclipse.ui.part.EditorPart;
\r
83 import org.eclipse.ui.part.FileEditorInput;
\r
85 import java.io.File;
\r
86 import java.io.FileOutputStream;
\r
87 import java.io.IOException;
\r
88 import java.io.InputStream;
\r
89 import java.io.PrintStream;
\r
90 import java.util.Map;
\r
93 * Graphical layout editor part, version 2.
\r
98 * - display error icon
\r
99 * - finish palette (see palette's todo list)
\r
100 * - finish canvas (see canva's todo list)
\r
101 * - completly rethink the property panel
\r
102 * - link to the existing outline editor (prolly reuse/adapt for simplier model and will need
\r
103 * to adapt the selection synchronizer.)
\r
105 public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutEditor {
\r
109 * To understand Drag'n'drop:
\r
110 * http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
\r
113 /** Reference to the layout editor */
\r
114 private final LayoutEditor mLayoutEditor;
\r
116 /** reference to the file being edited. Can also be used to access the {@link IProject}. */
\r
117 private IFile mEditedFile;
\r
119 /** The current clipboard. Must be disposed later. */
\r
120 private Clipboard mClipboard;
\r
122 /** The configuration composite at the top of the layout editor. */
\r
123 private ConfigurationComposite mConfigComposite;
\r
125 /** The sash that splits the palette from the canvas. */
\r
126 private SashForm mSashPalette;
\r
127 private SashForm mSashError;
\r
129 /** The palette displayed on the left of the sash. */
\r
130 private PaletteComposite mPalette;
\r
132 /** The layout canvas displayed to the right of the sash. */
\r
133 private LayoutCanvas mLayoutCanvas;
\r
135 /** The Groovy Rules Engine associated with this editor. It is project-specific. */
\r
136 private RulesEngine mRulesEngine;
\r
138 private StyledText mErrorLabel;
\r
140 /** The {@link FolderConfiguration} being edited. */
\r
141 private FolderConfiguration mEditedConfig;
\r
143 private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes;
\r
144 private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes;
\r
145 private ProjectCallback mProjectCallback;
\r
146 private ILayoutLog mLogger;
\r
148 private boolean mNeedsXmlReload = false;
\r
149 private boolean mNeedsRecompute = false;
\r
151 private TargetListener mTargetListener;
\r
153 private ConfigListener mConfigListener;
\r
155 private ReloadListener mReloadListener;
\r
157 protected boolean mUseExplodeMode;
\r
160 public GraphicalEditorPart(LayoutEditor layoutEditor) {
\r
161 mLayoutEditor = layoutEditor;
\r
162 setPartName("Graphical Layout");
\r
165 // ------------------------------------
\r
166 // Methods overridden from base classes
\r
167 //------------------------------------
\r
170 * Initializes the editor part with a site and input.
\r
174 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
\r
176 useNewEditorInput(input);
\r
178 if (mTargetListener == null) {
\r
179 mTargetListener = new TargetListener();
\r
180 AdtPlugin.getDefault().addTargetListener(mTargetListener);
\r
185 * Reloads this editor, by getting the new model from the {@link LayoutEditor}.
\r
187 public void reloadEditor() {
\r
188 // nothing to be done here. the edited file is now set by initWithFile(IFile)
\r
191 private void useNewEditorInput(IEditorInput input) throws PartInitException {
\r
192 // The contract of init() mentions we need to fail if we can't understand the input.
\r
193 if (!(input instanceof FileEditorInput)) {
\r
194 throw new PartInitException("Input is not of type FileEditorInput: " + //$NON-NLS-1$
\r
195 input == null ? "null" : input.toString()); //$NON-NLS-1$
\r
200 public void createPartControl(Composite parent) {
\r
202 Display d = parent.getDisplay();
\r
203 mClipboard = new Clipboard(d);
\r
205 GridLayout gl = new GridLayout(1, false);
\r
206 parent.setLayout(gl);
\r
207 gl.marginHeight = gl.marginWidth = 0;
\r
209 // create the top part for the configuration control
\r
211 CustomToggle[] toggles = new CustomToggle[] {
\r
215 "Displays extra margins in the layout."
\r
218 public void onSelected(boolean newState) {
\r
219 mUseExplodeMode = newState;
\r
226 "Shows the of all views in the layout."
\r
229 public void onSelected(boolean newState) {
\r
230 mLayoutCanvas.setShowOutline(newState);
\r
235 mConfigListener = new ConfigListener();
\r
236 mConfigComposite = new ConfigurationComposite(mConfigListener, toggles, parent, SWT.BORDER);
\r
237 mConfigComposite.updateThemesAndLocales();
\r
239 mSashPalette = new SashForm(parent, SWT.HORIZONTAL);
\r
240 mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));
\r
242 mPalette = new PaletteComposite(mSashPalette);
\r
244 mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER);
\r
245 mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
\r
247 mLayoutCanvas = new LayoutCanvas(mSashError, SWT.NONE);
\r
248 mLayoutCanvas.setRulesEngine(mRulesEngine);
\r
250 mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY);
\r
251 mErrorLabel.setEditable(false);
\r
252 mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
\r
253 mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
\r
255 mSashPalette.setWeights(new int[] { 20, 80 });
\r
256 mSashError.setWeights(new int[] { 80, 20 });
\r
257 mSashError.setMaximizedControl(mLayoutCanvas);
\r
259 setupEditActions();
\r
261 // Initialize the state
\r
265 private void setupEditActions() {
\r
267 IActionBars actionBars = getEditorSite().getActionBars();
\r
269 actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), new Action("Copy") {
\r
271 public void run() {
\r
272 // TODO enable copy only when there's a selection
\r
273 mLayoutCanvas.onCopy(mClipboard);
\r
277 actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), new Action("Cut") {
\r
279 public void run() {
\r
280 // TODO enable cut only when there's a selection
\r
281 mLayoutCanvas.onCut(mClipboard);
\r
285 actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new Action("Paste") {
\r
287 public void run() {
\r
288 mLayoutCanvas.onPaste(mClipboard);
\r
292 actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
\r
293 new Action("Select All") {
\r
295 public void run() {
\r
296 mLayoutCanvas.onSelectAll();
\r
302 * Switches the stack to display the error label and hide the canvas.
\r
303 * @param errorFormat The new error to display if not null.
\r
304 * @param parameters String.format parameters for the error format.
\r
306 private void displayError(String errorFormat, Object...parameters) {
\r
307 if (errorFormat != null) {
\r
308 mErrorLabel.setText(String.format(errorFormat, parameters));
\r
310 mSashError.setMaximizedControl(null);
\r
313 /** Displays the canvas and hides the error label. */
\r
314 private void hideError() {
\r
315 mSashError.setMaximizedControl(mLayoutCanvas);
\r
319 public void dispose() {
\r
320 if (mTargetListener != null) {
\r
321 AdtPlugin.getDefault().removeTargetListener(mTargetListener);
\r
322 mTargetListener = null;
\r
325 if (mReloadListener != null) {
\r
326 LayoutReloadMonitor.getMonitor().removeListener(mReloadListener);
\r
327 mReloadListener = null;
\r
330 if (mClipboard != null) {
\r
331 mClipboard.dispose();
\r
339 * Listens to changes from the Configuration UI banner and triggers layout rendering when
\r
340 * changed. Also provide the Configuration UI with the list of resources/layout to display.
\r
342 private class ConfigListener implements IConfigListener {
\r
345 * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
\r
346 * <p/>If there is no match, notify the user.
\r
348 public void onConfigurationChange() {
\r
349 mConfiguredFrameworkRes = mConfiguredProjectRes = null;
\r
351 if (mEditedFile == null || mEditedConfig == null) {
\r
355 // get the resources of the file's project.
\r
356 ProjectResources resources = ResourceManager.getInstance().getProjectResources(
\r
357 mEditedFile.getProject());
\r
359 // from the resources, look for a matching file
\r
360 ResourceFile match = null;
\r
361 if (resources != null) {
\r
362 match = resources.getMatchingFile(mEditedFile.getName(),
\r
363 ResourceFolderType.LAYOUT,
\r
364 mConfigComposite.getCurrentConfig());
\r
367 if (match != null) {
\r
368 if (match.getFile().equals(mEditedFile) == false) {
\r
371 getSite().getWorkbenchWindow().getActivePage(),
\r
372 match.getFile().getIFile());
\r
376 } catch (PartInitException e) {
\r
377 // FIXME: do something!
\r
381 // at this point, we have not opened a new file.
\r
383 // Even though the layout doesn't change, the config changed, and referenced
\r
384 // resources need to be updated.
\r
387 // display the error.
\r
388 FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
\r
390 "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.",
\r
391 currentConfig.toDisplayString(),
\r
392 currentConfig.getFolderName(ResourceFolderType.LAYOUT,
\r
393 Sdk.getCurrent().getTarget(mEditedFile.getProject())),
\r
394 mEditedFile.getName());
\r
398 public void onThemeChange() {
\r
402 public void onClippingChange() {
\r
406 public void onCreate() {
\r
407 LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(),
\r
408 mEditedFile.getName(),
\r
409 Sdk.getCurrent().getTarget(mEditedFile.getProject()),
\r
410 mConfigComposite.getCurrentConfig());
\r
411 if (dialog.open() == Dialog.OK) {
\r
412 final FolderConfiguration config = new FolderConfiguration();
\r
413 dialog.getConfiguration(config);
\r
415 createAlternateLayout(config);
\r
419 public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {
\r
420 if (mConfiguredFrameworkRes == null && mConfigComposite != null) {
\r
421 ProjectResources frameworkRes = getFrameworkResources();
\r
423 if (frameworkRes == null) {
\r
424 AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
\r
426 // get the framework resource values based on the current config
\r
427 mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
\r
428 mConfigComposite.getCurrentConfig());
\r
432 return mConfiguredFrameworkRes;
\r
435 public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {
\r
436 if (mConfiguredProjectRes == null && mConfigComposite != null) {
\r
437 ProjectResources project = getProjectResources();
\r
439 // make sure they are loaded
\r
442 // get the project resource values based on the current config
\r
443 mConfiguredProjectRes = project.getConfiguredResources(
\r
444 mConfigComposite.getCurrentConfig());
\r
447 return mConfiguredProjectRes;
\r
451 * Returns a {@link ProjectResources} for the framework resources.
\r
452 * @return the framework resources or null if not found.
\r
454 public ProjectResources getFrameworkResources() {
\r
455 if (mEditedFile != null) {
\r
456 Sdk currentSdk = Sdk.getCurrent();
\r
457 if (currentSdk != null) {
\r
458 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
\r
460 if (target != null) {
\r
461 AndroidTargetData data = currentSdk.getTargetData(target);
\r
463 if (data != null) {
\r
464 return data.getFrameworkResources();
\r
473 public ProjectResources getProjectResources() {
\r
474 if (mEditedFile != null) {
\r
475 ResourceManager manager = ResourceManager.getInstance();
\r
476 return manager.getProjectResources(mEditedFile.getProject());
\r
483 * Creates a new layout file from the specified {@link FolderConfiguration}.
\r
485 private void createAlternateLayout(final FolderConfiguration config) {
\r
486 new Job("Create Alternate Resource") {
\r
488 protected IStatus run(IProgressMonitor monitor) {
\r
489 // get the folder name
\r
490 String folderName = config.getFolderName(ResourceFolderType.LAYOUT,
\r
491 Sdk.getCurrent().getTarget(mEditedFile.getProject()));
\r
494 // look to see if it exists.
\r
495 // get the res folder
\r
496 IFolder res = (IFolder)mEditedFile.getParent().getParent();
\r
497 String path = res.getLocation().toOSString();
\r
499 File newLayoutFolder = new File(path + File.separator + folderName);
\r
500 if (newLayoutFolder.isFile()) {
\r
501 // this should not happen since aapt would have complained
\r
502 // before, but if one disable the automatic build, this could
\r
504 String message = String.format("File 'res/%1$s' is in the way!",
\r
507 AdtPlugin.displayError("Layout Creation", message);
\r
509 return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
\r
510 } else if (newLayoutFolder.exists() == false) {
\r
512 newLayoutFolder.mkdir();
\r
515 // now create the file
\r
516 File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +
\r
517 File.separator + mEditedFile.getName());
\r
519 newLayoutFile.createNewFile();
\r
521 InputStream input = mEditedFile.getContents();
\r
523 FileOutputStream fos = new FileOutputStream(newLayoutFile);
\r
525 byte[] data = new byte[512];
\r
527 while ((count = input.read(data)) != -1) {
\r
528 fos.write(data, 0, count);
\r
534 // refreshes the res folder to show up the new
\r
535 // layout folder (if needed) and the file.
\r
536 // We use a progress monitor to catch the end of the refresh
\r
537 // to trigger the edit of the new file.
\r
538 res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {
\r
539 public void done() {
\r
540 mConfigComposite.getDisplay().asyncExec(new Runnable() {
\r
541 public void run() {
\r
542 onConfigurationChange();
\r
547 public void beginTask(String name, int totalWork) {
\r
551 public void internalWorked(double work) {
\r
555 public boolean isCanceled() {
\r
560 public void setCanceled(boolean value) {
\r
564 public void setTaskName(String name) {
\r
568 public void subTask(String name) {
\r
572 public void worked(int work) {
\r
576 } catch (IOException e2) {
\r
577 String message = String.format(
\r
578 "Failed to create File 'res/%1$s/%2$s' : %3$s",
\r
579 folderName, mEditedFile.getName(), e2.getMessage());
\r
581 AdtPlugin.displayError("Layout Creation", message);
\r
583 return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
\r
585 } catch (CoreException e2) {
\r
586 String message = String.format(
\r
587 "Failed to create File 'res/%1$s/%2$s' : %3$s",
\r
588 folderName, mEditedFile.getName(), e2.getMessage());
\r
590 AdtPlugin.displayError("Layout Creation", message);
\r
592 return e2.getStatus();
\r
595 return Status.OK_STATUS;
\r
603 * Listens to target changed in the current project, to trigger a new layout rendering.
\r
605 private class TargetListener extends TargetChangeListener {
\r
606 boolean mSdkLoaded = false; // indicates whether we got a sdk loaded event.
\r
609 public IProject getProject() {
\r
610 return getLayoutEditor().getProject();
\r
614 public void reload() {
\r
615 // because the target changed we must reset the configured resources.
\r
616 mConfiguredFrameworkRes = mConfiguredProjectRes = null;
\r
618 // make sure we remove the custom view loader, since its parent class loader is the
\r
619 // bridge class loader.
\r
620 mProjectCallback = null;
\r
622 // update the themes and locales since the target change could have changed the
\r
624 mConfigComposite.updateThemesAndLocales();
\r
626 // reset the config selector UI to match the new target (and possibly sdk).
\r
627 if (mEditedConfig != null) {
\r
635 // SDK change has been handled, reset the flag.
\r
636 mSdkLoaded = false;
\r
640 public void onSdkLoaded() {
\r
641 mSdkLoaded = true; // this will be reset when we get the target loaded event
\r
642 // which always comes after.
\r
646 // ----------------
\r
649 * Save operation in the Graphical Editor Part.
\r
651 * In our workflow, the model is owned by the Structured XML Editor.
\r
652 * The graphical layout editor just displays it -- thus we don't really
\r
653 * save anything here.
\r
655 * This must NOT call the parent editor part. At the contrary, the parent editor
\r
656 * part will call this *after* having done the actual save operation.
\r
658 * The only action this editor must do is mark the undo command stack as
\r
659 * being no longer dirty.
\r
662 public void doSave(IProgressMonitor monitor) {
\r
663 // TODO implement a command stack
\r
664 // getCommandStack().markSaveLocation();
\r
665 // firePropertyChange(PROP_DIRTY);
\r
669 * Save operation in the Graphical Editor Part.
\r
671 * In our workflow, the model is owned by the Structured XML Editor.
\r
672 * The graphical layout editor just displays it -- thus we don't really
\r
673 * save anything here.
\r
676 public void doSaveAs() {
\r
681 * In our workflow, the model is owned by the Structured XML Editor.
\r
682 * The graphical layout editor just displays it -- thus we don't really
\r
683 * save anything here.
\r
686 public boolean isDirty() {
\r
691 * In our workflow, the model is owned by the Structured XML Editor.
\r
692 * The graphical layout editor just displays it -- thus we don't really
\r
693 * save anything here.
\r
696 public boolean isSaveAsAllowed() {
\r
701 public void setFocus() {
\r
702 // TODO Auto-generated method stub
\r
707 * Responds to a page change that made the Graphical editor page the activated page.
\r
709 public void activated() {
\r
710 if (mNeedsRecompute || mNeedsXmlReload) {
\r
716 * Responds to a page change that made the Graphical editor page the deactivated page
\r
718 public void deactivated() {
\r
719 // nothing to be done here for now.
\r
723 * Sets the UI for the edition of a new file.
\r
724 * @param iFile the file being edited.
\r
726 public void initWithFile(IFile file) {
\r
727 mEditedFile = file;
\r
729 ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
\r
730 mEditedConfig = resFolder.getConfiguration();
\r
732 mConfigComposite.updateThemesAndLocales();
\r
734 Sdk currentSdk = Sdk.getCurrent();
\r
735 if (currentSdk != null) {
\r
736 IAndroidTarget target = currentSdk.getTarget(file.getProject());
\r
737 if (target != null) {
\r
738 mConfigComposite.initWith(mEditedConfig, target);
\r
742 if (mReloadListener == null) {
\r
743 mReloadListener = new ReloadListener();
\r
744 LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);
\r
747 if (mRulesEngine == null) {
\r
748 mRulesEngine = new RulesEngine(mEditedFile.getProject());
\r
752 public void onTargetChange() {
\r
753 onTargetOrSdkChange(false /* reloadDevices */);
\r
756 public void onSdkChange() {
\r
757 onTargetOrSdkChange(true /* reloadDevices */);
\r
761 * Reloads the configuration selector.
\r
762 * @param reloadDevices whether the {@link LayoutDevice} objects should be reloaded.
\r
764 private void onTargetOrSdkChange(boolean reloadDevices) {
\r
765 Sdk currentSdk = Sdk.getCurrent();
\r
766 if (currentSdk != null) {
\r
767 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
\r
768 if (target != null) {
\r
769 mConfigComposite.resetUi(mEditedConfig, target, reloadDevices);
\r
770 mConfigListener.onConfigurationChange();
\r
775 public Clipboard getClipboard() {
\r
779 public LayoutEditor getLayoutEditor() {
\r
780 return mLayoutEditor;
\r
783 public UiDocumentNode getModel() {
\r
784 return mLayoutEditor.getUiRootNode();
\r
787 public SelectionSynchronizer getSelectionSynchronizer() {
\r
788 // TODO Auto-generated method stub
\r
793 * Callback for XML model changed. Only update/recompute the layout if the editor is visible
\r
795 public void onXmlModelChanged() {
\r
796 if (mLayoutEditor.isGraphicalEditorActive()) {
\r
797 doXmlReload(true /* force */);
\r
800 mNeedsXmlReload = true;
\r
805 * Actually performs the XML reload
\r
806 * @see #onXmlModelChanged()
\r
808 private void doXmlReload(boolean force) {
\r
809 if (force || mNeedsXmlReload) {
\r
811 // TODO : update the mLayoutCanvas, preserving the current selection if possible.
\r
813 // GraphicalViewer viewer = getGraphicalViewer();
\r
815 // // try to preserve the selection before changing the content
\r
816 // SelectionManager selMan = viewer.getSelectionManager();
\r
817 // ISelection selection = selMan.getSelection();
\r
820 // viewer.setContents(getModel());
\r
822 // selMan.setSelection(selection);
\r
825 mNeedsXmlReload = false;
\r
829 public void recomputeLayout() {
\r
830 doXmlReload(false /* force */);
\r
832 // check that the resource exists. If the file is opened but the project is closed
\r
833 // or deleted for some reason (changed from outside of eclipse), then this will
\r
835 if (mEditedFile.exists() == false) {
\r
836 displayError("Resource '%1$s' does not exist.",
\r
837 mEditedFile.getFullPath().toString());
\r
841 IProject iProject = mEditedFile.getProject();
\r
843 if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {
\r
844 String message = String.format("%1$s is out of sync. Please refresh.",
\r
845 mEditedFile.getName());
\r
847 displayError(message);
\r
849 // also print it in the error console.
\r
850 AdtPlugin.printErrorToConsole(iProject.getName(), message);
\r
854 Sdk currentSdk = Sdk.getCurrent();
\r
855 if (currentSdk != null) {
\r
856 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
\r
857 if (target == null) {
\r
858 displayError("The project target is not set.");
\r
862 AndroidTargetData data = currentSdk.getTargetData(target);
\r
863 if (data == null) {
\r
864 // It can happen that the workspace refreshes while the SDK is loading its
\r
865 // data, which could trigger a redraw of the opened layout if some resources
\r
866 // changed while Eclipse is closed.
\r
867 // In this case data could be null, but this is not an error.
\r
868 // We can just silently return, as all the opened editors are automatically
\r
869 // refreshed once the SDK finishes loading.
\r
870 LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);
\r
871 switch (targetLoadStatus) {
\r
873 displayError("The project target (%1$s) is still loading.\n%2$s will refresh automatically once the process is finished.",
\r
874 target.getName(), mEditedFile.getName());
\r
877 case FAILED: // known failure
\r
878 case LOADED: // success but data isn't loaded?!?!
\r
879 displayError("The project target (%s) was not properly loaded.",
\r
887 // check there is actually a model (maybe the file is empty).
\r
888 UiDocumentNode model = getModel();
\r
890 if (model.getUiChildren().size() == 0) {
\r
891 displayError("No Xml content. Go to the Outline view and add nodes.");
\r
895 LayoutBridge bridge = data.getLayoutBridge();
\r
897 if (bridge.bridge != null) { // bridge can never be null.
\r
898 ResourceManager resManager = ResourceManager.getInstance();
\r
900 ProjectResources projectRes = resManager.getProjectResources(iProject);
\r
901 if (projectRes == null) {
\r
902 displayError("Missing project resources.");
\r
906 // get the resources of the file's project.
\r
907 Map<String, Map<String, IResourceValue>> configuredProjectRes =
\r
908 mConfigListener.getConfiguredProjectResources();
\r
910 // get the framework resources
\r
911 Map<String, Map<String, IResourceValue>> frameworkResources =
\r
912 mConfigListener.getConfiguredFrameworkResources();
\r
914 if (configuredProjectRes != null && frameworkResources != null) {
\r
915 if (mProjectCallback == null) {
\r
916 mProjectCallback = new ProjectCallback(
\r
917 bridge.classLoader, projectRes, iProject);
\r
920 if (mLogger == null) {
\r
921 mLogger = new ILayoutLog() {
\r
922 public void error(String message) {
\r
923 AdtPlugin.printErrorToConsole(mEditedFile.getName(), message);
\r
926 public void error(Throwable error) {
\r
927 String message = error.getMessage();
\r
928 if (message == null) {
\r
929 message = error.getClass().getName();
\r
932 PrintStream ps = new PrintStream(AdtPlugin.getErrorStream());
\r
933 error.printStackTrace(ps);
\r
936 public void warning(String message) {
\r
937 AdtPlugin.printToConsole(mEditedFile.getName(), message);
\r
942 // get the selected theme
\r
943 String theme = mConfigComposite.getTheme();
\r
944 if (theme != null) {
\r
945 // Compute the layout
\r
946 Rectangle rect = getBounds();
\r
948 int width = rect.width;
\r
949 int height = rect.height;
\r
950 if (mUseExplodeMode) {
\r
951 // compute how many padding in x and y will bump the screen size
\r
952 ExplodedRenderingHelper helper = new ExplodedRenderingHelper(
\r
953 getModel(), iProject);
\r
955 // there are 2 paddings for each view
\r
956 // left and right, or top and bottom.
\r
957 int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;
\r
959 width += helper.getWidthPadding() * paddingValue;
\r
960 height += helper.getHeightPadding() * paddingValue;
\r
963 int density = mConfigComposite.getDensity().getDpiValue();
\r
964 float xdpi = mConfigComposite.getXDpi();
\r
965 float ydpi = mConfigComposite.getYDpi();
\r
966 boolean isProjectTheme = mConfigComposite.isProjectTheme();
\r
968 UiElementPullParser parser = new UiElementPullParser(getModel(),
\r
969 mUseExplodeMode, density, xdpi, iProject);
\r
971 ILayoutResult result = computeLayout(bridge, parser,
\r
972 iProject /* projectKey */,
\r
973 width, height, !mConfigComposite.getClipping(),
\r
974 density, xdpi, ydpi,
\r
975 theme, isProjectTheme,
\r
976 configuredProjectRes, frameworkResources, mProjectCallback,
\r
979 mLayoutCanvas.setResult(result);
\r
981 // update the UiElementNode with the layout info.
\r
982 if (result.getSuccess() == ILayoutResult.SUCCESS) {
\r
985 displayError(result.getErrorMessage());
\r
992 // SDK is loaded but not the layout library!
\r
994 // check whether the bridge managed to load, or not
\r
995 if (bridge.status == LoadStatus.LOADING) {
\r
996 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.",
\r
997 mEditedFile.getName());
\r
999 displayError("Eclipse failed to load the framework information and the layout library!");
\r
1003 displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",
\r
1004 mEditedFile.getName());
\r
1007 // no matter the result, we are done doing the recompute based on the latest
\r
1008 // resource/code change.
\r
1009 mNeedsRecompute = false;
\r
1014 * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
\r
1015 * the implementation API level.
\r
1017 * Implementation detail: the bridge's computeLayout() method already returns a newly
\r
1018 * allocated ILayoutResult.
\r
1020 @SuppressWarnings("deprecation")
\r
1021 private static ILayoutResult computeLayout(LayoutBridge bridge,
\r
1022 IXmlPullParser layoutDescription, Object projectKey,
\r
1023 int screenWidth, int screenHeight, boolean renderFullSize,
\r
1024 int density, float xdpi, float ydpi,
\r
1025 String themeName, boolean isProjectTheme,
\r
1026 Map<String, Map<String, IResourceValue>> projectResources,
\r
1027 Map<String, Map<String, IResourceValue>> frameworkResources,
\r
1028 IProjectCallback projectCallback, ILayoutLog logger) {
\r
1030 if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) {
\r
1031 // newest API with support for "render full height"
\r
1032 // TODO: link boolean to UI.
\r
1033 return bridge.bridge.computeLayout(layoutDescription,
\r
1034 projectKey, screenWidth, screenHeight, renderFullSize,
\r
1035 density, xdpi, ydpi,
\r
1036 themeName, isProjectTheme,
\r
1037 projectResources, frameworkResources, projectCallback,
\r
1039 } else if (bridge.apiLevel == 3) {
\r
1040 // newer api with density support.
\r
1041 return bridge.bridge.computeLayout(layoutDescription,
\r
1042 projectKey, screenWidth, screenHeight, density, xdpi, ydpi,
\r
1043 themeName, isProjectTheme,
\r
1044 projectResources, frameworkResources, projectCallback,
\r
1046 } else if (bridge.apiLevel == 2) {
\r
1047 // api with boolean for separation of project/framework theme
\r
1048 return bridge.bridge.computeLayout(layoutDescription,
\r
1049 projectKey, screenWidth, screenHeight, themeName, isProjectTheme,
\r
1050 projectResources, frameworkResources, projectCallback,
\r
1053 // oldest api with no density/dpi, and project theme boolean mixed
\r
1054 // into the theme name.
\r
1056 // change the string if it's a custom theme to make sure we can
\r
1057 // differentiate them
\r
1058 if (isProjectTheme) {
\r
1059 themeName = "*" + themeName; //$NON-NLS-1$
\r
1062 return bridge.bridge.computeLayout(layoutDescription,
\r
1063 projectKey, screenWidth, screenHeight, themeName,
\r
1064 projectResources, frameworkResources, projectCallback,
\r
1069 public Rectangle getBounds() {
\r
1070 return mConfigComposite.getScreenBounds();
\r
1073 public void reloadPalette() {
\r
1074 if (mPalette != null) {
\r
1075 mPalette.reloadPalette(mLayoutEditor.getTargetData());
\r
1080 * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
\r
1081 * created by {@link ElementCreateCommand#execute()}.
\r
1083 * @param uiNodeModel The {@link UiElementNode} to select.
\r
1085 public void selectModel(UiElementNode uiNodeModel) {
\r
1087 // TODO this method was useful for GLE1. We may not need it anymore now.
\r
1089 // GraphicalViewer viewer = getGraphicalViewer();
\r
1091 // // Give focus to the graphical viewer (in case the outline has it)
\r
1092 // viewer.getControl().forceFocus();
\r
1094 // Object editPart = viewer.getEditPartRegistry().get(uiNodeModel);
\r
1096 // if (editPart instanceof EditPart) {
\r
1097 // viewer.select((EditPart)editPart);
\r
1101 private class ReloadListener implements ILayoutReloadListener {
\r
1104 * @see com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener#reloadLayout(boolean, boolean, boolean)
\r
1106 * Called when the file changes triggered a redraw of the layout
\r
1108 public void reloadLayout(boolean codeChange, boolean rChange, boolean resChange) {
\r
1109 boolean recompute = rChange;
\r
1114 // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache.
\r
1116 // force a reparse in case a value XML file changed.
\r
1117 mConfiguredProjectRes = null;
\r
1119 // clear the cache in the bridge in case a bitmap/9-patch changed.
\r
1120 IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());
\r
1121 if (target != null) {
\r
1123 AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
\r
1124 if (data != null) {
\r
1125 LayoutBridge bridge = data.getLayoutBridge();
\r
1127 if (bridge.bridge != null) {
\r
1128 bridge.bridge.clearCaches(mEditedFile.getProject());
\r
1133 mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
\r
1134 public void run() {
\r
1135 mConfigComposite.updateThemesAndLocales();
\r
1141 // only recompute if the custom view loader was used to load some code.
\r
1142 if (mProjectCallback != null && mProjectCallback.isUsed()) {
\r
1143 mProjectCallback = null;
\r
1149 mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
\r
1150 public void run() {
\r
1151 if (mLayoutEditor.isGraphicalEditorActive()) {
\r
1152 recomputeLayout();
\r
1154 mNeedsRecompute = true;
\r