OSDN Git Service

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