• 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.ide.common.layout.LayoutConstants.ANDROID_URI;
19 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
20 
21 import com.android.ide.common.api.INode;
22 import com.android.ide.common.api.RuleAction;
23 import com.android.ide.common.api.RuleAction.Choices;
24 import com.android.ide.common.api.RuleAction.Separator;
25 import com.android.ide.common.api.RuleAction.Toggle;
26 import com.android.ide.common.layout.BaseViewRule;
27 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
28 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
29 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
30 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
31 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
32 import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
33 
34 import org.eclipse.jface.window.Window;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.events.SelectionAdapter;
37 import org.eclipse.swt.events.SelectionEvent;
38 import org.eclipse.swt.graphics.Image;
39 import org.eclipse.swt.graphics.Point;
40 import org.eclipse.swt.layout.GridData;
41 import org.eclipse.swt.layout.GridLayout;
42 import org.eclipse.swt.widgets.Composite;
43 import org.eclipse.swt.widgets.Event;
44 import org.eclipse.swt.widgets.Listener;
45 import org.eclipse.swt.widgets.Menu;
46 import org.eclipse.swt.widgets.MenuItem;
47 import org.eclipse.swt.widgets.ToolBar;
48 import org.eclipse.swt.widgets.ToolItem;
49 
50 import java.net.URL;
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
54 
55 /**
56  * Toolbar shown at the top of the layout editor, which adds a number of context-sensitive
57  * layout actions (as well as zooming controls on the right).
58  */
59 public class LayoutActionBar extends Composite {
60     private GraphicalEditorPart mEditor;
61     private ToolBar mLayoutToolBar;
62     private ToolBar mZoomToolBar;
63     private ToolItem mZoomRealSizeButton;
64     private ToolItem mZoomOutButton;
65     private ToolItem mZoomResetButton;
66     private ToolItem mZoomInButton;
67     private ToolItem mZoomFitButton;
68 
69     /**
70      * Creates a new {@link LayoutActionBar} and adds it to the given parent.
71      *
72      * @param parent the parent composite to add the actions bar to
73      * @param style the SWT style to apply
74      * @param editor the associated layout editor
75      */
LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor)76     public LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor) {
77         super(parent, style | SWT.NO_FOCUS);
78         mEditor = editor;
79 
80         GridLayout layout = new GridLayout(2, false);
81         setLayout(layout);
82 
83         mLayoutToolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
84         mLayoutToolBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false));
85         mZoomToolBar = createZoomControls();
86         mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
87     }
88 
89     /** Updates the layout contents based on the current selection */
updateSelection()90     void updateSelection() {
91         // Get rid of any previous children
92         for (ToolItem c : mLayoutToolBar.getItems()) {
93             c.dispose();
94         }
95         mLayoutToolBar.pack();
96 
97         NodeProxy parent = null;
98         LayoutCanvas canvas = mEditor.getCanvasControl();
99         SelectionManager selectionManager = canvas.getSelectionManager();
100         List<SelectionItem> selections = selectionManager.getSelections();
101         if (selections.size() > 0) {
102             // TODO: better handle multi-selection -- maybe we should disable it or
103             // something.
104             // What if you select children with different parents? Of different types?
105             // etc.
106             NodeProxy node = selections.get(0).getNode();
107             if (node != null && node.getParent() != null) {
108                 parent = (NodeProxy) node.getParent();
109             }
110         }
111 
112         if (parent == null) {
113             // Show the background's properties
114             CanvasViewInfo root = canvas.getViewHierarchy().getRoot();
115             if (root == null) {
116                 return;
117             }
118             parent = canvas.getNodeFactory().create(root);
119             selections = Collections.emptyList();
120         }
121 
122         RulesEngine engine = mEditor.getRulesEngine();
123         List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>();
124         for (SelectionItem item : selections) {
125             selectedNodes.add(item.getNode());
126         }
127         List<RuleAction> actions = new ArrayList<RuleAction>();
128         engine.callAddLayoutActions(actions, parent, selectedNodes);
129 
130         // Place actions in the correct order (the actions may come from different
131         // rules and should be merged properly via sorting keys)
132         Collections.sort(actions);
133 
134         // Add in actions for the child as well, if there is exactly one.
135         // These are not merged into the parent list of actions; they are appended
136         // at the end.
137         int index = -1;
138         String label = null;
139         if (selectedNodes.size() == 1) {
140             List<RuleAction> itemActions = new ArrayList<RuleAction>();
141             NodeProxy selectedNode = selectedNodes.get(0);
142             engine.callAddLayoutActions(itemActions, selectedNode, null);
143             if (itemActions.size() > 0) {
144                 Collections.sort(itemActions);
145 
146                 if (!(itemActions.get(0) instanceof RuleAction.Separator)) {
147                     actions.add(RuleAction.createSeparator(0));
148                 }
149                 label = selectedNode.getStringAttr(ANDROID_URI, ATTR_ID);
150                 if (label != null) {
151                     label = BaseViewRule.stripIdPrefix(label);
152                     index = actions.size();
153                 }
154                 actions.addAll(itemActions);
155             }
156         }
157 
158         addActions(actions, index, label);
159 
160         mLayoutToolBar.pack();
161         mLayoutToolBar.layout();
162     }
163 
addActions(List<RuleAction> actions, int labelIndex, String label)164     private void addActions(List<RuleAction> actions, int labelIndex, String label) {
165         if (actions.size() > 0) {
166             // Flag used to indicate that if there are any actions -after- this, it
167             // should be separated from this current action (we don't unconditionally
168             // add a separator at the end of these groups in case there are no more
169             // actions at the end so that we don't have a trailing separator)
170             boolean needSeparator = false;
171 
172             int index = 0;
173             for (RuleAction action : actions) {
174                 if (index == labelIndex) {
175                     final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH);
176                     button.setText(label);
177                     needSeparator = false;
178                 }
179                 index++;
180 
181                 if (action instanceof Separator) {
182                     addSeparator(mLayoutToolBar);
183                     needSeparator = false;
184                     continue;
185                 } else if (needSeparator) {
186                     addSeparator(mLayoutToolBar);
187                     needSeparator = false;
188                 }
189 
190                 if (action instanceof RuleAction.Choices) {
191                     RuleAction.Choices choices = (Choices) action;
192                     if (!choices.isRadio()) {
193                         addDropdown(choices);
194                     } else {
195                         addSeparator(mLayoutToolBar);
196                         addRadio(choices);
197                         needSeparator = true;
198                     }
199                 } else if (action instanceof RuleAction.Toggle) {
200                     addToggle((Toggle) action);
201                 } else {
202                     addPlainAction(action);
203                 }
204             }
205         }
206     }
207 
208     /** Add a separator to the toolbar, unless there already is one there at the end already */
addSeparator(ToolBar toolBar)209     private static void addSeparator(ToolBar toolBar) {
210         int n = toolBar.getItemCount();
211         if (n > 0 && (toolBar.getItem(n - 1).getStyle() & SWT.SEPARATOR) == 0) {
212             ToolItem separator = new ToolItem(toolBar, SWT.SEPARATOR);
213             separator.setWidth(15);
214         }
215     }
216 
addToggle(final Toggle toggle)217     private void addToggle(final Toggle toggle) {
218         final ToolItem button = new ToolItem(mLayoutToolBar, SWT.CHECK);
219 
220         URL iconUrl = toggle.getIconUrl();
221         String title = toggle.getTitle();
222         if (iconUrl != null) {
223             button.setImage(IconFactory.getInstance().getIcon(iconUrl));
224             button.setToolTipText(title);
225         } else {
226             button.setText(title);
227         }
228 
229         button.addSelectionListener(new SelectionAdapter() {
230             @Override
231             public void widgetSelected(SelectionEvent e) {
232                 toggle.getCallback().action(toggle, getSelectedNodes(),
233                         toggle.getId(), button.getSelection());
234                 updateSelection();
235             }
236         });
237         if (toggle.isChecked()) {
238             button.setSelection(true);
239         }
240     }
241 
getSelectedNodes()242     private List<INode> getSelectedNodes() {
243         List<SelectionItem> selections =
244                 mEditor.getCanvasControl().getSelectionManager().getSelections();
245         List<INode> nodes = new ArrayList<INode>(selections.size());
246         for (SelectionItem item : selections) {
247             nodes.add(item.getNode());
248         }
249 
250         return nodes;
251     }
252 
253 
addPlainAction(final RuleAction menuAction)254     private void addPlainAction(final RuleAction menuAction) {
255         final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH);
256 
257         URL iconUrl = menuAction.getIconUrl();
258         String title = menuAction.getTitle();
259         if (iconUrl != null) {
260             button.setImage(IconFactory.getInstance().getIcon(iconUrl));
261             button.setToolTipText(title);
262         } else {
263             button.setText(title);
264         }
265 
266         button.addSelectionListener(new SelectionAdapter() {
267             @Override
268             public void widgetSelected(SelectionEvent e) {
269                 menuAction.getCallback().action(menuAction, getSelectedNodes(), menuAction.getId(),
270                         false);
271                 updateSelection();
272             }
273         });
274     }
275 
addRadio(final RuleAction.Choices choices)276     private void addRadio(final RuleAction.Choices choices) {
277         List<URL> icons = choices.getIconUrls();
278         List<String> titles = choices.getTitles();
279         List<String> ids = choices.getIds();
280         String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$
281 
282         assert icons != null;
283         assert icons.size() == titles.size();
284 
285         for (int i = 0; i < icons.size(); i++) {
286             URL iconUrl = icons.get(i);
287             String title = titles.get(i);
288             final String id = ids.get(i);
289             final ToolItem item = new ToolItem(mLayoutToolBar, SWT.RADIO);
290             item.setToolTipText(title);
291             item.setImage(IconFactory.getInstance().getIcon(iconUrl));
292             item.addSelectionListener(new SelectionAdapter() {
293                 @Override
294                 public void widgetSelected(SelectionEvent e) {
295                     if (item.getSelection()) {
296                         choices.getCallback().action(choices, getSelectedNodes(), id, null);
297                         updateSelection();
298                     }
299                 }
300             });
301             boolean selected = current.equals(id);
302             if (selected) {
303                 item.setSelection(true);
304             }
305         }
306     }
307 
addDropdown(final RuleAction.Choices choices)308     private void addDropdown(final RuleAction.Choices choices) {
309         final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN);
310         URL iconUrl = choices.getIconUrl();
311         if (iconUrl != null) {
312             combo.setImage(IconFactory.getInstance().getIcon(iconUrl));
313             combo.setToolTipText(choices.getTitle());
314         } else {
315             combo.setText(choices.getTitle());
316         }
317 
318         Listener menuListener = new Listener() {
319             public void handleEvent(Event event) {
320                 // if (event.detail == SWT.ARROW) {
321                 Point point = new Point(event.x, event.y);
322                 point = combo.getDisplay().map(mLayoutToolBar, null, point);
323 
324                 Menu menu = new Menu(mLayoutToolBar.getShell(), SWT.POP_UP);
325 
326                 List<URL> icons = choices.getIconUrls();
327                 List<String> titles = choices.getTitles();
328                 List<String> ids = choices.getIds();
329                 String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$
330 
331                 for (int i = 0; i < titles.size(); i++) {
332                     String title = titles.get(i);
333                     final String id = ids.get(i);
334                     URL itemIconUrl = icons != null && icons.size() > 0 ? icons.get(i) : null;
335                     MenuItem item = new MenuItem(menu, SWT.CHECK);
336                     item.setText(title);
337                     if (itemIconUrl != null) {
338                         Image itemIcon = IconFactory.getInstance().getIcon(itemIconUrl);
339                         item.setImage(itemIcon);
340                     }
341 
342                     boolean selected = id.equals(current);
343                     if (selected) {
344                         item.setSelection(true);
345                     }
346 
347                     item.addSelectionListener(new SelectionAdapter() {
348                         @Override
349                         public void widgetSelected(SelectionEvent e) {
350                             choices.getCallback().action(choices, getSelectedNodes(), id, null);
351                             updateSelection();
352                         }
353                     });
354                 }
355 
356                 // TODO - how do I dispose of this?
357 
358                 menu.setLocation(point);
359                 menu.setVisible(true);
360             }
361         };
362         combo.addListener(SWT.Selection, menuListener);
363     }
364 
365     // ---- Zoom Controls ----
366 
createZoomControls()367     private ToolBar createZoomControls() {
368         ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
369 
370         IconFactory iconFactory = IconFactory.getInstance();
371         mZoomRealSizeButton = new ToolItem(toolBar, SWT.CHECK);
372         mZoomRealSizeButton.setToolTipText("Emulate Real Size");
373         mZoomRealSizeButton.setImage(iconFactory.getIcon("zoomreal")); //$NON-NLS-1$);
374         mZoomRealSizeButton.addSelectionListener(new SelectionAdapter() {
375             @Override
376             public void widgetSelected(SelectionEvent e) {
377                 boolean newState = mZoomRealSizeButton.getSelection();
378                 if (rescaleToReal(newState)) {
379                     mZoomOutButton.setEnabled(!newState);
380                     mZoomResetButton.setEnabled(!newState);
381                     mZoomInButton.setEnabled(!newState);
382                     mZoomFitButton.setEnabled(!newState);
383                 } else {
384                     mZoomRealSizeButton.setSelection(!newState);
385                 }
386             }
387         });
388 
389         mZoomFitButton = new ToolItem(toolBar, SWT.PUSH);
390         mZoomFitButton.setToolTipText("Zoom to Fit (0)");
391         mZoomFitButton.setImage(iconFactory.getIcon("zoomfit")); //$NON-NLS-1$);
392         mZoomFitButton.addSelectionListener(new SelectionAdapter() {
393             @Override
394             public void widgetSelected(SelectionEvent e) {
395                 rescaleToFit(true);
396             }
397         });
398 
399         mZoomResetButton = new ToolItem(toolBar, SWT.PUSH);
400         mZoomResetButton.setToolTipText("Reset Zoom to 100% (1)");
401         mZoomResetButton.setImage(iconFactory.getIcon("zoom100")); //$NON-NLS-1$);
402         mZoomResetButton.addSelectionListener(new SelectionAdapter() {
403             @Override
404             public void widgetSelected(SelectionEvent e) {
405                 resetScale();
406             }
407         });
408 
409         // Group zoom in/out separately
410         new ToolItem(toolBar, SWT.SEPARATOR);
411 
412         mZoomOutButton = new ToolItem(toolBar, SWT.PUSH);
413         mZoomOutButton.setToolTipText("Zoom Out (-)");
414         mZoomOutButton.setImage(iconFactory.getIcon("zoomminus")); //$NON-NLS-1$);
415         mZoomOutButton.addSelectionListener(new SelectionAdapter() {
416             @Override
417             public void widgetSelected(SelectionEvent e) {
418                 rescale(-1);
419             }
420         });
421 
422         mZoomInButton = new ToolItem(toolBar, SWT.PUSH);
423         mZoomInButton.setToolTipText("Zoom In (+)");
424         mZoomInButton.setImage(iconFactory.getIcon("zoomplus")); //$NON-NLS-1$);
425         mZoomInButton.addSelectionListener(new SelectionAdapter() {
426             @Override
427             public void widgetSelected(SelectionEvent e) {
428                 rescale(+1);
429             }
430         });
431 
432         return toolBar;
433     }
434 
435     /**
436      * Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while
437      * emulating real size)
438      *
439      * @return true if zooming is allowed
440      */
isZoomingAllowed()441     boolean isZoomingAllowed() {
442         return mZoomInButton.isEnabled();
443     }
444 
isZoomingRealSize()445     boolean isZoomingRealSize() {
446         return mZoomRealSizeButton.getSelection();
447     }
448 
449     /**
450      * Rescales canvas.
451      * @param direction +1 for zoom in, -1 for zoom out
452      */
rescale(int direction)453     void rescale(int direction) {
454         LayoutCanvas canvas = mEditor.getCanvasControl();
455         double s = canvas.getScale();
456 
457         if (direction > 0) {
458             s = s * 1.2;
459         } else {
460             s = s / 1.2;
461         }
462 
463         // Some operations are faster if the zoom is EXACTLY 1.0 rather than ALMOST 1.0.
464         // (This is because there is a fast-path when image copying and the scale is 1.0;
465         // in that case it does not have to do any scaling).
466         //
467         // If you zoom out 10 times and then back in 10 times, small rounding errors mean
468         // that you end up with a scale=1.0000000000000004. In the cases, when you get close
469         // to 1.0, just make the zoom an exact 1.0.
470         if (Math.abs(s-1.0) < 0.0001) {
471             s = 1.0;
472         }
473 
474         canvas.setScale(s, true /*redraw*/);
475     }
476 
477     /**
478      * Reset the canvas scale to 100%
479      */
resetScale()480     void resetScale() {
481         mEditor.getCanvasControl().setScale(1, true /*redraw*/);
482     }
483 
484     /**
485      * Reset the canvas scale to best fit (so content is as large as possible without scrollbars)
486      */
rescaleToFit(boolean onlyZoomOut)487     void rescaleToFit(boolean onlyZoomOut) {
488         mEditor.getCanvasControl().setFitScale(onlyZoomOut);
489     }
490 
rescaleToReal(boolean real)491     boolean rescaleToReal(boolean real) {
492         if (real) {
493             return computeAndSetRealScale(true /*redraw*/);
494         } else {
495             // reset the scale to 100%
496             mEditor.getCanvasControl().setScale(1, true /*redraw*/);
497             return true;
498         }
499     }
500 
computeAndSetRealScale(boolean redraw)501     boolean computeAndSetRealScale(boolean redraw) {
502         // compute average dpi of X and Y
503         ConfigurationComposite config = mEditor.getConfigurationComposite();
504         float dpi = (config.getXDpi() + config.getYDpi()) / 2.f;
505 
506         // get the monitor dpi
507         float monitor = AdtPrefs.getPrefs().getMonitorDensity();
508         if (monitor == 0.f) {
509             ResolutionChooserDialog dialog = new ResolutionChooserDialog(
510                     config.getShell());
511             if (dialog.open() == Window.OK) {
512                 monitor = dialog.getDensity();
513                 AdtPrefs.getPrefs().setMonitorDensity(monitor);
514             } else {
515                 return false;
516             }
517         }
518 
519         mEditor.getCanvasControl().setScale(monitor / dpi, redraw);
520         return true;
521     }
522 }
523