Home | History | Annotate | Download | only in gle2
      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      */
     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
    111     public void dispose() {
    112         super.dispose();
    113         mPrevActions = null;
    114     }
    115 
    116     /** Updates the layout contents based on the current selection */
    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 */
    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      */
    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 
    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 */
    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 
    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 
    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 
    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 
    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 
    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
    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
    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      */
    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      */
    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      */
    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      */
    649     boolean isZoomingAllowed() {
    650         return mZoomInButton.isEnabled();
    651     }
    652 
    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      */
    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      */
    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      */
    695     void rescaleToFit(boolean onlyZoomOut) {
    696         mEditor.getCanvasControl().setFitScale(onlyZoomOut, true /*allowZoomIn*/);
    697     }
    698 
    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 
    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