• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
17 
18 import static com.android.SdkConstants.ANDROID_URI;
19 import static com.android.SdkConstants.ATTR_ID;
20 
21 import com.android.annotations.NonNull;
22 import com.android.ide.common.api.INode;
23 import com.android.ide.common.api.RuleAction;
24 import com.android.ide.common.api.RuleAction.Choices;
25 import com.android.ide.common.api.RuleAction.Separator;
26 import com.android.ide.common.api.RuleAction.Toggle;
27 import com.android.ide.common.layout.BaseViewRule;
28 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
29 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
30 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
31 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
32 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
33 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
34 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
35 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
36 import com.android.sdklib.devices.Device;
37 import com.android.sdklib.devices.Screen;
38 import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
39 import com.google.common.base.Strings;
40 
41 import org.eclipse.core.resources.IFile;
42 import org.eclipse.core.resources.IMarker;
43 import org.eclipse.jface.window.Window;
44 import org.eclipse.swt.SWT;
45 import org.eclipse.swt.events.SelectionAdapter;
46 import org.eclipse.swt.events.SelectionEvent;
47 import org.eclipse.swt.graphics.Image;
48 import org.eclipse.swt.graphics.Point;
49 import org.eclipse.swt.graphics.Rectangle;
50 import org.eclipse.swt.layout.GridData;
51 import org.eclipse.swt.layout.GridLayout;
52 import org.eclipse.swt.widgets.Composite;
53 import org.eclipse.swt.widgets.Display;
54 import org.eclipse.swt.widgets.Event;
55 import org.eclipse.swt.widgets.Listener;
56 import org.eclipse.swt.widgets.Menu;
57 import org.eclipse.swt.widgets.MenuItem;
58 import org.eclipse.swt.widgets.ToolBar;
59 import org.eclipse.swt.widgets.ToolItem;
60 import org.eclipse.ui.ISharedImages;
61 import org.eclipse.ui.PlatformUI;
62 
63 import java.net.URL;
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.List;
67 
68 /**
69  * Toolbar shown at the top of the layout editor, which adds a number of context-sensitive
70  * layout actions (as well as zooming controls on the right).
71  */
72 public class LayoutActionBar extends Composite {
73     private GraphicalEditorPart mEditor;
74     private ToolBar mLayoutToolBar;
75     private ToolBar mLintToolBar;
76     private ToolBar mZoomToolBar;
77     private ToolItem mZoomRealSizeButton;
78     private ToolItem mZoomOutButton;
79     private ToolItem mZoomResetButton;
80     private ToolItem mZoomInButton;
81     private ToolItem mZoomFitButton;
82     private ToolItem mLintButton;
83     private List<RuleAction> mPrevActions;
84 
85     /**
86      * Creates a new {@link LayoutActionBar} and adds it to the given parent.
87      *
88      * @param parent the parent composite to add the actions bar to
89      * @param style the SWT style to apply
90      * @param editor the associated layout editor
91      */
LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor)92     public LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor) {
93         super(parent, style | SWT.NO_FOCUS);
94         mEditor = editor;
95 
96         GridLayout layout = new GridLayout(3, false);
97         setLayout(layout);
98 
99         mLayoutToolBar = new ToolBar(this, /*SWT.WRAP |*/ SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
100         mLayoutToolBar.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
101         mZoomToolBar = createZoomControls();
102         mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false));
103         mLintToolBar = createLintControls();
104 
105         GridData lintData = new GridData(SWT.END, SWT.BEGINNING, false, false);
106         lintData.exclude = true;
107         mLintToolBar.setLayoutData(lintData);
108     }
109 
110     @Override
dispose()111     public void dispose() {
112         super.dispose();
113         mPrevActions = null;
114     }
115 
116     /** Updates the layout contents based on the current selection */
updateSelection()117     void updateSelection() {
118         NodeProxy parent = null;
119         LayoutCanvas canvas = mEditor.getCanvasControl();
120         SelectionManager selectionManager = canvas.getSelectionManager();
121         List<SelectionItem> selections = selectionManager.getSelections();
122         if (selections.size() > 0) {
123             // TODO: better handle multi-selection -- maybe we should disable it or
124             // something.
125             // What if you select children with different parents? Of different types?
126             // etc.
127             NodeProxy node = selections.get(0).getNode();
128             if (node != null && node.getParent() != null) {
129                 parent = (NodeProxy) node.getParent();
130             }
131         }
132 
133         if (parent == null) {
134             // Show the background's properties
135             CanvasViewInfo root = canvas.getViewHierarchy().getRoot();
136             if (root == null) {
137                 return;
138             }
139             parent = canvas.getNodeFactory().create(root);
140             selections = Collections.emptyList();
141         }
142 
143         RulesEngine engine = mEditor.getRulesEngine();
144         List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>();
145         for (SelectionItem item : selections) {
146             selectedNodes.add(item.getNode());
147         }
148         List<RuleAction> actions = new ArrayList<RuleAction>();
149         engine.callAddLayoutActions(actions, parent, selectedNodes);
150 
151         // Place actions in the correct order (the actions may come from different
152         // rules and should be merged properly via sorting keys)
153         Collections.sort(actions);
154 
155         // Add in actions for the child as well, if there is exactly one.
156         // These are not merged into the parent list of actions; they are appended
157         // at the end.
158         int index = -1;
159         String label = null;
160         if (selectedNodes.size() == 1) {
161             List<RuleAction> itemActions = new ArrayList<RuleAction>();
162             NodeProxy selectedNode = selectedNodes.get(0);
163             engine.callAddLayoutActions(itemActions, selectedNode, null);
164             if (itemActions.size() > 0) {
165                 Collections.sort(itemActions);
166 
167                 if (!(itemActions.get(0) instanceof RuleAction.Separator)) {
168                     actions.add(RuleAction.createSeparator(0));
169                 }
170                 label = selectedNode.getStringAttr(ANDROID_URI, ATTR_ID);
171                 if (label != null) {
172                     label = BaseViewRule.stripIdPrefix(label);
173                     index = actions.size();
174                 }
175                 actions.addAll(itemActions);
176             }
177         }
178 
179         if (!updateActions(actions)) {
180             updateToolbar(actions, index, label);
181         }
182         mPrevActions = actions;
183     }
184 
185     /** Update the toolbar widgets */
updateToolbar(final List<RuleAction> actions, final int labelIndex, final String label)186     private void updateToolbar(final List<RuleAction> actions, final int labelIndex,
187             final String label) {
188         if (mLayoutToolBar == null || mLayoutToolBar.isDisposed()) {
189             return;
190         }
191         for (ToolItem c : mLayoutToolBar.getItems()) {
192             c.dispose();
193         }
194         mLayoutToolBar.pack();
195         addActions(actions, labelIndex, label);
196         mLayoutToolBar.pack();
197         mLayoutToolBar.layout();
198     }
199 
200     /**
201      * Attempts to update the existing toolbar actions, if the action list is
202      * similar to the current list. Returns false if this cannot be done and the
203      * contents must be replaced.
204      */
updateActions(@onNull List<RuleAction> actions)205     private boolean updateActions(@NonNull List<RuleAction> actions) {
206         List<RuleAction> before = mPrevActions;
207         List<RuleAction> after = actions;
208 
209         if (before == null) {
210             return false;
211         }
212 
213         if (!before.equals(after) || after.size() > mLayoutToolBar.getItemCount()) {
214             return false;
215         }
216 
217         int actionIndex = 0;
218         for (int i = 0, max = mLayoutToolBar.getItemCount(); i < max; i++) {
219             ToolItem item = mLayoutToolBar.getItem(i);
220             int style = item.getStyle();
221             Object data = item.getData();
222             if (data != null) {
223                 // One action can result in multiple toolbar items (e.g. a choice action
224                 // can result in multiple radio buttons), so we've have to replace all of
225                 // them with the corresponding new action
226                 RuleAction prevAction = before.get(actionIndex);
227                 while (prevAction != data) {
228                     actionIndex++;
229                     if (actionIndex == before.size()) {
230                         return false;
231                     }
232                     prevAction = before.get(actionIndex);
233                     if (prevAction == data) {
234                         break;
235                     } else if (!(prevAction instanceof RuleAction.Separator)) {
236                         return false;
237                     }
238                 }
239                 RuleAction newAction = after.get(actionIndex);
240                 assert newAction.equals(prevAction); // Maybe I can do this lazily instead?
241 
242                 // Update action binding to the new action
243                 item.setData(newAction);
244 
245                 // Sync button states: the checked state is not considered part of
246                 // RuleAction equality
247                 if ((style & SWT.CHECK) != 0) {
248                     assert newAction instanceof Toggle;
249                     Toggle toggle = (Toggle) newAction;
250                     item.setSelection(toggle.isChecked());
251                 } else if ((style & SWT.RADIO) != 0) {
252                     assert newAction instanceof Choices;
253                     Choices choices = (Choices) newAction;
254                     String current = choices.getCurrent();
255                     String id = (String) item.getData(ATTR_ID);
256                     boolean selected = Strings.nullToEmpty(current).equals(id);
257                     item.setSelection(selected);
258                 }
259             } else {
260                 // Must be a separator, or a label (which we insert for nested widgets)
261                 assert (style & SWT.SEPARATOR) != 0 || !item.getText().isEmpty() : item;
262             }
263         }
264 
265         return true;
266     }
267 
addActions(List<RuleAction> actions, int labelIndex, String label)268     private void addActions(List<RuleAction> actions, int labelIndex, String label) {
269         if (actions.size() > 0) {
270             // Flag used to indicate that if there are any actions -after- this, it
271             // should be separated from this current action (we don't unconditionally
272             // add a separator at the end of these groups in case there are no more
273             // actions at the end so that we don't have a trailing separator)
274             boolean needSeparator = false;
275 
276             int index = 0;
277             for (RuleAction action : actions) {
278                 if (index == labelIndex) {
279                     final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH);
280                     button.setText(label);
281                     needSeparator = false;
282                 }
283                 index++;
284 
285                 if (action instanceof Separator) {
286                     addSeparator(mLayoutToolBar);
287                     needSeparator = false;
288                     continue;
289                 } else if (needSeparator) {
290                     addSeparator(mLayoutToolBar);
291                     needSeparator = false;
292                 }
293 
294                 if (action instanceof RuleAction.Choices) {
295                     RuleAction.Choices choices = (Choices) action;
296                     if (!choices.isRadio()) {
297                         addDropdown(choices);
298                     } else {
299                         addSeparator(mLayoutToolBar);
300                         addRadio(choices);
301                         needSeparator = true;
302                     }
303                 } else if (action instanceof RuleAction.Toggle) {
304                     addToggle((Toggle) action);
305                 } else {
306                     addPlainAction(action);
307                 }
308             }
309         }
310     }
311 
312     /** Add a separator to the toolbar, unless there already is one there at the end already */
addSeparator(ToolBar toolBar)313     private static void addSeparator(ToolBar toolBar) {
314         int n = toolBar.getItemCount();
315         if (n > 0 && (toolBar.getItem(n - 1).getStyle() & SWT.SEPARATOR) == 0) {
316             ToolItem separator = new ToolItem(toolBar, SWT.SEPARATOR);
317             separator.setWidth(15);
318         }
319     }
320 
addToggle(Toggle toggle)321     private void addToggle(Toggle toggle) {
322         final ToolItem button = new ToolItem(mLayoutToolBar, SWT.CHECK);
323 
324         URL iconUrl = toggle.getIconUrl();
325         String title = toggle.getTitle();
326         if (iconUrl != null) {
327             button.setImage(IconFactory.getInstance().getIcon(iconUrl));
328             button.setToolTipText(title);
329         } else {
330             button.setText(title);
331         }
332         button.setData(toggle);
333 
334         button.addSelectionListener(new SelectionAdapter() {
335             @Override
336             public void widgetSelected(SelectionEvent e) {
337                 Toggle toggle = (Toggle) button.getData();
338                 toggle.getCallback().action(toggle, getSelectedNodes(),
339                         toggle.getId(), button.getSelection());
340                 updateSelection();
341             }
342         });
343         if (toggle.isChecked()) {
344             button.setSelection(true);
345         }
346     }
347 
getSelectedNodes()348     private List<INode> getSelectedNodes() {
349         List<SelectionItem> selections =
350                 mEditor.getCanvasControl().getSelectionManager().getSelections();
351         List<INode> nodes = new ArrayList<INode>(selections.size());
352         for (SelectionItem item : selections) {
353             nodes.add(item.getNode());
354         }
355 
356         return nodes;
357     }
358 
359 
addPlainAction(RuleAction menuAction)360     private void addPlainAction(RuleAction menuAction) {
361         final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH);
362 
363         URL iconUrl = menuAction.getIconUrl();
364         String title = menuAction.getTitle();
365         if (iconUrl != null) {
366             button.setImage(IconFactory.getInstance().getIcon(iconUrl));
367             button.setToolTipText(title);
368         } else {
369             button.setText(title);
370         }
371         button.setData(menuAction);
372 
373         button.addSelectionListener(new SelectionAdapter() {
374             @Override
375             public void widgetSelected(SelectionEvent e) {
376                 RuleAction menuAction = (RuleAction) button.getData();
377                 menuAction.getCallback().action(menuAction, getSelectedNodes(), menuAction.getId(),
378                         false);
379                 updateSelection();
380             }
381         });
382     }
383 
addRadio(RuleAction.Choices choices)384     private void addRadio(RuleAction.Choices choices) {
385         List<URL> icons = choices.getIconUrls();
386         List<String> titles = choices.getTitles();
387         List<String> ids = choices.getIds();
388         String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$
389 
390         assert icons != null;
391         assert icons.size() == titles.size();
392 
393         for (int i = 0; i < icons.size(); i++) {
394             URL iconUrl = icons.get(i);
395             String title = titles.get(i);
396             final String id = ids.get(i);
397             final ToolItem item = new ToolItem(mLayoutToolBar, SWT.RADIO);
398             item.setToolTipText(title);
399             item.setImage(IconFactory.getInstance().getIcon(iconUrl));
400             item.setData(choices);
401             item.setData(ATTR_ID, id);
402             item.addSelectionListener(new SelectionAdapter() {
403                 @Override
404                 public void widgetSelected(SelectionEvent e) {
405                     if (item.getSelection()) {
406                         RuleAction.Choices choices = (Choices) item.getData();
407                         choices.getCallback().action(choices, getSelectedNodes(), id, null);
408                         updateSelection();
409                     }
410                 }
411             });
412             boolean selected = current.equals(id);
413             if (selected) {
414                 item.setSelection(true);
415             }
416         }
417     }
418 
addDropdown(RuleAction.Choices choices)419     private void addDropdown(RuleAction.Choices choices) {
420         final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN);
421         URL iconUrl = choices.getIconUrl();
422         if (iconUrl != null) {
423             combo.setImage(IconFactory.getInstance().getIcon(iconUrl));
424             combo.setToolTipText(choices.getTitle());
425         } else {
426             combo.setText(choices.getTitle());
427         }
428         combo.setData(choices);
429 
430         Listener menuListener = new Listener() {
431             @Override
432             public void handleEvent(Event event) {
433                 Menu menu = new Menu(mLayoutToolBar.getShell(), SWT.POP_UP);
434                 RuleAction.Choices choices = (Choices) combo.getData();
435                 List<URL> icons = choices.getIconUrls();
436                 List<String> titles = choices.getTitles();
437                 List<String> ids = choices.getIds();
438                 String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$
439 
440                 for (int i = 0; i < titles.size(); i++) {
441                     String title = titles.get(i);
442                     final String id = ids.get(i);
443                     URL itemIconUrl = icons != null && icons.size() > 0 ? icons.get(i) : null;
444                     MenuItem item = new MenuItem(menu, SWT.CHECK);
445                     item.setText(title);
446                     if (itemIconUrl != null) {
447                         Image itemIcon = IconFactory.getInstance().getIcon(itemIconUrl);
448                         item.setImage(itemIcon);
449                     }
450 
451                     boolean selected = id.equals(current);
452                     if (selected) {
453                         item.setSelection(true);
454                     }
455 
456                     item.addSelectionListener(new SelectionAdapter() {
457                         @Override
458                         public void widgetSelected(SelectionEvent e) {
459                             RuleAction.Choices choices = (Choices) combo.getData();
460                             choices.getCallback().action(choices, getSelectedNodes(), id, null);
461                             updateSelection();
462                         }
463                     });
464                 }
465 
466                 Rectangle bounds = combo.getBounds();
467                 Point location = new Point(bounds.x, bounds.y + bounds.height);
468                 location = combo.getParent().toDisplay(location);
469                 menu.setLocation(location.x, location.y);
470                 menu.setVisible(true);
471             }
472         };
473         combo.addListener(SWT.Selection, menuListener);
474     }
475 
476     // ---- Zoom Controls ----
477 
478     @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
createZoomControls()479     private ToolBar createZoomControls() {
480         ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
481 
482         IconFactory iconFactory = IconFactory.getInstance();
483         mZoomRealSizeButton = new ToolItem(toolBar, SWT.CHECK);
484         mZoomRealSizeButton.setToolTipText("Emulate Real Size");
485         mZoomRealSizeButton.setImage(iconFactory.getIcon("zoomreal")); //$NON-NLS-1$);
486         mZoomRealSizeButton.addSelectionListener(new SelectionAdapter() {
487             @Override
488             public void widgetSelected(SelectionEvent e) {
489                 boolean newState = mZoomRealSizeButton.getSelection();
490                 if (rescaleToReal(newState)) {
491                     mZoomOutButton.setEnabled(!newState);
492                     mZoomResetButton.setEnabled(!newState);
493                     mZoomInButton.setEnabled(!newState);
494                     mZoomFitButton.setEnabled(!newState);
495                 } else {
496                     mZoomRealSizeButton.setSelection(!newState);
497                 }
498             }
499         });
500 
501         mZoomFitButton = new ToolItem(toolBar, SWT.PUSH);
502         mZoomFitButton.setToolTipText("Zoom to Fit (0)");
503         mZoomFitButton.setImage(iconFactory.getIcon("zoomfit")); //$NON-NLS-1$);
504         mZoomFitButton.addSelectionListener(new SelectionAdapter() {
505             @Override
506             public void widgetSelected(SelectionEvent e) {
507                 rescaleToFit(true);
508             }
509         });
510 
511         mZoomResetButton = new ToolItem(toolBar, SWT.PUSH);
512         mZoomResetButton.setToolTipText("Reset Zoom to 100% (1)");
513         mZoomResetButton.setImage(iconFactory.getIcon("zoom100")); //$NON-NLS-1$);
514         mZoomResetButton.addSelectionListener(new SelectionAdapter() {
515             @Override
516             public void widgetSelected(SelectionEvent e) {
517                 resetScale();
518             }
519         });
520 
521         // Group zoom in/out separately
522         new ToolItem(toolBar, SWT.SEPARATOR);
523 
524         mZoomOutButton = new ToolItem(toolBar, SWT.PUSH);
525         mZoomOutButton.setToolTipText("Zoom Out (-)");
526         mZoomOutButton.setImage(iconFactory.getIcon("zoomminus")); //$NON-NLS-1$);
527         mZoomOutButton.addSelectionListener(new SelectionAdapter() {
528             @Override
529             public void widgetSelected(SelectionEvent e) {
530                 rescale(-1);
531             }
532         });
533 
534         mZoomInButton = new ToolItem(toolBar, SWT.PUSH);
535         mZoomInButton.setToolTipText("Zoom In (+)");
536         mZoomInButton.setImage(iconFactory.getIcon("zoomplus")); //$NON-NLS-1$);
537         mZoomInButton.addSelectionListener(new SelectionAdapter() {
538             @Override
539             public void widgetSelected(SelectionEvent e) {
540                 rescale(+1);
541             }
542         });
543 
544         return toolBar;
545     }
546 
547     @SuppressWarnings("unused") // SWT constructors have side effects, they are not unused
createLintControls()548     private ToolBar createLintControls() {
549         ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
550 
551         // Separate from adjacent toolbar
552         new ToolItem(toolBar, SWT.SEPARATOR);
553 
554         ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
555         mLintButton = new ToolItem(toolBar, SWT.PUSH);
556         mLintButton.setToolTipText("Show Lint Warnings for this Layout");
557         mLintButton.setImage(sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK));
558         mLintButton.addSelectionListener(new SelectionAdapter() {
559             @Override
560             public void widgetSelected(SelectionEvent e) {
561                 CommonXmlEditor editor = mEditor.getEditorDelegate().getEditor();
562                 IFile file = editor.getInputFile();
563                 if (file != null) {
564                     EclipseLintClient.showErrors(getShell(), file, editor);
565                 }
566             }
567         });
568 
569         return toolBar;
570     }
571 
572     /**
573      * Updates the lint indicator state in the given layout editor
574      */
updateErrorIndicator()575     public void updateErrorIndicator() {
576         updateErrorIndicator(mEditor.getEditedFile());
577     }
578 
579     /**
580      * Updates the lint indicator state for the given file
581      *
582      * @param file the file to show the indicator status for
583      */
updateErrorIndicator(IFile file)584     public void updateErrorIndicator(IFile file) {
585         IMarker[] markers = EclipseLintClient.getMarkers(file);
586         updateErrorIndicator(markers.length);
587     }
588 
589     /**
590      * Sets whether the action bar should show the "lint warnings" button
591      *
592      * @param hasLintWarnings whether there are lint errors to be shown
593      */
updateErrorIndicator(final int markerCount)594     private void updateErrorIndicator(final int markerCount) {
595         Display display = getDisplay();
596         if (display.getThread() != Thread.currentThread()) {
597             display.asyncExec(new Runnable() {
598                 @Override
599                 public void run() {
600                     if (!isDisposed()) {
601                         updateErrorIndicator(markerCount);
602                     }
603                 }
604             });
605             return;
606         }
607 
608         GridData layoutData = (GridData) mLintToolBar.getLayoutData();
609         Integer existing = (Integer) mLintToolBar.getData();
610         Integer current = Integer.valueOf(markerCount);
611         if (!current.equals(existing)) {
612             mLintToolBar.setData(current);
613             boolean layout = false;
614             boolean hasLintWarnings = markerCount > 0 && AdtPrefs.getPrefs().isLintOnSave();
615             if (layoutData.exclude == hasLintWarnings) {
616                 layoutData.exclude = !hasLintWarnings;
617                 mLintToolBar.setVisible(hasLintWarnings);
618                 layout = true;
619             }
620             if (markerCount > 0) {
621                 String iconName = "";
622                 switch (markerCount) {
623                     case 1: iconName = "lint1"; break;  //$NON-NLS-1$
624                     case 2: iconName = "lint2"; break;  //$NON-NLS-1$
625                     case 3: iconName = "lint3"; break;  //$NON-NLS-1$
626                     case 4: iconName = "lint4"; break;  //$NON-NLS-1$
627                     case 5: iconName = "lint5"; break;  //$NON-NLS-1$
628                     case 6: iconName = "lint6"; break;  //$NON-NLS-1$
629                     case 7: iconName = "lint7"; break;  //$NON-NLS-1$
630                     case 8: iconName = "lint8"; break;  //$NON-NLS-1$
631                     case 9: iconName = "lint9"; break;  //$NON-NLS-1$
632                     default: iconName = "lint9p"; break;//$NON-NLS-1$
633                 }
634                 mLintButton.setImage(IconFactory.getInstance().getIcon(iconName));
635             }
636             if (layout) {
637                 layout();
638             }
639             redraw();
640         }
641     }
642 
643     /**
644      * Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while
645      * emulating real size)
646      *
647      * @return true if zooming is allowed
648      */
isZoomingAllowed()649     boolean isZoomingAllowed() {
650         return mZoomInButton.isEnabled();
651     }
652 
isZoomingRealSize()653     boolean isZoomingRealSize() {
654         return mZoomRealSizeButton.getSelection();
655     }
656 
657     /**
658      * Rescales canvas.
659      * @param direction +1 for zoom in, -1 for zoom out
660      */
rescale(int direction)661     void rescale(int direction) {
662         LayoutCanvas canvas = mEditor.getCanvasControl();
663         double s = canvas.getScale();
664 
665         if (direction > 0) {
666             s = s * 1.2;
667         } else {
668             s = s / 1.2;
669         }
670 
671         // Some operations are faster if the zoom is EXACTLY 1.0 rather than ALMOST 1.0.
672         // (This is because there is a fast-path when image copying and the scale is 1.0;
673         // in that case it does not have to do any scaling).
674         //
675         // If you zoom out 10 times and then back in 10 times, small rounding errors mean
676         // that you end up with a scale=1.0000000000000004. In the cases, when you get close
677         // to 1.0, just make the zoom an exact 1.0.
678         if (Math.abs(s-1.0) < 0.0001) {
679             s = 1.0;
680         }
681 
682         canvas.setScale(s, true /*redraw*/);
683     }
684 
685     /**
686      * Reset the canvas scale to 100%
687      */
resetScale()688     void resetScale() {
689         mEditor.getCanvasControl().setScale(1, true /*redraw*/);
690     }
691 
692     /**
693      * Reset the canvas scale to best fit (so content is as large as possible without scrollbars)
694      */
rescaleToFit(boolean onlyZoomOut)695     void rescaleToFit(boolean onlyZoomOut) {
696         mEditor.getCanvasControl().setFitScale(onlyZoomOut, true /*allowZoomIn*/);
697     }
698 
rescaleToReal(boolean real)699     boolean rescaleToReal(boolean real) {
700         if (real) {
701             return computeAndSetRealScale(true /*redraw*/);
702         } else {
703             // reset the scale to 100%
704             mEditor.getCanvasControl().setScale(1, true /*redraw*/);
705             return true;
706         }
707     }
708 
computeAndSetRealScale(boolean redraw)709     boolean computeAndSetRealScale(boolean redraw) {
710         // compute average dpi of X and Y
711         ConfigurationChooser chooser = mEditor.getConfigurationChooser();
712         Configuration config = chooser.getConfiguration();
713         Device device = config.getDevice();
714         Screen screen = device.getDefaultHardware().getScreen();
715         double dpi = (screen.getXdpi() + screen.getYdpi()) / 2.;
716 
717         // get the monitor dpi
718         float monitor = AdtPrefs.getPrefs().getMonitorDensity();
719         if (monitor == 0.f) {
720             ResolutionChooserDialog dialog = new ResolutionChooserDialog(chooser.getShell());
721             if (dialog.open() == Window.OK) {
722                 monitor = dialog.getDensity();
723                 AdtPrefs.getPrefs().setMonitorDensity(monitor);
724             } else {
725                 return false;
726             }
727         }
728 
729         mEditor.getCanvasControl().setScale(monitor / dpi, redraw);
730         return true;
731     }
732 }
733