OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / editors / layout / gle1 / GraphicalLayoutEditor.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.ide.eclipse.adt.internal.editors.layout.gle1;
18
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
21 import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
22 import com.android.ide.eclipse.adt.internal.editors.layout.IGraphicalLayoutEditor;
23 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
24 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
25 import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
26 import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
27 import com.android.ide.eclipse.adt.internal.editors.layout.WidgetPullParser;
28 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor.UiEditorActions;
29 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
30 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
31 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
32 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
33 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomToggle;
34 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
35 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
36 import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;
37 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementEditPart;
38 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementsEditPartFactory;
39 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementsEditPartFactory.IOutlineProvider;
40 import com.android.ide.eclipse.adt.internal.editors.ui.tree.CopyCutAction;
41 import com.android.ide.eclipse.adt.internal.editors.ui.tree.PasteAction;
42 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
43 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
44 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
45 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
46 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;
47 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
48 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
49 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
50 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
51 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
52 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
53 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
54 import com.android.ide.eclipse.adt.io.IFileWrapper;
55 import com.android.layoutlib.api.ILayoutBridge;
56 import com.android.layoutlib.api.ILayoutLog;
57 import com.android.layoutlib.api.ILayoutResult;
58 import com.android.layoutlib.api.IProjectCallback;
59 import com.android.layoutlib.api.IResourceValue;
60 import com.android.layoutlib.api.IXmlPullParser;
61 import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
62 import com.android.sdklib.IAndroidTarget;
63
64 import org.eclipse.core.resources.IFile;
65 import org.eclipse.core.resources.IFolder;
66 import org.eclipse.core.resources.IProject;
67 import org.eclipse.core.resources.IResource;
68 import org.eclipse.core.runtime.CoreException;
69 import org.eclipse.core.runtime.IProgressMonitor;
70 import org.eclipse.core.runtime.IStatus;
71 import org.eclipse.core.runtime.Status;
72 import org.eclipse.core.runtime.jobs.Job;
73 import org.eclipse.draw2d.geometry.Rectangle;
74 import org.eclipse.gef.DefaultEditDomain;
75 import org.eclipse.gef.EditPart;
76 import org.eclipse.gef.EditPartViewer;
77 import org.eclipse.gef.GraphicalViewer;
78 import org.eclipse.gef.SelectionManager;
79 import org.eclipse.gef.dnd.TemplateTransferDragSourceListener;
80 import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
81 import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
82 import org.eclipse.gef.palette.PaletteRoot;
83 import org.eclipse.gef.requests.CreationFactory;
84 import org.eclipse.gef.ui.parts.GraphicalEditorWithPalette;
85 import org.eclipse.gef.ui.parts.SelectionSynchronizer;
86 import org.eclipse.jface.action.Action;
87 import org.eclipse.jface.action.IMenuListener;
88 import org.eclipse.jface.action.IMenuManager;
89 import org.eclipse.jface.action.MenuManager;
90 import org.eclipse.jface.action.Separator;
91 import org.eclipse.jface.dialogs.Dialog;
92 import org.eclipse.jface.viewers.ISelection;
93 import org.eclipse.swt.SWT;
94 import org.eclipse.swt.dnd.Clipboard;
95 import org.eclipse.swt.graphics.ImageData;
96 import org.eclipse.swt.graphics.PaletteData;
97 import org.eclipse.swt.layout.FillLayout;
98 import org.eclipse.swt.layout.GridData;
99 import org.eclipse.swt.layout.GridLayout;
100 import org.eclipse.swt.widgets.Composite;
101 import org.eclipse.ui.IEditorInput;
102 import org.eclipse.ui.PartInitException;
103 import org.eclipse.ui.ide.IDE;
104 import org.eclipse.ui.part.FileEditorInput;
105
106 import java.awt.image.BufferedImage;
107 import java.awt.image.DataBufferInt;
108 import java.awt.image.Raster;
109 import java.io.File;
110 import java.io.FileOutputStream;
111 import java.io.IOException;
112 import java.io.InputStream;
113 import java.io.PrintStream;
114 import java.util.ArrayList;
115 import java.util.HashMap;
116 import java.util.List;
117 import java.util.Map;
118
119 /**
120  * Graphical layout editor, based on GEF.
121  * <p/>
122  * To understand GEF: http://www.ibm.com/developerworks/opensource/library/os-gef/
123  * <p/>
124  * To understand Drag'n'drop: http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
125  *
126  * @since GLE1
127  */
128 public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
129         implements IGraphicalLayoutEditor, IConfigListener, ILayoutReloadListener, IOutlineProvider {
130
131
132     /** Reference to the layout editor */
133     private final LayoutEditor mLayoutEditor;
134
135     /** reference to the file being edited. */
136     private IFile mEditedFile;
137
138     private Clipboard mClipboard;
139     private Composite mParent;
140     private ConfigurationComposite mConfigComposite;
141
142     private PaletteRoot mPaletteRoot;
143
144     private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes;
145     private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes;
146     private ProjectCallback mProjectCallback;
147     private ILayoutLog mLogger;
148
149     private boolean mUseExplodeMode;
150     private boolean mUseOutlineMode;
151     private boolean mNeedsXmlReload = false;
152     private boolean mNeedsRecompute = false;
153
154     /** Listener to update the root node if the target of the file is changed because of a
155      * SDK location change or a project target change */
156     private ITargetChangeListener mTargetListener = new ITargetChangeListener() {
157         public void onProjectTargetChange(IProject changedProject) {
158             if (changedProject != null && changedProject.equals(getProject())) {
159                 updateEditor();
160             }
161         }
162
163         public void onTargetLoaded(IAndroidTarget target) {
164             IProject project = getProject();
165             if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) {
166                 updateEditor();
167             }
168         }
169
170         public void onSdkLoaded() {
171             Sdk currentSdk = Sdk.getCurrent();
172             if (currentSdk != null) {
173                 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
174                 if (target != null) {
175                     mConfigComposite.onSdkLoaded(target);
176                     onConfigurationChange();
177                 }
178             }
179         }
180
181         private void updateEditor() {
182             mLayoutEditor.commitPages(false /* onSave */);
183
184             // because the target changed we must reset the configured resources.
185             mConfiguredFrameworkRes = mConfiguredProjectRes = null;
186
187             // make sure we remove the custom view loader, since its parent class loader is the
188             // bridge class loader.
189             mProjectCallback = null;
190
191             // recreate the ui root node always, this will also call onTargetChange
192             // on the config composite
193             mLayoutEditor.initUiRootNode(true /*force*/);
194         }
195
196         private IProject getProject() {
197             return getLayoutEditor().getProject();
198         }
199     };
200
201     private final Runnable mConditionalRecomputeRunnable = new Runnable() {
202         public void run() {
203             if (mLayoutEditor.isGraphicalEditorActive()) {
204                 recomputeLayout();
205             } else {
206                 mNeedsRecompute = true;
207             }
208         }
209     };
210
211     private final Runnable mLocaleUpdaterFromUiRunnable = new Runnable() {
212         public void run() {
213             mConfigComposite.updateLocales();
214         }
215     };
216
217     public GraphicalLayoutEditor(LayoutEditor layoutEditor) {
218         mLayoutEditor = layoutEditor;
219         setEditDomain(new DefaultEditDomain(this));
220         setPartName("Layout");
221
222         AdtPlugin.getDefault().addTargetListener(mTargetListener);
223     }
224
225     // ------------------------------------
226     // Methods overridden from base classes
227     //------------------------------------
228
229     @Override
230     public void createPartControl(Composite parent) {
231         mParent = parent;
232         GridLayout gl;
233
234         mClipboard = new Clipboard(parent.getDisplay());
235
236         parent.setLayout(gl = new GridLayout(1, false));
237         gl.marginHeight = gl.marginWidth = 0;
238
239         // create the top part for the configuration control
240
241         CustomToggle[] toggles = new CustomToggle[] {
242                 new CustomToggle(
243                         "Explode",
244                         null, //image
245                         "Displays extra margins in the layout."
246                         ) {
247                     @Override
248                     public void onSelected(boolean newState) {
249                         mUseExplodeMode = newState;
250                         recomputeLayout();
251                     }
252                 },
253                 new CustomToggle(
254                         "Outline",
255                         null, //image
256                         "Shows the outline of all views in the layout."
257                         ) {
258                     @Override
259                     public void onSelected(boolean newState) {
260                         mUseOutlineMode = newState;
261                         recomputeLayout();
262                     }
263                 }
264         };
265
266         mConfigComposite = new ConfigurationComposite(this, toggles, parent, SWT.NONE);
267
268         // create a new composite that will contain the standard editor controls.
269         Composite editorParent = new Composite(parent, SWT.NONE);
270         editorParent.setLayoutData(new GridData(GridData.FILL_BOTH));
271         editorParent.setLayout(new FillLayout());
272         super.createPartControl(editorParent);
273     }
274
275     @Override
276     public void dispose() {
277         if (mTargetListener != null) {
278             AdtPlugin.getDefault().removeTargetListener(mTargetListener);
279             mTargetListener = null;
280         }
281
282         LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this);
283
284         if (mClipboard != null) {
285             mClipboard.dispose();
286             mClipboard = null;
287         }
288
289         super.dispose();
290     }
291
292     /**
293      * Returns the selection synchronizer object.
294      * The synchronizer can be used to sync the selection of 2 or more EditPartViewers.
295      * <p/>
296      * This is changed from protected to public so that the outline can use it.
297      *
298      * @return the synchronizer
299      */
300     @Override
301     public SelectionSynchronizer getSelectionSynchronizer() {
302         return super.getSelectionSynchronizer();
303     }
304
305     /**
306      * Returns the edit domain.
307      * <p/>
308      * This is changed from protected to public so that the outline can use it.
309      *
310      * @return the edit domain
311      */
312     @Override
313     public DefaultEditDomain getEditDomain() {
314         return super.getEditDomain();
315     }
316
317     /* (non-Javadoc)
318      * Creates the palette root.
319      */
320     @Override
321     protected PaletteRoot getPaletteRoot() {
322         mPaletteRoot = PaletteFactory.createPaletteRoot(mPaletteRoot,
323                 mLayoutEditor.getTargetData());
324         return mPaletteRoot;
325     }
326
327     public Clipboard getClipboard() {
328         return mClipboard;
329     }
330
331     /**
332      * Save operation in the Graphical Layout Editor.
333      * <p/>
334      * In our workflow, the model is owned by the Structured XML Editor.
335      * The graphical layout editor just displays it -- thus we don't really
336      * save anything here.
337      * <p/>
338      * This must NOT call the parent editor part. At the contrary, the parent editor
339      * part will call this *after* having done the actual save operation.
340      * <p/>
341      * The only action this editor must do is mark the undo command stack as
342      * being no longer dirty.
343      */
344     @Override
345     public void doSave(IProgressMonitor monitor) {
346         getCommandStack().markSaveLocation();
347         firePropertyChange(PROP_DIRTY);
348     }
349
350     @Override
351     protected void configurePaletteViewer() {
352         super.configurePaletteViewer();
353
354         // Create a drag source listener on an edit part that is a viewer.
355         // What this does is use DND with a TemplateTransfer type which is actually
356         // the PaletteTemplateEntry held in the PaletteRoot.
357         TemplateTransferDragSourceListener dragSource =
358             new TemplateTransferDragSourceListener(getPaletteViewer());
359
360         // Create a drag source on the palette viewer.
361         // See the drag target associated with the GraphicalViewer in configureGraphicalViewer.
362         getPaletteViewer().addDragSourceListener(dragSource);
363     }
364
365     /* (non-javadoc)
366      * Configure the graphical viewer before it receives its contents.
367      */
368     @Override
369     protected void configureGraphicalViewer() {
370         super.configureGraphicalViewer();
371
372         GraphicalViewer viewer = getGraphicalViewer();
373         viewer.setEditPartFactory(new UiElementsEditPartFactory(mParent.getDisplay(), this));
374         viewer.setRootEditPart(new ScalableFreeformRootEditPart());
375
376         // Disable the following -- we don't drag *from* the GraphicalViewer yet:
377         // viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer));
378
379         viewer.addDropTargetListener(new DropListener(viewer));
380     }
381
382     class DropListener extends TemplateTransferDropTargetListener {
383         public DropListener(EditPartViewer viewer) {
384             super(viewer);
385         }
386
387         // TODO explain
388         @Override
389         protected CreationFactory getFactory(final Object template) {
390             return new CreationFactory() {
391                 public Object getNewObject() {
392                     // We don't know the newly created EditPart since "creating" new
393                     // elements is done by ElementCreateCommand.execute() directly by
394                     // manipulating the XML elements..
395                     return null;
396                 }
397
398                 public Object getObjectType() {
399                     return template;
400                 }
401
402             };
403         }
404     }
405
406     /* (non-javadoc)
407      * Set the contents of the GraphicalViewer after it has been created.
408      */
409     @Override
410     protected void initializeGraphicalViewer() {
411         GraphicalViewer viewer = getGraphicalViewer();
412         viewer.setContents(getModel());
413
414         IEditorInput input = getEditorInput();
415         if (input instanceof FileEditorInput) {
416             FileEditorInput fileInput = (FileEditorInput)input;
417             mEditedFile = fileInput.getFile();
418
419             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), this);
420         } else {
421             // really this shouldn't happen! Log it in case it happens
422             mEditedFile = null;
423             AdtPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s",
424                     input.toString());
425         }
426     }
427
428     /* (non-javadoc)
429      * Sets the graphicalViewer for this EditorPart.
430      * @param viewer the graphical viewer
431      */
432     @Override
433     protected void setGraphicalViewer(GraphicalViewer viewer) {
434         super.setGraphicalViewer(viewer);
435
436         // TODO: viewer.setKeyHandler()
437         viewer.setContextMenu(createContextMenu(viewer));
438     }
439
440     /**
441      * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
442      * created by {@link ElementCreateCommand#execute()}.
443      *
444      * @param uiNodeModel The {@link UiElementNode} to select.
445      */
446     public void selectModel(UiElementNode uiNodeModel) {
447         GraphicalViewer viewer = getGraphicalViewer();
448
449         // Give focus to the graphical viewer (in case the outline has it)
450         viewer.getControl().forceFocus();
451
452         Object editPart = viewer.getEditPartRegistry().get(uiNodeModel);
453
454         if (editPart instanceof EditPart) {
455             viewer.select((EditPart)editPart);
456         }
457     }
458
459
460     //--------------
461     // Local methods
462     //--------------
463
464     public LayoutEditor getLayoutEditor() {
465         return mLayoutEditor;
466     }
467
468     private MenuManager createContextMenu(GraphicalViewer viewer) {
469         MenuManager menuManager = new MenuManager();
470         menuManager.setRemoveAllWhenShown(true);
471         menuManager.addMenuListener(new ActionMenuListener(viewer));
472
473         return menuManager;
474     }
475
476     private class ActionMenuListener implements IMenuListener {
477         private final GraphicalViewer mViewer;
478
479         public ActionMenuListener(GraphicalViewer viewer) {
480             mViewer = viewer;
481         }
482
483         /**
484          * The menu is about to be shown. The menu manager has already been
485          * requested to remove any existing menu item. This method gets the
486          * tree selection and if it is of the appropriate type it re-creates
487          * the necessary actions.
488          */
489        public void menuAboutToShow(IMenuManager manager) {
490            ArrayList<UiElementNode> selected = new ArrayList<UiElementNode>();
491
492            // filter selected items and only keep those we can handle
493            for (Object obj : mViewer.getSelectedEditParts()) {
494                if (obj instanceof UiElementEditPart) {
495                    UiElementEditPart part = (UiElementEditPart) obj;
496                    UiElementNode uiNode = part.getUiNode();
497                    if (uiNode != null) {
498                        selected.add(uiNode);
499                    }
500                }
501            }
502
503            if (selected.size() > 0) {
504                doCreateMenuAction(manager, mViewer, selected);
505            }
506         }
507     }
508
509     private void doCreateMenuAction(IMenuManager manager,
510             final GraphicalViewer viewer,
511             final ArrayList<UiElementNode> selected) {
512         if (selected != null) {
513             boolean hasXml = false;
514             for (UiElementNode uiNode : selected) {
515                 if (uiNode.getXmlNode() != null) {
516                     hasXml = true;
517                     break;
518                 }
519             }
520
521             if (hasXml) {
522                 manager.add(new CopyCutAction(mLayoutEditor, getClipboard(),
523                         null, selected, true /* cut */));
524                 manager.add(new CopyCutAction(mLayoutEditor, getClipboard(),
525                         null, selected, false /* cut */));
526
527                 // Can't paste with more than one element selected (the selection is the target)
528                 if (selected.size() <= 1) {
529                     // Paste is not valid if it would add a second element on a terminal element
530                     // which parent is a document -- an XML document can only have one child. This
531                     // means paste is valid if the current UI node can have children or if the
532                     // parent is not a document.
533                     UiElementNode ui_root = selected.get(0).getUiRoot();
534                     if (ui_root.getDescriptor().hasChildren() ||
535                             !(ui_root.getUiParent() instanceof UiDocumentNode)) {
536                         manager.add(new PasteAction(mLayoutEditor, getClipboard(),
537                                                     selected.get(0)));
538                     }
539                 }
540                 manager.add(new Separator());
541             }
542         }
543
544         // Append "add" and "remove" actions. They do the same thing as the add/remove
545         // buttons on the side.
546         IconFactory factory = IconFactory.getInstance();
547
548         final UiEditorActions uiActions = mLayoutEditor.getUiEditorActions();
549
550         // "Add" makes sense only if there's 0 or 1 item selected since the
551         // one selected item becomes the target.
552         if (selected == null || selected.size() <= 1) {
553             manager.add(new Action("Add...", factory.getImageDescriptor("add")) { //$NON-NLS-2$
554                 @Override
555                 public void run() {
556                     UiElementNode node = selected != null && selected.size() > 0 ? selected.get(0)
557                                                                                  : null;
558                     uiActions.doAdd(node, viewer.getControl().getShell());
559                 }
560             });
561         }
562
563         if (selected != null) {
564             manager.add(new Action("Remove", factory.getImageDescriptor("delete")) { //$NON-NLS-2$
565                 @Override
566                 public void run() {
567                     uiActions.doRemove(selected, viewer.getControl().getShell());
568                 }
569             });
570
571             manager.add(new Separator());
572
573             manager.add(new Action("Up", factory.getImageDescriptor("up")) { //$NON-NLS-2$
574                 @Override
575                 public void run() {
576                     uiActions.doUp(selected);
577                 }
578             });
579             manager.add(new Action("Down", factory.getImageDescriptor("down")) { //$NON-NLS-2$
580                 @Override
581                 public void run() {
582                     uiActions.doDown(selected);
583                 }
584             });
585         }
586
587     }
588
589     /**
590      * Opens and initialize the editor with a new file.
591      * @param file the file being edited.
592      */
593     public void openFile(IFile file) {
594         mEditedFile = file;
595         mConfigComposite.setFile(mEditedFile);
596     }
597
598     /**
599      * Resets the editor with a replacement file.
600      * @param file the replacement file.
601      */
602     public void replaceFile(IFile file) {
603         resetInput();
604
605         mEditedFile = file;
606         mConfigComposite.replaceFile(mEditedFile);
607     }
608
609     /**
610      * Resets the editor with a replacement file coming from a config change in the config
611      * selector.
612      * @param file the replacement file.
613      */
614     public void changeFileOnNewConfig(IFile file) {
615         resetInput();
616
617         mEditedFile = file;
618         mConfigComposite.changeFileOnNewConfig(mEditedFile);
619     }
620
621     public void onTargetChange() {
622         resetInput();
623         mConfigComposite.onXmlModelLoaded();
624         onConfigurationChange();
625     }
626
627     public void onSdkChange() {
628         Sdk currentSdk = Sdk.getCurrent();
629         if (currentSdk != null) {
630             IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
631             if (target != null) {
632                 mConfigComposite.onSdkLoaded(target);
633                 onConfigurationChange();
634             }
635         }
636     }
637
638     /**
639      * Resets the editor's input and the viewer model.
640      */
641     private void resetInput() {
642         GraphicalViewer viewer = getGraphicalViewer();
643         viewer.setContents(getModel());
644
645         IEditorInput input = mLayoutEditor.getEditorInput();
646         setInput(input);
647     }
648
649
650     public Rectangle getBounds() {
651         return mConfigComposite.getScreenBounds();
652     }
653
654     /**
655      * Renders an Android View described by a {@link ViewElementDescriptor}.
656      * <p/>This uses the <code>wrap_content</code> mode for both <code>layout_width</code> and
657      * <code>layout_height</code>, and use the class name for the <code>text</code> attribute.
658      * @param descriptor the descriptor for the class to render.
659      * @return an ImageData containing the rendering or <code>null</code> if rendering failed.
660      */
661     public ImageData renderWidget(ViewElementDescriptor descriptor) {
662         if (mEditedFile == null) {
663             return null;
664         }
665
666         IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());
667         if (target == null) {
668             return null;
669         }
670
671         AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
672         if (data == null) {
673             return null;
674         }
675
676         LayoutBridge bridge = data.getLayoutBridge();
677
678         if (bridge.bridge != null) { // bridge can never be null.
679             ResourceManager resManager = ResourceManager.getInstance();
680
681             ProjectCallback projectCallback = null;
682             Map<String, Map<String, IResourceValue>> configuredProjectResources = null;
683             if (mEditedFile != null) {
684                 ProjectResources projectRes = resManager.getProjectResources(
685                         mEditedFile.getProject());
686                 projectCallback = new ProjectCallback(bridge.classLoader,
687                         projectRes, mEditedFile.getProject());
688
689                 // get the configured resources for the project
690                 // get the resources of the file's project.
691                 if (mConfiguredProjectRes == null && projectRes != null) {
692                     // make sure they are loaded
693                     projectRes.loadAll();
694
695                     // get the project resource values based on the current config
696                     mConfiguredProjectRes = projectRes.getConfiguredResources(
697                             mConfigComposite.getCurrentConfig());
698                 }
699
700                 configuredProjectResources = mConfiguredProjectRes;
701             } else {
702                 // we absolutely need a Map of configured project resources.
703                 configuredProjectResources = new HashMap<String, Map<String, IResourceValue>>();
704             }
705
706             // get the framework resources
707             Map<String, Map<String, IResourceValue>> frameworkResources =
708                     getConfiguredFrameworkResources();
709
710             if (configuredProjectResources != null && frameworkResources != null) {
711                 // get the selected theme
712                 String theme = mConfigComposite.getTheme();
713                 if (theme != null) {
714                     // Render a single object as described by the ViewElementDescriptor.
715                     WidgetPullParser parser = new WidgetPullParser(descriptor);
716                     ILayoutResult result = computeLayout(bridge, parser,
717                             null /* projectKey */,
718                             1 /* width */, 1 /* height */, true /* renderFullSize */,
719                             160 /*density*/, 160.f /*xdpi*/, 160.f /*ydpi*/, theme,
720                             mConfigComposite.isProjectTheme(),
721                             configuredProjectResources, frameworkResources, projectCallback,
722                             null /* logger */);
723
724                     // post rendering clean up
725                     bridge.cleanUp();
726
727                     // update the UiElementNode with the layout info.
728                     if (result.getSuccess() == ILayoutResult.SUCCESS) {
729                         BufferedImage largeImage = result.getImage();
730
731                         // we need to resize it to the actual widget size, and convert it into
732                         // an SWT image object.
733                         int width = result.getRootView().getRight();
734                         int height = result.getRootView().getBottom();
735                         Raster raster = largeImage.getData(new java.awt.Rectangle(width, height));
736                         int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData();
737
738                         ImageData imageData = new ImageData(width, height, 32,
739                                 new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
740
741                         imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
742
743                         return imageData;
744                     }
745                 }
746             }
747         }
748         return null;
749     }
750
751     /**
752      * Callback for XML model changed. Only update/recompute the layout if the editor is visible
753      */
754     public void onXmlModelChanged() {
755         if (mLayoutEditor.isGraphicalEditorActive()) {
756             doXmlReload(true /* force */);
757             recomputeLayout();
758         } else {
759             mNeedsXmlReload = true;
760         }
761     }
762
763     /**
764      * Actually performs the XML reload
765      * @see #onXmlModelChanged()
766      */
767     private void doXmlReload(boolean force) {
768         if (force || mNeedsXmlReload) {
769             GraphicalViewer viewer = getGraphicalViewer();
770
771             // try to preserve the selection before changing the content
772             SelectionManager selMan = viewer.getSelectionManager();
773             ISelection selection = selMan.getSelection();
774
775             try {
776                 viewer.setContents(getModel());
777             } finally {
778                 selMan.setSelection(selection);
779             }
780
781             mNeedsXmlReload = false;
782         }
783     }
784
785     public UiDocumentNode getModel() {
786         return mLayoutEditor.getUiRootNode();
787     }
788
789     public void reloadPalette() {
790         PaletteFactory.createPaletteRoot(mPaletteRoot, mLayoutEditor.getTargetData());
791     }
792
793     /**
794      * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
795      * <p/>If there is no match, notify the user.
796      */
797     public void onConfigurationChange() {
798         mConfiguredFrameworkRes = mConfiguredProjectRes = null;
799
800         if (mEditedFile == null || mConfigComposite.getEditedConfig() == null) {
801             return;
802         }
803
804         // Before doing the normal process, test for the following case.
805         // - the editor is being opened (or reset for a new input)
806         // - the file being opened is not the best match for any possible configuration
807         // - another random compatible config was chosen in the config composite.
808         // The result is that match will not be the file being edited, but because this is not
809         // due to a config change, we should not trigger opening the actual best match (also,
810         // because the editor is still opening the MatchingStrategy woudln't answer true
811         // and the best match file would open in a different editor).
812         // So the solution is that if the editor is being created, we just call recomputeLayout
813         // without looking for a better matching layout file.
814         if (mLayoutEditor.isCreatingPages()) {
815             recomputeLayout();
816         } else {
817             // get the resources of the file's project.
818             ProjectResources resources = ResourceManager.getInstance().getProjectResources(
819                     mEditedFile.getProject());
820
821             // from the resources, look for a matching file
822             ResourceFile match = null;
823             if (resources != null) {
824                 match = resources.getMatchingFile(mEditedFile.getName(),
825                                                   ResourceFolderType.LAYOUT,
826                                                   mConfigComposite.getCurrentConfig());
827             }
828
829             if (match != null) {
830                 // since this is coming from Eclipse, this is always an instance of IFileWrapper
831                 IFileWrapper iFileWrapper = (IFileWrapper) match.getFile();
832                 IFile iFile = iFileWrapper.getIFile();
833                 if (iFile.equals(mEditedFile) == false) {
834                     try {
835                         // tell the editor that the next replacement file is due to a config change.
836                         mLayoutEditor.setNewFileOnConfigChange(true);
837
838                         // ask the IDE to open the replacement file.
839                         IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), iFile);
840
841                         // we're done!
842                         return;
843                     } catch (PartInitException e) {
844                         // FIXME: do something!
845                     }
846                 }
847
848                 // at this point, we have not opened a new file.
849
850                 // Store the state in the current file
851                 mConfigComposite.storeState();
852
853                 // Even though the layout doesn't change, the config changed, and referenced
854                 // resources need to be updated.
855                 recomputeLayout();
856             } else {
857                 // display the error.
858                 FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
859                 String message = String.format(
860                         "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.",
861                         currentConfig.toDisplayString(),
862                         currentConfig.getFolderName(ResourceFolderType.LAYOUT),
863                         mEditedFile.getName());
864                 showErrorInEditor(message);
865             }
866         }
867     }
868
869     public void onThemeChange() {
870         // Store the state in the current file
871         mConfigComposite.storeState();
872
873         recomputeLayout();
874     }
875
876     public void onClippingChange() {
877         recomputeLayout();
878     }
879
880
881     public void onCreate() {
882         LayoutCreatorDialog dialog = new LayoutCreatorDialog(mParent.getShell(),
883                 mEditedFile.getName(), mConfigComposite.getCurrentConfig());
884         if (dialog.open() == Dialog.OK) {
885             final FolderConfiguration config = new FolderConfiguration();
886             dialog.getConfiguration(config);
887
888             createAlternateLayout(config);
889         }
890     }
891
892     /**
893      * Recomputes the layout with the help of layoutlib.
894      */
895     public void recomputeLayout() {
896         doXmlReload(false /* force */);
897         try {
898             // check that the resource exists. If the file is opened but the project is closed
899             // or deleted for some reason (changed from outside of eclipse), then this will
900             // return false;
901             if (mEditedFile.exists() == false) {
902                 String message = String.format("Resource '%1$s' does not exist.",
903                         mEditedFile.getFullPath().toString());
904
905                 showErrorInEditor(message);
906
907                 return;
908             }
909
910             IProject iProject = mEditedFile.getProject();
911
912             if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {
913                 String message = String.format("%1$s is out of sync. Please refresh.",
914                         mEditedFile.getName());
915
916                 showErrorInEditor(message);
917
918                 // also print it in the error console.
919                 AdtPlugin.printErrorToConsole(iProject.getName(), message);
920                 return;
921             }
922
923             Sdk currentSdk = Sdk.getCurrent();
924             if (currentSdk != null) {
925                 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
926                 if (target == null) {
927                     showErrorInEditor("The project target is not set.");
928                     return;
929                 }
930
931                 AndroidTargetData data = currentSdk.getTargetData(target);
932                 if (data == null) {
933                     // It can happen that the workspace refreshes while the target is loading its
934                     // data, which could trigger a redraw of the opened layout if some resources
935                     // changed while Eclipse is closed.
936                     // In this case data could be null, but this is not an error.
937                     // We can just silently return, as all the opened editors are automatically
938                     // refreshed once the SDK finishes loading.
939                     LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);
940                     switch (targetLoadStatus) {
941                         case LOADING:
942                             showErrorInEditor(String.format(
943                                     "The project target (%1$s) is still loading.\n%2$s will refresh automatically once the process is finished.",
944                                     target.getName(), mEditedFile.getName()));
945                             break;
946                         case FAILED: // known failure
947                         case LOADED: // success but data isn't loaded?!?!
948                             showErrorInEditor(String.format(
949                                     "The project target (%s) was not properly loaded.",
950                                     target.getName()));
951                             break;
952                     }
953                     return;
954                 }
955
956                 // check there is actually a model (maybe the file is empty).
957                 UiDocumentNode model = getModel();
958
959                 if (model.getUiChildren().size() == 0) {
960                     showErrorInEditor("No Xml content. Go to the Outline view and add nodes.");
961                     return;
962                 }
963
964                 LayoutBridge bridge = data.getLayoutBridge();
965
966                 if (bridge.bridge != null) { // bridge can never be null.
967                     ResourceManager resManager = ResourceManager.getInstance();
968
969                     ProjectResources projectRes = resManager.getProjectResources(iProject);
970                     if (projectRes == null) {
971                         return;
972                     }
973
974                     // get the resources of the file's project.
975                     Map<String, Map<String, IResourceValue>> configuredProjectRes =
976                         getConfiguredProjectResources();
977
978                     // get the framework resources
979                     Map<String, Map<String, IResourceValue>> frameworkResources =
980                         getConfiguredFrameworkResources();
981
982                     if (configuredProjectRes != null && frameworkResources != null) {
983                         if (mProjectCallback == null) {
984                             mProjectCallback = new ProjectCallback(
985                                     bridge.classLoader, projectRes, iProject);
986                         }
987
988                         if (mLogger == null) {
989                             mLogger = new ILayoutLog() {
990                                 public void error(String message) {
991                                     AdtPlugin.printErrorToConsole(mEditedFile.getName(), message);
992                                 }
993
994                                 public void error(Throwable error) {
995                                     String message = error.getMessage();
996                                     if (message == null) {
997                                         message = error.getClass().getName();
998                                     }
999
1000                                     PrintStream ps = new PrintStream(AdtPlugin.getErrorStream());
1001                                     error.printStackTrace(ps);
1002                                 }
1003
1004                                 public void warning(String message) {
1005                                     AdtPlugin.printToConsole(mEditedFile.getName(), message);
1006                                 }
1007                             };
1008                         }
1009
1010                         // get the selected theme
1011                         String theme = mConfigComposite.getTheme();
1012                         if (theme != null) {
1013                             // Compute the layout
1014                             Rectangle rect = getBounds();
1015
1016                             int width = rect.width;
1017                             int height = rect.height;
1018                             if (mUseExplodeMode) {
1019                                 // compute how many padding in x and y will bump the screen size
1020                                 List<UiElementNode> children = getModel().getUiChildren();
1021                                 if (children.size() == 1) {
1022                                     ExplodedRenderingHelper helper = new ExplodedRenderingHelper(
1023                                             children.get(0).getXmlNode(), iProject);
1024
1025                                     // there are 2 paddings for each view
1026                                     // left and right, or top and bottom.
1027                                     int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;
1028
1029                                     width += helper.getWidthPadding() * paddingValue;
1030                                     height += helper.getHeightPadding() * paddingValue;
1031                                 }
1032                             }
1033
1034                             int density = mConfigComposite.getDensity().getDpiValue();
1035                             float xdpi = mConfigComposite.getXDpi();
1036                             float ydpi = mConfigComposite.getYDpi();
1037                             boolean isProjectTheme = mConfigComposite.isProjectTheme();
1038
1039                             UiElementPullParser parser = new UiElementPullParser(getModel(),
1040                                     mUseExplodeMode, density, xdpi, iProject);
1041
1042                             ILayoutResult result = computeLayout(bridge, parser,
1043                                     iProject /* projectKey */,
1044                                     width, height, !mConfigComposite.getClipping(),
1045                                     density, xdpi, ydpi,
1046                                     theme, isProjectTheme,
1047                                     configuredProjectRes, frameworkResources, mProjectCallback,
1048                                     mLogger);
1049
1050                             // post-rendering clean up
1051                             bridge.cleanUp();
1052
1053                             // update the UiElementNode with the layout info.
1054                             if (result.getSuccess() == ILayoutResult.SUCCESS) {
1055                                 model.setEditData(result.getImage());
1056
1057                                 updateNodeWithBounds(result.getRootView());
1058                             } else {
1059                                 String message = result.getErrorMessage();
1060
1061                                 // Reset the edit data for all the nodes.
1062                                 resetNodeBounds(model);
1063
1064                                 if (message != null) {
1065                                     // set the error in the top element.
1066                                     model.setEditData(message);
1067                                 }
1068                             }
1069
1070                             model.refreshUi();
1071                         }
1072                     }
1073                 } else {
1074                     // SDK is loaded but not the layout library!
1075                     String message = null;
1076                     // check whether the bridge managed to load, or not
1077                     if (bridge.status == LoadStatus.LOADING) {
1078                         message = String.format(
1079                                 "Eclipse is loading framework information and the Layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.",
1080                                 mEditedFile.getName());
1081                     } else {
1082                         message = String.format("Eclipse failed to load the framework information and the Layout library!");
1083                     }
1084                     showErrorInEditor(message);
1085                 }
1086             } else {
1087                 String message = String.format(
1088                         "Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",
1089                         mEditedFile.getName());
1090
1091                 showErrorInEditor(message);
1092             }
1093         } finally {
1094             // no matter the result, we are done doing the recompute based on the latest
1095             // resource/code change.
1096             mNeedsRecompute = false;
1097         }
1098     }
1099
1100     private void showErrorInEditor(String message) {
1101         // get the model to display the error directly in the editor
1102         UiDocumentNode model = getModel();
1103
1104         // Reset the edit data for all the nodes.
1105         resetNodeBounds(model);
1106
1107         if (message != null) {
1108             // set the error in the top element.
1109             model.setEditData(message);
1110         }
1111
1112         model.refreshUi();
1113     }
1114
1115     private void resetNodeBounds(UiElementNode node) {
1116         node.setEditData(null);
1117
1118         List<UiElementNode> children = node.getUiChildren();
1119         for (UiElementNode child : children) {
1120             resetNodeBounds(child);
1121         }
1122     }
1123
1124     private void updateNodeWithBounds(ILayoutViewInfo r) {
1125         if (r != null) {
1126             // update the node itself, as the viewKey is the XML node in this implementation.
1127             Object viewKey = r.getViewKey();
1128             if (viewKey instanceof UiElementNode) {
1129                 Rectangle bounds = new Rectangle(r.getLeft(), r.getTop(),
1130                         r.getRight()-r.getLeft(), r.getBottom() - r.getTop());
1131
1132                 ((UiElementNode)viewKey).setEditData(bounds);
1133             }
1134
1135             // and then its children.
1136             ILayoutViewInfo[] children = r.getChildren();
1137             if (children != null) {
1138                 for (ILayoutViewInfo child : children) {
1139                     updateNodeWithBounds(child);
1140                 }
1141             }
1142         }
1143     }
1144
1145     /*
1146      * Called when the file changes triggered a redraw of the layout
1147      */
1148     public void reloadLayout(ChangeFlags flags, boolean libraryChanged) {
1149         boolean recompute = false;
1150
1151         if (flags.rClass) {
1152             recompute = true;
1153             ProjectResources projectRes = getProjectResources();
1154             if (projectRes != null) {
1155                 projectRes.resetDynamicIds();
1156             }
1157         }
1158
1159         if (flags.localeList) {
1160             // the locale list *potentially* changed so we update the locale list in the
1161             // config composite.
1162             // However there's no recompute, as it could not be needed (for instance a new layout)
1163             // If a resource that's not a layout changed this will trigger a recompute anyway.
1164             mParent.getDisplay().asyncExec(mLocaleUpdaterFromUiRunnable);
1165         }
1166
1167         if (flags.resources || (libraryChanged && flags.layout)) {
1168             recompute = true;
1169
1170             // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache.
1171
1172             // force a reparse in case a value XML file changed.
1173             mConfiguredProjectRes = null;
1174
1175             // clear the cache in the bridge in case a bitmap/9-patch changed.
1176             IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());
1177             if (target != null) {
1178
1179                 AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
1180                 if (data != null) {
1181                     LayoutBridge bridge = data.getLayoutBridge();
1182
1183                     if (bridge.bridge != null) {
1184                         bridge.bridge.clearCaches(mEditedFile.getProject());
1185                     }
1186                 }
1187             }
1188
1189         }
1190
1191         if (flags.code) {
1192             // only recompute if the custom view loader was used to load some code.
1193             if (mProjectCallback != null && mProjectCallback.isUsed()) {
1194                 mProjectCallback = null;
1195                 recompute = true;
1196             }
1197         }
1198
1199         if (recompute) {
1200             mParent.getDisplay().asyncExec(mConditionalRecomputeRunnable);
1201         }
1202     }
1203
1204     /**
1205      * Responds to a page change that made the Graphical editor page the activated page.
1206      */
1207     public void activated() {
1208         if (mNeedsRecompute || mNeedsXmlReload) {
1209             recomputeLayout();
1210         }
1211     }
1212
1213     /**
1214      * Responds to a page change that made the Graphical editor page the deactivated page
1215      */
1216     public void deactivated() {
1217         // nothing to be done here for now.
1218     }
1219
1220     public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {
1221         if (mConfiguredFrameworkRes == null) {
1222             ProjectResources frameworkRes = getFrameworkResources();
1223
1224             if (frameworkRes == null) {
1225                 AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
1226             } else {
1227                 // get the framework resource values based on the current config
1228                 mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
1229                         mConfigComposite.getCurrentConfig());
1230             }
1231         }
1232
1233         return mConfiguredFrameworkRes;
1234     }
1235
1236     public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {
1237         if (mConfiguredProjectRes == null) {
1238             ProjectResources project = getProjectResources();
1239
1240             // make sure they are loaded
1241             project.loadAll();
1242
1243             // get the project resource values based on the current config
1244             mConfiguredProjectRes = project.getConfiguredResources(
1245                     mConfigComposite.getCurrentConfig());
1246         }
1247
1248         return mConfiguredProjectRes;
1249     }
1250
1251     /**
1252      * Returns a {@link ProjectResources} for the framework resources.
1253      * @return the framework resources or null if not found.
1254      */
1255     public ProjectResources getFrameworkResources() {
1256         if (mEditedFile != null) {
1257             Sdk currentSdk = Sdk.getCurrent();
1258             if (currentSdk != null) {
1259                 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
1260
1261                 if (target != null) {
1262                     AndroidTargetData data = currentSdk.getTargetData(target);
1263
1264                     if (data != null) {
1265                         return data.getFrameworkResources();
1266                     }
1267                 }
1268             }
1269         }
1270
1271         return null;
1272     }
1273
1274     public ProjectResources getProjectResources() {
1275         if (mEditedFile != null) {
1276             ResourceManager manager = ResourceManager.getInstance();
1277             return manager.getProjectResources(mEditedFile.getProject());
1278         }
1279
1280         return null;
1281     }
1282
1283     /**
1284      * Creates a new layout file from the specified {@link FolderConfiguration}.
1285      */
1286     private void createAlternateLayout(final FolderConfiguration config) {
1287         new Job("Create Alternate Resource") {
1288             @Override
1289             protected IStatus run(IProgressMonitor monitor) {
1290                 // get the folder name
1291                 String folderName = config.getFolderName(ResourceFolderType.LAYOUT);
1292                 try {
1293
1294                     // look to see if it exists.
1295                     // get the res folder
1296                     IFolder res = (IFolder)mEditedFile.getParent().getParent();
1297                     String path = res.getLocation().toOSString();
1298
1299                     File newLayoutFolder = new File(path + File.separator + folderName);
1300                     if (newLayoutFolder.isFile()) {
1301                         // this should not happen since aapt would have complained
1302                         // before, but if one disable the automatic build, this could
1303                         // happen.
1304                         String message = String.format("File 'res/%1$s' is in the way!",
1305                                 folderName);
1306
1307                         AdtPlugin.displayError("Layout Creation", message);
1308
1309                         return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
1310                     } else if (newLayoutFolder.exists() == false) {
1311                         // create it.
1312                         newLayoutFolder.mkdir();
1313                     }
1314
1315                     // now create the file
1316                     File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +
1317                                 File.separator + mEditedFile.getName());
1318
1319                     newLayoutFile.createNewFile();
1320
1321                     InputStream input = mEditedFile.getContents();
1322
1323                     FileOutputStream fos = new FileOutputStream(newLayoutFile);
1324
1325                     byte[] data = new byte[512];
1326                     int count;
1327                     while ((count = input.read(data)) != -1) {
1328                         fos.write(data, 0, count);
1329                     }
1330
1331                     input.close();
1332                     fos.close();
1333
1334                     // refreshes the res folder to show up the new
1335                     // layout folder (if needed) and the file.
1336                     // We use a progress monitor to catch the end of the refresh
1337                     // to trigger the edit of the new file.
1338                     res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {
1339                         public void done() {
1340                             mParent.getDisplay().asyncExec(new Runnable() {
1341                                 public void run() {
1342                                     onConfigurationChange();
1343                                 }
1344                             });
1345                         }
1346
1347                         public void beginTask(String name, int totalWork) {
1348                             // pass
1349                         }
1350
1351                         public void internalWorked(double work) {
1352                             // pass
1353                         }
1354
1355                         public boolean isCanceled() {
1356                             // pass
1357                             return false;
1358                         }
1359
1360                         public void setCanceled(boolean value) {
1361                             // pass
1362                         }
1363
1364                         public void setTaskName(String name) {
1365                             // pass
1366                         }
1367
1368                         public void subTask(String name) {
1369                             // pass
1370                         }
1371
1372                         public void worked(int work) {
1373                             // pass
1374                         }
1375                     });
1376                 } catch (IOException e2) {
1377                     String message = String.format(
1378                             "Failed to create File 'res/%1$s/%2$s' : %3$s",
1379                             folderName, mEditedFile.getName(), e2.getMessage());
1380
1381                     AdtPlugin.displayError("Layout Creation", message);
1382
1383                     return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
1384                             message, e2);
1385                 } catch (CoreException e2) {
1386                     String message = String.format(
1387                             "Failed to create File 'res/%1$s/%2$s' : %3$s",
1388                             folderName, mEditedFile.getName(), e2.getMessage());
1389
1390                     AdtPlugin.displayError("Layout Creation", message);
1391
1392                     return e2.getStatus();
1393                 }
1394
1395                 return Status.OK_STATUS;
1396
1397             }
1398         }.schedule();
1399     }
1400
1401     /**
1402      * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
1403      * the implementation API level.
1404      */
1405     @SuppressWarnings("deprecation")
1406     private static ILayoutResult computeLayout(LayoutBridge bridge,
1407             IXmlPullParser layoutDescription, Object projectKey,
1408             int screenWidth, int screenHeight, boolean renderFullSize,
1409             int density, float xdpi, float ydpi,
1410             String themeName, boolean isProjectTheme,
1411             Map<String, Map<String, IResourceValue>> projectResources,
1412             Map<String, Map<String, IResourceValue>> frameworkResources,
1413             IProjectCallback projectCallback, ILayoutLog logger) {
1414
1415         if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) {
1416             // newest API with support for "render full height"
1417             // TODO: link boolean to UI.
1418             return bridge.bridge.computeLayout(layoutDescription,
1419                     projectKey, screenWidth, screenHeight, renderFullSize,
1420                     density, xdpi, ydpi,
1421                     themeName, isProjectTheme,
1422                     projectResources, frameworkResources, projectCallback,
1423                     logger);
1424         } else if (bridge.apiLevel == 3) {
1425             // newer api with density support.
1426             return bridge.bridge.computeLayout(layoutDescription,
1427                     projectKey, screenWidth, screenHeight, density, xdpi, ydpi,
1428                     themeName, isProjectTheme,
1429                     projectResources, frameworkResources, projectCallback,
1430                     logger);
1431         } else if (bridge.apiLevel == 2) {
1432             // api with boolean for separation of project/framework theme
1433             return bridge.bridge.computeLayout(layoutDescription,
1434                     projectKey, screenWidth, screenHeight, themeName, isProjectTheme,
1435                     projectResources, frameworkResources, projectCallback,
1436                     logger);
1437         } else {
1438             // oldest api with no density/dpi, and project theme boolean mixed
1439             // into the theme name.
1440
1441             // change the string if it's a custom theme to make sure we can
1442             // differentiate them
1443             if (isProjectTheme) {
1444                 themeName = "*" + themeName; //$NON-NLS-1$
1445             }
1446
1447             return bridge.bridge.computeLayout(layoutDescription,
1448                     projectKey, screenWidth, screenHeight, themeName,
1449                     projectResources, frameworkResources, projectCallback,
1450                     logger);
1451         }
1452     }
1453
1454     public boolean hasOutline() {
1455         return mUseOutlineMode;
1456     }
1457 }