OSDN Git Service

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