• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.layout.LayoutReloadMonitor.ILayoutReloadListener;
21 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
22 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
23 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
24 import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;
25 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
26 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
27 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
28 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
29 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;
30 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
31 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
32 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
33 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
34 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
35 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
36 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
37 import com.android.layoutlib.api.ILayoutBridge;
38 import com.android.layoutlib.api.ILayoutLog;
39 import com.android.layoutlib.api.ILayoutResult;
40 import com.android.layoutlib.api.IProjectCallback;
41 import com.android.layoutlib.api.IResourceValue;
42 import com.android.layoutlib.api.IXmlPullParser;
43 import com.android.sdklib.IAndroidTarget;
44 
45 import org.eclipse.core.resources.IFile;
46 import org.eclipse.core.resources.IFolder;
47 import org.eclipse.core.resources.IProject;
48 import org.eclipse.core.resources.IResource;
49 import org.eclipse.core.runtime.CoreException;
50 import org.eclipse.core.runtime.IProgressMonitor;
51 import org.eclipse.core.runtime.IStatus;
52 import org.eclipse.core.runtime.Status;
53 import org.eclipse.core.runtime.jobs.Job;
54 import org.eclipse.draw2d.geometry.Rectangle;
55 import org.eclipse.gef.ui.parts.SelectionSynchronizer;
56 import org.eclipse.jface.dialogs.Dialog;
57 import org.eclipse.swt.SWT;
58 import org.eclipse.swt.custom.SashForm;
59 import org.eclipse.swt.custom.StyledText;
60 import org.eclipse.swt.dnd.Clipboard;
61 import org.eclipse.swt.layout.GridData;
62 import org.eclipse.swt.layout.GridLayout;
63 import org.eclipse.swt.widgets.Composite;
64 import org.eclipse.swt.widgets.Display;
65 import org.eclipse.ui.IEditorInput;
66 import org.eclipse.ui.IEditorSite;
67 import org.eclipse.ui.PartInitException;
68 import org.eclipse.ui.ide.IDE;
69 import org.eclipse.ui.part.EditorPart;
70 import org.eclipse.ui.part.FileEditorInput;
71 
72 import java.io.File;
73 import java.io.FileOutputStream;
74 import java.io.IOException;
75 import java.io.InputStream;
76 import java.io.PrintStream;
77 import java.util.Map;
78 
79 /**
80  * Graphical layout editor part, version 2.
81  *
82  * @since GLE2
83  *
84  * TODO List:
85  * - display error icon
86  * - finish palette (see palette's todo list)
87  * - finish canvas (see canva's todo list)
88  * - completly rethink the property panel
89  * - link to the existing outline editor (prolly reuse/adapt for simplier model and will need
90  *   to adapt the selection synchronizer.)
91  */
92 public class GraphicalEditorPart extends EditorPart implements IGraphicalLayoutEditor {
93 
94     /*
95      * Useful notes:
96      * To understand Drag'n'drop:
97      *   http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
98      */
99 
100     /** Reference to the layout editor */
101     private final LayoutEditor mLayoutEditor;
102 
103     /** reference to the file being edited. */
104     private IFile mEditedFile;
105 
106     /** The current clipboard. Must be disposed later. */
107     private Clipboard mClipboard;
108 
109     /** The configuration composite at the top of the layout editor. */
110     private ConfigurationComposite mConfigComposite;
111 
112     /** The sash that splits the palette from the canvas. */
113     private SashForm mSashPalette;
114     private SashForm mSashError;
115 
116     /** The palette displayed on the left of the sash. */
117     private PaletteComposite mPalette;
118 
119     /** The layout canvas displayed o the right of the sash. */
120     private LayoutCanvas mLayoutCanvas;
121 
122     private StyledText mErrorLabel;
123 
124     /** The {@link FolderConfiguration} being edited. */
125     private FolderConfiguration mEditedConfig;
126 
127     private Map<String, Map<String, IResourceValue>> mConfiguredFrameworkRes;
128     private Map<String, Map<String, IResourceValue>> mConfiguredProjectRes;
129     private ProjectCallback mProjectCallback;
130     private ILayoutLog mLogger;
131 
132     private boolean mNeedsXmlReload = false;
133     private boolean mNeedsRecompute = false;
134 
135     private TargetListener mTargetListener;
136 
137     private ConfigListener mConfigListener;
138 
139     private ReloadListener mReloadListener;
140 
141 
GraphicalEditorPart(LayoutEditor layoutEditor)142     public GraphicalEditorPart(LayoutEditor layoutEditor) {
143         mLayoutEditor = layoutEditor;
144         setPartName("Graphical Layout");
145     }
146 
147     // ------------------------------------
148     // Methods overridden from base classes
149     //------------------------------------
150 
151     /**
152      * Initializes the editor part with a site and input.
153      * {@inheritDoc}
154      */
155     @Override
init(IEditorSite site, IEditorInput input)156     public void init(IEditorSite site, IEditorInput input) throws PartInitException {
157         setSite(site);
158         useNewEditorInput(input);
159 
160         if (mTargetListener == null) {
161             mTargetListener = new TargetListener();
162             AdtPlugin.getDefault().addTargetListener(mTargetListener);
163         }
164 
165         if (mReloadListener == null) {
166             mReloadListener = new ReloadListener();
167             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);
168         }
169     }
170 
171     /**
172      * Reloads this editor, by getting the new model from the {@link LayoutEditor}.
173      */
reloadEditor()174     public void reloadEditor() {
175         IEditorInput input = mLayoutEditor.getEditorInput();
176 
177         try {
178             useNewEditorInput(input);
179         } catch (PartInitException e) {
180             // really this shouldn't happen! Log it in case it happens.
181             mEditedFile = null;
182             AdtPlugin.log(e, "Input is not of type FileEditorInput: %1$s",          //$NON-NLS-1$
183                     input == null ? "null" : input.toString());                     //$NON-NLS-1$
184         }
185     }
186 
useNewEditorInput(IEditorInput input)187     private void useNewEditorInput(IEditorInput input) throws PartInitException {
188         // The contract of init() mentions we need to fail if we can't understand the input.
189         if (!(input instanceof FileEditorInput)) {
190             throw new PartInitException("Input is not of type FileEditorInput: " +  //$NON-NLS-1$
191                     input == null ? "null" : input.toString());                     //$NON-NLS-1$
192         }
193 
194         FileEditorInput fileInput = (FileEditorInput)input;
195         mEditedFile = fileInput.getFile();
196     }
197 
198     @Override
createPartControl(Composite parent)199     public void createPartControl(Composite parent) {
200 
201         Display d = parent.getDisplay();
202         mClipboard = new Clipboard(d);
203 
204         GridLayout gl = new GridLayout(1, false);
205         parent.setLayout(gl);
206         gl.marginHeight = gl.marginWidth = 0;
207 
208         // create the top part for the configuration control
209         mConfigListener = new ConfigListener();
210         mConfigComposite = new ConfigurationComposite(mConfigListener, parent, SWT.BORDER);
211         mConfigComposite.updateUIFromResources();
212 
213         mSashPalette = new SashForm(parent, SWT.HORIZONTAL);
214         mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH));
215 
216         mPalette = new PaletteComposite(mSashPalette);
217 
218         mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER);
219         mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
220 
221         mLayoutCanvas = new LayoutCanvas(mSashError, SWT.NONE);
222         mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY);
223         mErrorLabel.setEditable(false);
224         mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
225         mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
226 
227         mSashPalette.setWeights(new int[] { 20, 80 });
228         mSashError.setWeights(new int[] { 80, 20 });
229         mSashError.setMaximizedControl(mLayoutCanvas);
230 
231         // Initialize the state
232         reloadPalette();
233     }
234 
235     /**
236      * Switches the stack to display the error label and hide the canvas.
237      * @param errorFormat The new error to display if not null.
238      * @param parameters String.format parameters for the error format.
239      */
displayError(String errorFormat, Object...parameters)240     private void displayError(String errorFormat, Object...parameters) {
241         if (errorFormat != null) {
242             mErrorLabel.setText(String.format(errorFormat, parameters));
243         }
244         mSashError.setMaximizedControl(null);
245     }
246 
247     /** Displays the canvas and hides the error label. */
hideError()248     private void hideError() {
249         mSashError.setMaximizedControl(mLayoutCanvas);
250     }
251 
252     @Override
dispose()253     public void dispose() {
254         if (mTargetListener != null) {
255             AdtPlugin.getDefault().removeTargetListener(mTargetListener);
256             mTargetListener = null;
257         }
258 
259         if (mReloadListener != null) {
260             LayoutReloadMonitor.getMonitor().removeListener(mReloadListener);
261             mReloadListener = null;
262         }
263 
264         if (mClipboard != null) {
265             mClipboard.dispose();
266             mClipboard = null;
267         }
268 
269         super.dispose();
270     }
271 
272     /**
273      * Listens to changes from the Configuration UI banner and triggers layout rendering when
274      * changed. Also provide the Configuration UI with the list of resources/layout to display.
275      */
276     private class ConfigListener implements IConfigListener {
277 
278         /**
279          * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
280          * <p/>If there is no match, notify the user.
281          */
onConfigurationChange()282         public void onConfigurationChange() {
283             mConfiguredFrameworkRes = mConfiguredProjectRes = null;
284 
285             if (mEditedFile == null || mEditedConfig == null) {
286                 return;
287             }
288 
289             // get the resources of the file's project.
290             ProjectResources resources = ResourceManager.getInstance().getProjectResources(
291                     mEditedFile.getProject());
292 
293             // from the resources, look for a matching file
294             ResourceFile match = null;
295             if (resources != null) {
296                 match = resources.getMatchingFile(mEditedFile.getName(),
297                                                   ResourceFolderType.LAYOUT,
298                                                   mConfigComposite.getCurrentConfig());
299             }
300 
301             if (match != null) {
302                 if (match.getFile().equals(mEditedFile) == false) {
303                     try {
304                         IDE.openEditor(
305                                 getSite().getWorkbenchWindow().getActivePage(),
306                                 match.getFile().getIFile());
307 
308                         // we're done!
309                         return;
310                     } catch (PartInitException e) {
311                         // FIXME: do something!
312                     }
313                 }
314 
315                 // at this point, we have not opened a new file.
316 
317                 // update the configuration icons with the new edited config.
318                 setConfiguration(mEditedConfig, false /*force*/);
319 
320                 // enable the create button if the current and edited config are not equals
321                 mConfigComposite.setEnabledCreate(
322                         mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
323 
324                 // Even though the layout doesn't change, the config changed, and referenced
325                 // resources need to be updated.
326                 recomputeLayout();
327             } else {
328                 // enable the Create button
329                 mConfigComposite.setEnabledCreate(true);
330 
331                 // display the error.
332                 FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
333                 displayError(
334                         "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.",
335                         currentConfig.toDisplayString(),
336                         currentConfig.getFolderName(ResourceFolderType.LAYOUT,
337                                 Sdk.getCurrent().getTarget(mEditedFile.getProject())),
338                         mEditedFile.getName());
339             }
340         }
341 
onThemeChange()342         public void onThemeChange() {
343             recomputeLayout();
344         }
345 
OnClippingChange()346         public void OnClippingChange() {
347             recomputeLayout();
348         }
349 
onCreate()350         public void onCreate() {
351             LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigComposite.getShell(),
352                     mEditedFile.getName(),
353                     Sdk.getCurrent().getTarget(mEditedFile.getProject()),
354                     mConfigComposite.getCurrentConfig());
355             if (dialog.open() == Dialog.OK) {
356                 final FolderConfiguration config = new FolderConfiguration();
357                 dialog.getConfiguration(config);
358 
359                 createAlternateLayout(config);
360             }
361         }
362 
getConfiguredFrameworkResources()363         public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {
364             if (mConfiguredFrameworkRes == null && mConfigComposite != null) {
365                 ProjectResources frameworkRes = getFrameworkResources();
366 
367                 if (frameworkRes == null) {
368                     AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
369                 } else {
370                     // get the framework resource values based on the current config
371                     mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
372                             mConfigComposite.getCurrentConfig());
373                 }
374             }
375 
376             return mConfiguredFrameworkRes;
377         }
378 
getConfiguredProjectResources()379         public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {
380             if (mConfiguredProjectRes == null && mConfigComposite != null) {
381                 ProjectResources project = getProjectResources();
382 
383                 // make sure they are loaded
384                 project.loadAll();
385 
386                 // get the project resource values based on the current config
387                 mConfiguredProjectRes = project.getConfiguredResources(
388                         mConfigComposite.getCurrentConfig());
389             }
390 
391             return mConfiguredProjectRes;
392         }
393 
394         /**
395          * Returns a {@link ProjectResources} for the framework resources.
396          * @return the framework resources or null if not found.
397          */
getFrameworkResources()398         public ProjectResources getFrameworkResources() {
399             if (mEditedFile != null) {
400                 Sdk currentSdk = Sdk.getCurrent();
401                 if (currentSdk != null) {
402                     IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
403 
404                     if (target != null) {
405                         AndroidTargetData data = currentSdk.getTargetData(target);
406 
407                         if (data != null) {
408                             return data.getFrameworkResources();
409                         }
410                     }
411                 }
412             }
413 
414             return null;
415         }
416 
getProjectResources()417         public ProjectResources getProjectResources() {
418             if (mEditedFile != null) {
419                 ResourceManager manager = ResourceManager.getInstance();
420                 return manager.getProjectResources(mEditedFile.getProject());
421             }
422 
423             return null;
424         }
425 
426         /**
427          * Creates a new layout file from the specified {@link FolderConfiguration}.
428          */
createAlternateLayout(final FolderConfiguration config)429         private void createAlternateLayout(final FolderConfiguration config) {
430             new Job("Create Alternate Resource") {
431                 @Override
432                 protected IStatus run(IProgressMonitor monitor) {
433                     // get the folder name
434                     String folderName = config.getFolderName(ResourceFolderType.LAYOUT,
435                             Sdk.getCurrent().getTarget(mEditedFile.getProject()));
436                     try {
437 
438                         // look to see if it exists.
439                         // get the res folder
440                         IFolder res = (IFolder)mEditedFile.getParent().getParent();
441                         String path = res.getLocation().toOSString();
442 
443                         File newLayoutFolder = new File(path + File.separator + folderName);
444                         if (newLayoutFolder.isFile()) {
445                             // this should not happen since aapt would have complained
446                             // before, but if one disable the automatic build, this could
447                             // happen.
448                             String message = String.format("File 'res/%1$s' is in the way!",
449                                     folderName);
450 
451                             AdtPlugin.displayError("Layout Creation", message);
452 
453                             return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
454                         } else if (newLayoutFolder.exists() == false) {
455                             // create it.
456                             newLayoutFolder.mkdir();
457                         }
458 
459                         // now create the file
460                         File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() +
461                                     File.separator + mEditedFile.getName());
462 
463                         newLayoutFile.createNewFile();
464 
465                         InputStream input = mEditedFile.getContents();
466 
467                         FileOutputStream fos = new FileOutputStream(newLayoutFile);
468 
469                         byte[] data = new byte[512];
470                         int count;
471                         while ((count = input.read(data)) != -1) {
472                             fos.write(data, 0, count);
473                         }
474 
475                         input.close();
476                         fos.close();
477 
478                         // refreshes the res folder to show up the new
479                         // layout folder (if needed) and the file.
480                         // We use a progress monitor to catch the end of the refresh
481                         // to trigger the edit of the new file.
482                         res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {
483                             public void done() {
484                                 mConfigComposite.getDisplay().asyncExec(new Runnable() {
485                                     public void run() {
486                                         onConfigurationChange();
487                                     }
488                                 });
489                             }
490 
491                             public void beginTask(String name, int totalWork) {
492                                 // pass
493                             }
494 
495                             public void internalWorked(double work) {
496                                 // pass
497                             }
498 
499                             public boolean isCanceled() {
500                                 // pass
501                                 return false;
502                             }
503 
504                             public void setCanceled(boolean value) {
505                                 // pass
506                             }
507 
508                             public void setTaskName(String name) {
509                                 // pass
510                             }
511 
512                             public void subTask(String name) {
513                                 // pass
514                             }
515 
516                             public void worked(int work) {
517                                 // pass
518                             }
519                         });
520                     } catch (IOException e2) {
521                         String message = String.format(
522                                 "Failed to create File 'res/%1$s/%2$s' : %3$s",
523                                 folderName, mEditedFile.getName(), e2.getMessage());
524 
525                         AdtPlugin.displayError("Layout Creation", message);
526 
527                         return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
528                                 message, e2);
529                     } catch (CoreException e2) {
530                         String message = String.format(
531                                 "Failed to create File 'res/%1$s/%2$s' : %3$s",
532                                 folderName, mEditedFile.getName(), e2.getMessage());
533 
534                         AdtPlugin.displayError("Layout Creation", message);
535 
536                         return e2.getStatus();
537                     }
538 
539                     return Status.OK_STATUS;
540 
541                 }
542             }.schedule();
543         }
544     }
545 
546     /**
547      * Listens to target changed in the current project, to trigger a new layout rendering.
548      */
549     private class TargetListener implements ITargetChangeListener {
550 
onProjectTargetChange(IProject changedProject)551         public void onProjectTargetChange(IProject changedProject) {
552             if (changedProject == getLayoutEditor().getProject()) {
553                 onTargetsLoaded();
554             }
555         }
556 
onTargetsLoaded()557         public void onTargetsLoaded() {
558             // because the SDK changed we must reset the configured framework resource.
559             mConfiguredFrameworkRes = null;
560 
561             mConfigComposite.updateUIFromResources();
562 
563             // updateUiFromFramework will reset language/region combo, so we must call
564             // setConfiguration after, or the settext on language/region will be lost.
565             if (mEditedConfig != null) {
566                 setConfiguration(mEditedConfig, false /*force*/);
567             }
568 
569             // make sure we remove the custom view loader, since its parent class loader is the
570             // bridge class loader.
571             mProjectCallback = null;
572 
573             recomputeLayout();
574         }
575     }
576 
577     /**
578      * Update the UI controls state with a given {@link FolderConfiguration}.
579      * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
580      * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
581      * the UI control is not modified. However if the value in the control is not the default value,
582      * a warning icon is shown.
583      * @param config The {@link FolderConfiguration} to set.
584      * @param force Whether the UI should be changed to exactly match the received configuration.
585      */
setConfiguration(FolderConfiguration config, boolean force)586     void setConfiguration(FolderConfiguration config, boolean force) {
587         mEditedConfig = config;
588         mConfiguredFrameworkRes = mConfiguredProjectRes = null;
589 
590         mConfigComposite.setConfiguration(config, force);
591 
592     }
593 
594     // ----------------
595 
596     /**
597      * Save operation in the Graphical Editor Part.
598      * <p/>
599      * In our workflow, the model is owned by the Structured XML Editor.
600      * The graphical layout editor just displays it -- thus we don't really
601      * save anything here.
602      * <p/>
603      * This must NOT call the parent editor part. At the contrary, the parent editor
604      * part will call this *after* having done the actual save operation.
605      * <p/>
606      * The only action this editor must do is mark the undo command stack as
607      * being no longer dirty.
608      */
609     @Override
doSave(IProgressMonitor monitor)610     public void doSave(IProgressMonitor monitor) {
611         // TODO implement a command stack
612 //        getCommandStack().markSaveLocation();
613 //        firePropertyChange(PROP_DIRTY);
614     }
615 
616     /**
617      * Save operation in the Graphical Editor Part.
618      * <p/>
619      * In our workflow, the model is owned by the Structured XML Editor.
620      * The graphical layout editor just displays it -- thus we don't really
621      * save anything here.
622      */
623     @Override
doSaveAs()624     public void doSaveAs() {
625         // pass
626     }
627 
628     /**
629      * In our workflow, the model is owned by the Structured XML Editor.
630      * The graphical layout editor just displays it -- thus we don't really
631      * save anything here.
632      */
633     @Override
isDirty()634     public boolean isDirty() {
635         return false;
636     }
637 
638     /**
639      * In our workflow, the model is owned by the Structured XML Editor.
640      * The graphical layout editor just displays it -- thus we don't really
641      * save anything here.
642      */
643     @Override
isSaveAsAllowed()644     public boolean isSaveAsAllowed() {
645         return false;
646     }
647 
648     @Override
setFocus()649     public void setFocus() {
650         // TODO Auto-generated method stub
651 
652     }
653 
654     /**
655      * Responds to a page change that made the Graphical editor page the activated page.
656      */
activated()657     public void activated() {
658         if (mNeedsRecompute || mNeedsXmlReload) {
659             recomputeLayout();
660         }
661     }
662 
663     /**
664      * Responds to a page change that made the Graphical editor page the deactivated page
665      */
deactivated()666     public void deactivated() {
667         // nothing to be done here for now.
668     }
669 
670     /**
671      * Sets the UI for the edition of a new file.
672      * @param configuration the configuration of the new file.
673      */
editNewFile(FolderConfiguration configuration)674     public void editNewFile(FolderConfiguration configuration) {
675         // update the configuration UI
676         setConfiguration(configuration, true /*force*/);
677 
678         // enable the create button if the current and edited config are not equals
679         mConfigComposite.setEnabledCreate(
680                 mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
681 
682         reloadConfigurationUi(false /*notifyListener*/);
683     }
684 
getClipboard()685     public Clipboard getClipboard() {
686         return mClipboard;
687     }
688 
getLayoutEditor()689     public LayoutEditor getLayoutEditor() {
690         return mLayoutEditor;
691     }
692 
getModel()693     public UiDocumentNode getModel() {
694         return mLayoutEditor.getUiRootNode();
695     }
696 
getSelectionSynchronizer()697     public SelectionSynchronizer getSelectionSynchronizer() {
698         // TODO Auto-generated method stub
699         return null;
700     }
701 
702     /**
703      * Callback for XML model changed. Only update/recompute the layout if the editor is visible
704      */
onXmlModelChanged()705     public void onXmlModelChanged() {
706         if (mLayoutEditor.isGraphicalEditorActive()) {
707             doXmlReload(true /* force */);
708             recomputeLayout();
709         } else {
710             mNeedsXmlReload = true;
711         }
712     }
713 
714     /**
715      * Actually performs the XML reload
716      * @see #onXmlModelChanged()
717      */
doXmlReload(boolean force)718     private void doXmlReload(boolean force) {
719         if (force || mNeedsXmlReload) {
720 
721             // TODO : update the mLayoutCanvas, preserving the current selection if possible.
722 
723 //            GraphicalViewer viewer = getGraphicalViewer();
724 //
725 //            // try to preserve the selection before changing the content
726 //            SelectionManager selMan = viewer.getSelectionManager();
727 //            ISelection selection = selMan.getSelection();
728 //
729 //            try {
730 //                viewer.setContents(getModel());
731 //            } finally {
732 //                selMan.setSelection(selection);
733 //            }
734 
735             mNeedsXmlReload = false;
736         }
737     }
738 
recomputeLayout()739     public void recomputeLayout() {
740         doXmlReload(false /* force */);
741         try {
742             // check that the resource exists. If the file is opened but the project is closed
743             // or deleted for some reason (changed from outside of eclipse), then this will
744             // return false;
745             if (mEditedFile.exists() == false) {
746                 displayError("Resource '%1$s' does not exist.",
747                              mEditedFile.getFullPath().toString());
748                 return;
749             }
750 
751             IProject iProject = mEditedFile.getProject();
752 
753             if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {
754                 String message = String.format("%1$s is out of sync. Please refresh.",
755                         mEditedFile.getName());
756 
757                 displayError(message);
758 
759                 // also print it in the error console.
760                 AdtPlugin.printErrorToConsole(iProject.getName(), message);
761                 return;
762             }
763 
764             Sdk currentSdk = Sdk.getCurrent();
765             if (currentSdk != null) {
766                 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
767                 if (target == null) {
768                     displayError("The project target is not set.");
769                     return;
770                 }
771 
772                 AndroidTargetData data = currentSdk.getTargetData(target);
773                 if (data == null) {
774                     // It can happen that the workspace refreshes while the SDK is loading its
775                     // data, which could trigger a redraw of the opened layout if some resources
776                     // changed while Eclipse is closed.
777                     // In this case data could be null, but this is not an error.
778                     // We can just silently return, as all the opened editors are automatically
779                     // refreshed once the SDK finishes loading.
780                     if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) {
781                         displayError("The project target (%s) was not properly loaded.",
782                                      target.getName());
783                     }
784                     return;
785                 }
786 
787                 // check there is actually a model (maybe the file is empty).
788                 UiDocumentNode model = getModel();
789 
790                 if (model.getUiChildren().size() == 0) {
791                     displayError("No Xml content. Go to the Outline view and add nodes.");
792                     return;
793                 }
794 
795                 LayoutBridge bridge = data.getLayoutBridge();
796 
797                 if (bridge.bridge != null) { // bridge can never be null.
798                     ResourceManager resManager = ResourceManager.getInstance();
799 
800                     ProjectResources projectRes = resManager.getProjectResources(iProject);
801                     if (projectRes == null) {
802                         displayError("Missing project resources.");
803                         return;
804                     }
805 
806                     // get the resources of the file's project.
807                     Map<String, Map<String, IResourceValue>> configuredProjectRes =
808                         mConfigListener.getConfiguredProjectResources();
809 
810                     // get the framework resources
811                     Map<String, Map<String, IResourceValue>> frameworkResources =
812                         mConfigListener.getConfiguredFrameworkResources();
813 
814                     if (configuredProjectRes != null && frameworkResources != null) {
815                         if (mProjectCallback == null) {
816                             mProjectCallback = new ProjectCallback(
817                                     bridge.classLoader, projectRes, iProject);
818                         }
819 
820                         if (mLogger == null) {
821                             mLogger = new ILayoutLog() {
822                                 public void error(String message) {
823                                     AdtPlugin.printErrorToConsole(mEditedFile.getName(), message);
824                                 }
825 
826                                 public void error(Throwable error) {
827                                     String message = error.getMessage();
828                                     if (message == null) {
829                                         message = error.getClass().getName();
830                                     }
831 
832                                     PrintStream ps = new PrintStream(AdtPlugin.getErrorStream());
833                                     error.printStackTrace(ps);
834                                 }
835 
836                                 public void warning(String message) {
837                                     AdtPlugin.printToConsole(mEditedFile.getName(), message);
838                                 }
839                             };
840                         }
841 
842                         // get the selected theme
843                         String theme = mConfigComposite.getTheme();
844                         if (theme != null) {
845 
846                             // Compute the layout
847                             UiElementPullParser parser = new UiElementPullParser(getModel());
848                             Rectangle rect = getBounds();
849                             boolean isProjectTheme = mConfigComposite.isProjectTheme();
850 
851                             int density = mConfigComposite.getDensity().getDpiValue();
852                             float xdpi = mConfigComposite.getXDpi();
853                             float ydpi = mConfigComposite.getYDpi();
854 
855                             ILayoutResult result = computeLayout(bridge, parser,
856                                     iProject /* projectKey */,
857                                     rect.width, rect.height, !mConfigComposite.getClipping(),
858                                     density, xdpi, ydpi,
859                                     theme, isProjectTheme,
860                                     configuredProjectRes, frameworkResources, mProjectCallback,
861                                     mLogger);
862 
863                             mLayoutCanvas.setResult(result);
864 
865                             // update the UiElementNode with the layout info.
866                             if (result.getSuccess() == ILayoutResult.SUCCESS) {
867                                 hideError();
868                             } else {
869                                 displayError(result.getErrorMessage());
870                             }
871 
872                             model.refreshUi();
873                         }
874                     }
875                 } else {
876                     // SDK is loaded but not the layout library!
877 
878                     // check whether the bridge managed to load, or not
879                     if (bridge.status == LoadStatus.LOADING) {
880                         displayError("Eclipse is loading framework information and the layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.",
881                                      mEditedFile.getName());
882                     } else {
883                         displayError("Eclipse failed to load the framework information and the layout library!");
884                     }
885                 }
886             } else {
887                 displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",
888                              mEditedFile.getName());
889             }
890         } finally {
891             // no matter the result, we are done doing the recompute based on the latest
892             // resource/code change.
893             mNeedsRecompute = false;
894         }
895     }
896 
897     /**
898      * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
899      * the implementation API level.
900      *
901      * Implementation detail: the bridge's computeLayout() method already returns a newly
902      * allocated ILayourResult.
903      */
904     @SuppressWarnings("deprecation")
computeLayout(LayoutBridge bridge, IXmlPullParser layoutDescription, Object projectKey, int screenWidth, int screenHeight, boolean renderFullSize, int density, float xdpi, float ydpi, String themeName, boolean isProjectTheme, Map<String, Map<String, IResourceValue>> projectResources, Map<String, Map<String, IResourceValue>> frameworkResources, IProjectCallback projectCallback, ILayoutLog logger)905     private static ILayoutResult computeLayout(LayoutBridge bridge,
906             IXmlPullParser layoutDescription, Object projectKey,
907             int screenWidth, int screenHeight, boolean renderFullSize,
908             int density, float xdpi, float ydpi,
909             String themeName, boolean isProjectTheme,
910             Map<String, Map<String, IResourceValue>> projectResources,
911             Map<String, Map<String, IResourceValue>> frameworkResources,
912             IProjectCallback projectCallback, ILayoutLog logger) {
913 
914         if (bridge.apiLevel >= ILayoutBridge.API_CURRENT) {
915             // newest API with support for "render full height"
916             // TODO: link boolean to UI.
917             return bridge.bridge.computeLayout(layoutDescription,
918                     projectKey, screenWidth, screenHeight, renderFullSize,
919                     density, xdpi, ydpi,
920                     themeName, isProjectTheme,
921                     projectResources, frameworkResources, projectCallback,
922                     logger);
923         } else if (bridge.apiLevel == 3) {
924             // newer api with density support.
925             return bridge.bridge.computeLayout(layoutDescription,
926                     projectKey, screenWidth, screenHeight, density, xdpi, ydpi,
927                     themeName, isProjectTheme,
928                     projectResources, frameworkResources, projectCallback,
929                     logger);
930         } else if (bridge.apiLevel == 2) {
931             // api with boolean for separation of project/framework theme
932             return bridge.bridge.computeLayout(layoutDescription,
933                     projectKey, screenWidth, screenHeight, themeName, isProjectTheme,
934                     projectResources, frameworkResources, projectCallback,
935                     logger);
936         } else {
937             // oldest api with no density/dpi, and project theme boolean mixed
938             // into the theme name.
939 
940             // change the string if it's a custom theme to make sure we can
941             // differentiate them
942             if (isProjectTheme) {
943                 themeName = "*" + themeName; //$NON-NLS-1$
944             }
945 
946             return bridge.bridge.computeLayout(layoutDescription,
947                     projectKey, screenWidth, screenHeight, themeName,
948                     projectResources, frameworkResources, projectCallback,
949                     logger);
950         }
951     }
952 
getBounds()953     public Rectangle getBounds() {
954         return mConfigComposite.getScreenBounds();
955     }
956 
reloadPalette()957     public void reloadPalette() {
958         if (mPalette != null) {
959             mPalette.reloadPalette(mLayoutEditor.getTargetData());
960         }
961     }
962 
reloadConfigurationUi(boolean notifyListener)963     public void reloadConfigurationUi(boolean notifyListener) {
964         // enable the clipping button if it's supported.
965         Sdk currentSdk = Sdk.getCurrent();
966         if (currentSdk != null) {
967             IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
968             AndroidTargetData data = currentSdk.getTargetData(target);
969             if (data != null) {
970                 LayoutBridge bridge = data.getLayoutBridge();
971                 mConfigComposite.reloadDevices(notifyListener);
972                 mConfigComposite.setClippingSupport(bridge.apiLevel >= 4);
973             }
974         }
975     }
976 
977     /**
978      * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
979      * created by {@link ElementCreateCommand#execute()}.
980      *
981      * @param uiNodeModel The {@link UiElementNode} to select.
982      */
selectModel(UiElementNode uiNodeModel)983     public void selectModel(UiElementNode uiNodeModel) {
984 
985         // TODO this method was useful for GLE1. We may not need it anymore now.
986 
987 //        GraphicalViewer viewer = getGraphicalViewer();
988 //
989 //        // Give focus to the graphical viewer (in case the outline has it)
990 //        viewer.getControl().forceFocus();
991 //
992 //        Object editPart = viewer.getEditPartRegistry().get(uiNodeModel);
993 //
994 //        if (editPart instanceof EditPart) {
995 //            viewer.select((EditPart)editPart);
996 //        }
997     }
998 
999     private class ReloadListener implements ILayoutReloadListener {
1000         /*
1001          * (non-Javadoc)
1002          * @see com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener#reloadLayout(boolean, boolean, boolean)
1003          *
1004          * Called when the file changes triggered a redraw of the layout
1005          */
reloadLayout(boolean codeChange, boolean rChange, boolean resChange)1006         public void reloadLayout(boolean codeChange, boolean rChange, boolean resChange) {
1007             boolean recompute = rChange;
1008 
1009             if (resChange) {
1010                 recompute = true;
1011 
1012                 // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache.
1013 
1014                 // force a reparse in case a value XML file changed.
1015                 mConfiguredProjectRes = null;
1016 
1017                 // clear the cache in the bridge in case a bitmap/9-patch changed.
1018                 IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject());
1019                 if (target != null) {
1020 
1021                     AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
1022                     if (data != null) {
1023                         LayoutBridge bridge = data.getLayoutBridge();
1024 
1025                         if (bridge.bridge != null) {
1026                             bridge.bridge.clearCaches(mEditedFile.getProject());
1027                         }
1028                     }
1029                 }
1030 
1031                 mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
1032                     public void run() {
1033                         mConfigComposite.updateUIFromResources();
1034                     }
1035                 });
1036             }
1037 
1038             if (codeChange) {
1039                 // only recompute if the custom view loader was used to load some code.
1040                 if (mProjectCallback != null && mProjectCallback.isUsed()) {
1041                     mProjectCallback = null;
1042                     recompute = true;
1043                 }
1044             }
1045 
1046             if (recompute) {
1047                 mLayoutCanvas.getDisplay().asyncExec(new Runnable() {
1048                     public void run() {
1049                         if (mLayoutEditor.isGraphicalEditorActive()) {
1050                             recomputeLayout();
1051                         } else {
1052                             mNeedsRecompute = true;
1053                         }
1054                     }
1055                 });
1056             }
1057         }
1058     }
1059 }
1060