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