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