• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
18 
19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
20 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT;
21 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH;
22 import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
23 import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
24 
25 import com.android.ide.common.api.InsertType;
26 import com.android.ide.common.api.Rect;
27 import com.android.ide.common.api.RuleAction.Toggle;
28 import com.android.ide.common.rendering.LayoutLibrary;
29 import com.android.ide.common.rendering.api.Capability;
30 import com.android.ide.common.rendering.api.LayoutLog;
31 import com.android.ide.common.rendering.api.RenderSession;
32 import com.android.ide.common.rendering.api.ViewInfo;
33 import com.android.ide.eclipse.adt.AdtPlugin;
34 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
35 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
36 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
37 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
38 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
39 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
40 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
41 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
42 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
43 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
44 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
45 import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor;
46 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
47 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode;
48 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
49 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
50 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
51 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
52 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
53 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
54 import com.android.sdklib.IAndroidTarget;
55 import com.android.util.Pair;
56 
57 import org.eclipse.jface.action.Action;
58 import org.eclipse.jface.action.IAction;
59 import org.eclipse.jface.action.IToolBarManager;
60 import org.eclipse.jface.action.MenuManager;
61 import org.eclipse.jface.action.Separator;
62 import org.eclipse.jface.resource.ImageDescriptor;
63 import org.eclipse.swt.SWT;
64 import org.eclipse.swt.custom.CLabel;
65 import org.eclipse.swt.dnd.DND;
66 import org.eclipse.swt.dnd.DragSource;
67 import org.eclipse.swt.dnd.DragSourceEvent;
68 import org.eclipse.swt.dnd.DragSourceListener;
69 import org.eclipse.swt.dnd.Transfer;
70 import org.eclipse.swt.events.DisposeEvent;
71 import org.eclipse.swt.events.DisposeListener;
72 import org.eclipse.swt.events.MenuDetectEvent;
73 import org.eclipse.swt.events.MenuDetectListener;
74 import org.eclipse.swt.events.MouseAdapter;
75 import org.eclipse.swt.events.MouseEvent;
76 import org.eclipse.swt.events.MouseTrackListener;
77 import org.eclipse.swt.events.SelectionAdapter;
78 import org.eclipse.swt.events.SelectionEvent;
79 import org.eclipse.swt.graphics.Color;
80 import org.eclipse.swt.graphics.GC;
81 import org.eclipse.swt.graphics.Image;
82 import org.eclipse.swt.graphics.ImageData;
83 import org.eclipse.swt.graphics.Point;
84 import org.eclipse.swt.graphics.RGB;
85 import org.eclipse.swt.graphics.Rectangle;
86 import org.eclipse.swt.layout.FillLayout;
87 import org.eclipse.swt.layout.GridData;
88 import org.eclipse.swt.layout.GridLayout;
89 import org.eclipse.swt.widgets.Button;
90 import org.eclipse.swt.widgets.Composite;
91 import org.eclipse.swt.widgets.Control;
92 import org.eclipse.swt.widgets.Display;
93 import org.eclipse.swt.widgets.Menu;
94 import org.eclipse.swt.widgets.ToolBar;
95 import org.eclipse.swt.widgets.ToolItem;
96 import org.eclipse.wb.internal.core.editor.structure.IPage;
97 import org.w3c.dom.Attr;
98 import org.w3c.dom.Document;
99 import org.w3c.dom.Element;
100 
101 import java.awt.image.BufferedImage;
102 import java.io.IOException;
103 import java.io.StringWriter;
104 import java.util.ArrayList;
105 import java.util.Collection;
106 import java.util.Collections;
107 import java.util.HashMap;
108 import java.util.List;
109 import java.util.Map;
110 import java.util.Set;
111 
112 /**
113  * A palette control for the {@link GraphicalEditorPart}.
114  * <p/>
115  * The palette contains several groups, each with a UI name (e.g. layouts and views) and each
116  * with a list of element descriptors.
117  * <p/>
118  *
119  * TODO list:
120  *   - The available items should depend on the actual GLE2 Canvas selection. Selected android
121  *     views should force filtering on what they accept can be dropped on them (e.g. TabHost,
122  *     TableLayout). Should enable/disable them, not hide them, to avoid shuffling around.
123  *   - Optional: a text filter
124  *   - Optional: have context-sensitive tools items, e.g. selection arrow tool,
125  *     group selection tool, alignment, etc.
126  */
127 public class PaletteControl extends Composite {
128 
129     /**
130      * Wrapper to create a {@link PaletteControl}
131      */
132     static class PalettePage implements IPage {
133         private final GraphicalEditorPart mEditorPart;
134         private PaletteControl mControl;
135 
PalettePage(GraphicalEditorPart editor)136         PalettePage(GraphicalEditorPart editor) {
137             mEditorPart = editor;
138         }
139 
140         @Override
createControl(Composite parent)141         public void createControl(Composite parent) {
142             mControl = new PaletteControl(parent, mEditorPart);
143         }
144 
145         @Override
getControl()146         public Control getControl() {
147             return mControl;
148         }
149 
150         @Override
dispose()151         public void dispose() {
152             mControl.dispose();
153         }
154 
155         @Override
setToolBar(IToolBarManager toolBarManager)156         public void setToolBar(IToolBarManager toolBarManager) {
157         }
158 
159         /**
160          * Add tool bar items to the given toolbar
161          *
162          * @param toolbar the toolbar to add items into
163          */
createToolbarItems(final ToolBar toolbar)164         void createToolbarItems(final ToolBar toolbar) {
165             final ToolItem popupMenuItem = new ToolItem(toolbar, SWT.PUSH);
166             popupMenuItem.setToolTipText("View Menu");
167             popupMenuItem.setImage(IconFactory.getInstance().getIcon("view_menu"));
168             popupMenuItem.addSelectionListener(new SelectionAdapter() {
169                 @Override
170                 public void widgetSelected(SelectionEvent e) {
171                     Rectangle bounds = popupMenuItem.getBounds();
172                     // Align menu horizontally with the toolbar button and
173                     // vertically with the bottom of the toolbar
174                     Point point = toolbar.toDisplay(bounds.x, bounds.y + bounds.height);
175                     mControl.showMenu(point.x, point.y);
176                 }
177             });
178         }
179 
180         @Override
setFocus()181         public void setFocus() {
182             mControl.setFocus();
183         }
184     }
185 
186     /**
187      * The parent grid layout that contains all the {@link Toggle} and
188      * {@link IconTextItem} widgets.
189      */
190     private GraphicalEditorPart mEditor;
191     private Color mBackground;
192     private Color mForeground;
193 
194     /** The palette modes control various ways to visualize and lay out the views */
195     private static enum PaletteMode {
196         /** Show rendered previews of the views */
197         PREVIEW("Show Previews", true),
198         /** Show rendered previews of the views, scaled down to 75% */
199         SMALL_PREVIEW("Show Small Previews", true),
200         /** Show rendered previews of the views, scaled down to 50% */
201         TINY_PREVIEW("Show Tiny Previews", true),
202         /** Show an icon + text label */
203         ICON_TEXT("Show Icon and Text", false),
204         /** Show only icons, packed multiple per row */
205         ICON_ONLY("Show Only Icons", true);
206 
PaletteMode(String actionLabel, boolean wrap)207         PaletteMode(String actionLabel, boolean wrap) {
208             mActionLabel = actionLabel;
209             mWrap = wrap;
210         }
211 
getActionLabel()212         public String getActionLabel() {
213             return mActionLabel;
214         }
215 
getWrap()216         public boolean getWrap() {
217             return mWrap;
218         }
219 
isPreview()220         public boolean isPreview() {
221             return this == PREVIEW || this == SMALL_PREVIEW || this == TINY_PREVIEW;
222         }
223 
isScaledPreview()224         public boolean isScaledPreview() {
225             return this == SMALL_PREVIEW || this == TINY_PREVIEW;
226         }
227 
228         private final String mActionLabel;
229         private final boolean mWrap;
230     };
231 
232     /** Token used in preference string to record alphabetical sorting */
233     private static final String VALUE_ALPHABETICAL = "alpha";   //$NON-NLS-1$
234     /** Token used in preference string to record categories being turned off */
235     private static final String VALUE_NO_CATEGORIES = "nocat"; //$NON-NLS-1$
236     /** Token used in preference string to record auto close being turned off */
237     private static final String VALUE_NO_AUTOCLOSE = "noauto";      //$NON-NLS-1$
238 
239     private final PreviewIconFactory mPreviewIconFactory = new PreviewIconFactory(this);
240     private PaletteMode mPaletteMode = null;
241     /** Use alphabetical sorting instead of natural order? */
242     private boolean mAlphabetical;
243     /** Use categories instead of a single large list of views? */
244     private boolean mCategories = true;
245     /** Auto-close the previous category when new categories are opened */
246     private boolean mAutoClose = true;
247     private AccordionControl mAccordion;
248     private String mCurrentTheme;
249     private String mCurrentDevice;
250     private IAndroidTarget mCurrentTarget;
251     private AndroidTargetData mCurrentTargetData;
252 
253     /**
254      * Create the composite.
255      * @param parent The parent composite.
256      * @param editor An editor associated with this palette.
257      */
PaletteControl(Composite parent, GraphicalEditorPart editor)258     public PaletteControl(Composite parent, GraphicalEditorPart editor) {
259         super(parent, SWT.NONE);
260 
261         mEditor = editor;
262     }
263 
264     /** Reads UI mode from persistent store to preserve palette mode across IDE sessions */
loadPaletteMode()265     private void loadPaletteMode() {
266         String paletteModes = AdtPrefs.getPrefs().getPaletteModes();
267         if (paletteModes.length() > 0) {
268             String[] tokens = paletteModes.split(","); //$NON-NLS-1$
269             try {
270                 mPaletteMode = PaletteMode.valueOf(tokens[0]);
271             } catch (Throwable t) {
272                 mPaletteMode = PaletteMode.values()[0];
273             }
274             mAlphabetical = paletteModes.contains(VALUE_ALPHABETICAL);
275             mCategories = !paletteModes.contains(VALUE_NO_CATEGORIES);
276             mAutoClose = !paletteModes.contains(VALUE_NO_AUTOCLOSE);
277         } else {
278             mPaletteMode = PaletteMode.SMALL_PREVIEW;
279         }
280     }
281 
282     /**
283      * Returns the most recently stored version of auto-close-mode; this is the last
284      * user-initiated setting of the auto-close mode (we programmatically switch modes when
285      * you enter icons-only mode, and set it back to this when going to any other mode)
286      */
getSavedAutoCloseMode()287     private boolean getSavedAutoCloseMode() {
288         return !AdtPrefs.getPrefs().getPaletteModes().contains(VALUE_NO_AUTOCLOSE);
289     }
290 
291     /** Saves UI mode to persistent store to preserve palette mode across IDE sessions */
savePaletteMode()292     private void savePaletteMode() {
293         StringBuilder sb = new StringBuilder();
294         sb.append(mPaletteMode);
295         if (mAlphabetical) {
296             sb.append(',').append(VALUE_ALPHABETICAL);
297         }
298         if (!mCategories) {
299             sb.append(',').append(VALUE_NO_CATEGORIES);
300         }
301         if (!mAutoClose) {
302             sb.append(',').append(VALUE_NO_AUTOCLOSE);
303         }
304         AdtPrefs.getPrefs().setPaletteModes(sb.toString());
305     }
306 
refreshPalette()307     private void refreshPalette() {
308         IAndroidTarget oldTarget = mCurrentTarget;
309         mCurrentTarget = null;
310         mCurrentTargetData = null;
311         mCurrentTheme = null;
312         mCurrentDevice = null;
313         reloadPalette(oldTarget);
314     }
315 
316     @Override
checkSubclass()317     protected void checkSubclass() {
318         // Disable the check that prevents subclassing of SWT components
319     }
320 
321     @Override
dispose()322     public void dispose() {
323         if (mBackground != null) {
324             mBackground.dispose();
325             mBackground = null;
326         }
327         if (mForeground != null) {
328             mForeground.dispose();
329             mForeground = null;
330         }
331 
332         super.dispose();
333     }
334 
335     /**
336      * Returns the currently displayed target
337      *
338      * @return the current target, or null
339      */
getCurrentTarget()340     public IAndroidTarget getCurrentTarget() {
341         return mCurrentTarget;
342     }
343 
344     /**
345      * Returns the currently displayed theme (in palette modes that support previewing)
346      *
347      * @return the current theme, or null
348      */
getCurrentTheme()349     public String getCurrentTheme() {
350         return mCurrentTheme;
351     }
352 
353     /**
354      * Returns the currently displayed device (in palette modes that support previewing)
355      *
356      * @return the current device, or null
357      */
getCurrentDevice()358     public String getCurrentDevice() {
359         return mCurrentDevice;
360     }
361 
362     /** Returns true if previews in the palette should be made available */
previewsAvailable()363     private boolean previewsAvailable() {
364         // Not layoutlib 5 -- we require custom background support to do
365         // a decent job with previews
366         LayoutLibrary layoutLibrary = mEditor.getLayoutLibrary();
367         return layoutLibrary != null && layoutLibrary.supports(Capability.CUSTOM_BACKGROUND_COLOR);
368     }
369 
370     /**
371      * Loads or reloads the palette elements by using the layout and view descriptors from the
372      * given target data.
373      *
374      * @param target The target that has just been loaded
375      */
reloadPalette(IAndroidTarget target)376     public void reloadPalette(IAndroidTarget target) {
377         ConfigurationComposite configuration = mEditor.getConfigurationComposite();
378         String theme = configuration.getTheme();
379         String device = configuration.getDevice();
380         AndroidTargetData targetData =
381             target != null ? Sdk.getCurrent().getTargetData(target) : null;
382         if (target == mCurrentTarget && targetData == mCurrentTargetData
383                 && mCurrentTheme != null && mCurrentTheme.equals(theme)
384                 && mCurrentDevice != null && mCurrentDevice.equals(device)) {
385             return;
386         }
387         mCurrentTheme = theme;
388         mCurrentTarget = target;
389         mCurrentTargetData = targetData;
390         mCurrentDevice = device;
391         mPreviewIconFactory.reset();
392 
393         if (targetData == null) {
394             return;
395         }
396 
397         Set<String> expandedCategories = null;
398         if (mAccordion != null) {
399             expandedCategories = mAccordion.getExpandedCategories();
400             // We auto-expand all categories when showing icons-only. When returning to some
401             // other mode we don't want to retain all categories open.
402             if (expandedCategories.size() > 3) {
403                 expandedCategories = null;
404             }
405         }
406 
407         // Erase old content and recreate new
408         for (Control c : getChildren()) {
409             c.dispose();
410         }
411 
412         if (mPaletteMode == null) {
413             loadPaletteMode();
414             assert mPaletteMode != null;
415         }
416 
417         // Ensure that the palette mode is supported on this version of the layout library
418         if (!previewsAvailable()) {
419             if (mPaletteMode.isPreview()) {
420                 mPaletteMode = PaletteMode.ICON_TEXT;
421             }
422         }
423 
424         if (mPaletteMode.isPreview()) {
425             if (mForeground != null) {
426                 mForeground.dispose();
427                 mForeground = null;
428             }
429             if (mBackground != null) {
430                 mBackground.dispose();
431                 mBackground = null;
432             }
433             RGB background = mPreviewIconFactory.getBackgroundColor();
434             if (background != null) {
435                 mBackground = new Color(getDisplay(), background);
436             }
437             RGB foreground = mPreviewIconFactory.getForegroundColor();
438             if (foreground != null) {
439                 mForeground = new Color(getDisplay(), foreground);
440             }
441         }
442 
443         List<String> headers = Collections.emptyList();
444         final Map<String, List<ViewElementDescriptor>> categoryToItems;
445         categoryToItems = new HashMap<String, List<ViewElementDescriptor>>();
446         headers = new ArrayList<String>();
447         List<Pair<String,List<ViewElementDescriptor>>> paletteEntries =
448             ViewMetadataRepository.get().getPaletteEntries(targetData,
449                     mAlphabetical, mCategories);
450         for (Pair<String,List<ViewElementDescriptor>> pair : paletteEntries) {
451             String category = pair.getFirst();
452             List<ViewElementDescriptor> categoryItems = pair.getSecond();
453             headers.add(category);
454             categoryToItems.put(category, categoryItems);
455         }
456 
457         headers.add("Custom & Library Views");
458 
459         // Set the categories to expand the first item if
460         //   (1) we don't have a previously selected category, or
461         //   (2) there's just one category anyway, or
462         //   (3) the set of categories have changed so our previously selected category
463         //       doesn't exist anymore (can happen when you toggle "Show Categories")
464         if ((expandedCategories == null && headers.size() > 0) || headers.size() == 1 ||
465                 (expandedCategories != null && expandedCategories.size() >= 1
466                         && !headers.contains(
467                                 expandedCategories.iterator().next().replace("&&", "&")))) { //$NON-NLS-1$ //$NON-NLS-2$
468             // Expand the first category if we don't have a previous selection (e.g. refresh)
469             expandedCategories = Collections.singleton(headers.get(0));
470         }
471 
472         boolean wrap = mPaletteMode.getWrap();
473 
474         // Pack icon-only view vertically; others stretch to fill palette region
475         boolean fillVertical = mPaletteMode != PaletteMode.ICON_ONLY;
476 
477         mAccordion = new AccordionControl(this, SWT.NONE, headers, fillVertical, wrap,
478                 expandedCategories) {
479             @Override
480             protected Composite createChildContainer(Composite parent, Object header, int style) {
481                 assert categoryToItems != null;
482                 List<ViewElementDescriptor> list = categoryToItems.get(header);
483                 final Composite composite;
484                 if (list == null) {
485                     assert header.equals("Custom & Library Views");
486 
487                     Composite wrapper = new Composite(parent, SWT.NONE);
488                     GridLayout gridLayout = new GridLayout(1, false);
489                     gridLayout.marginWidth = gridLayout.marginHeight = 0;
490                     gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
491                     gridLayout.marginBottom = 3;
492                     wrapper.setLayout(gridLayout);
493                     if (mPaletteMode.isPreview() && mBackground != null) {
494                         wrapper.setBackground(mBackground);
495                     }
496                     composite = super.createChildContainer(wrapper, header, style);
497                     if (mPaletteMode.isPreview() && mBackground != null) {
498                         composite.setBackground(mBackground);
499                     }
500                     composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
501 
502                     Button refreshButton = new Button(wrapper, SWT.PUSH | SWT.FLAT);
503                     refreshButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
504                             false, false, 1, 1));
505                     refreshButton.setText("Refresh");
506                     refreshButton.setImage(IconFactory.getInstance().getIcon("refresh")); //$NON-NLS-1$
507                     refreshButton.addSelectionListener(new SelectionAdapter() {
508                         @Override
509                         public void widgetSelected(SelectionEvent e) {
510                             CustomViewFinder finder = CustomViewFinder.get(mEditor.getProject());
511                             finder.refresh(new ViewFinderListener(composite));
512                         }
513                     });
514 
515                     wrapper.layout(true);
516                 } else {
517                     composite = super.createChildContainer(parent, header, style);
518                     if (mPaletteMode.isPreview() && mBackground != null) {
519                         composite.setBackground(mBackground);
520                     }
521                 }
522                 addMenu(composite);
523                 return composite;
524             }
525             @Override
526             protected void createChildren(Composite parent, Object header) {
527                 assert categoryToItems != null;
528                 List<ViewElementDescriptor> list = categoryToItems.get(header);
529                 if (list == null) {
530                     assert header.equals("Custom & Library Views");
531                     addCustomItems(parent);
532                     return;
533                 } else {
534                     for (ViewElementDescriptor desc : list) {
535                         createItem(parent, desc);
536                     }
537                 }
538             }
539         };
540         addMenu(mAccordion);
541         for (CLabel headerLabel : mAccordion.getHeaderLabels()) {
542             addMenu(headerLabel);
543         }
544         setLayout(new FillLayout());
545 
546         // Expand All for icon-only mode, but don't store it as the persistent auto-close mode;
547         // when we enter other modes it will read back whatever persistent mode.
548         if (mPaletteMode == PaletteMode.ICON_ONLY) {
549             mAccordion.expandAll(true);
550             mAccordion.setAutoClose(false);
551         } else {
552             mAccordion.setAutoClose(getSavedAutoCloseMode());
553         }
554 
555         layout(true);
556     }
557 
addCustomItems(final Composite parent)558     protected void addCustomItems(final Composite parent) {
559         final CustomViewFinder finder = CustomViewFinder.get(mEditor.getProject());
560         Collection<String> allViews = finder.getAllViews();
561         if (allViews == null) { // Not yet initialized: trigger an async refresh
562             finder.refresh(new ViewFinderListener(parent));
563             return;
564         }
565 
566         // Remove previous content
567         for (Control c : parent.getChildren()) {
568             c.dispose();
569         }
570 
571         // Add new views
572         for (final String fqcn : allViews) {
573             CustomViewDescriptorService service = CustomViewDescriptorService.getInstance();
574             ViewElementDescriptor desc = service.getDescriptor(mEditor.getProject(), fqcn);
575             if (desc == null) {
576                 // The descriptor lookup performs validation steps of the class, and may
577                 // in some cases determine that this is not a view and will return null;
578                 // guard against that.
579                 continue;
580             }
581 
582             Control item = createItem(parent, desc);
583 
584             // Add control-click listener on custom view items to you can warp to
585             // (and double click listener too -- the more discoverable, the better.)
586             if (item instanceof IconTextItem) {
587                 IconTextItem it = (IconTextItem) item;
588                 it.addMouseListener(new MouseAdapter() {
589                     @Override
590                     public void mouseDoubleClick(MouseEvent e) {
591                         AdtPlugin.openJavaClass(mEditor.getProject(), fqcn);
592                     }
593 
594                     @Override
595                     public void mouseDown(MouseEvent e) {
596                         if ((e.stateMask & SWT.MOD1) != 0) {
597                             AdtPlugin.openJavaClass(mEditor.getProject(), fqcn);
598                         }
599                     }
600                 });
601             }
602         }
603     }
604 
getEditor()605     /* package */ GraphicalEditorPart getEditor() {
606         return mEditor;
607     }
608 
createItem(Composite parent, ViewElementDescriptor desc)609     private Control createItem(Composite parent, ViewElementDescriptor desc) {
610         Control item = null;
611         switch (mPaletteMode) {
612             case SMALL_PREVIEW:
613             case TINY_PREVIEW:
614             case PREVIEW: {
615                 ImageDescriptor descriptor = mPreviewIconFactory.getImageDescriptor(desc);
616                 if (descriptor != null) {
617                     Image image = descriptor.createImage();
618                     ImageControl imageControl = new ImageControl(parent, SWT.None, image);
619                     if (mPaletteMode.isScaledPreview()) {
620                         // Try to preserve the overall size since rendering sizes typically
621                         // vary with the dpi - so while the scaling factor for a 160 dpi
622                         // rendering the scaling factor should be 0.5, for a 320 dpi one the
623                         // scaling factor should be half that, 0.25.
624                         float scale = 1.0f;
625                         if (mPaletteMode == PaletteMode.SMALL_PREVIEW) {
626                             scale = 0.75f;
627                         } else if (mPaletteMode == PaletteMode.TINY_PREVIEW) {
628                             scale = 0.5f;
629                         }
630                         int dpi = mEditor.getConfigurationComposite().getDensity().getDpiValue();
631                         while (dpi > 160) {
632                             scale = scale / 2;
633                             dpi = dpi / 2;
634                         }
635                         imageControl.setScale(scale);
636                     }
637                     imageControl.setHoverColor(getDisplay().getSystemColor(SWT.COLOR_WHITE));
638                     if (mBackground != null) {
639                         imageControl.setBackground(mBackground);
640                     }
641                     String toolTip = desc.getUiName();
642                     // It appears pretty much none of the descriptors have tooltips
643                     //String descToolTip = desc.getTooltip();
644                     //if (descToolTip != null && descToolTip.length() > 0) {
645                     //    toolTip = toolTip + "\n" + descToolTip;
646                     //}
647                     imageControl.setToolTipText(toolTip);
648 
649                     item = imageControl;
650                 } else {
651                     // Just use an Icon+Text item for these for now
652                     item = new IconTextItem(parent, desc);
653                     if (mForeground != null) {
654                         item.setForeground(mForeground);
655                         item.setBackground(mBackground);
656                     }
657                 }
658                 break;
659             }
660             case ICON_TEXT: {
661                 item = new IconTextItem(parent, desc);
662                 break;
663             }
664             case ICON_ONLY: {
665                 item = new ImageControl(parent, SWT.None, desc.getGenericIcon());
666                 item.setToolTipText(desc.getUiName());
667                 break;
668             }
669             default:
670                 throw new IllegalArgumentException("Not yet implemented");
671         }
672 
673         final DragSource source = new DragSource(item, DND.DROP_COPY);
674         source.setTransfer(new Transfer[] { SimpleXmlTransfer.getInstance() });
675         source.addDragListener(new DescDragSourceListener(desc));
676         item.addDisposeListener(new DisposeListener() {
677             @Override
678             public void widgetDisposed(DisposeEvent e) {
679                 source.dispose();
680             }
681         });
682         addMenu(item);
683 
684         return item;
685     }
686 
687     /**
688      * An Item widget represents one {@link ElementDescriptor} that can be dropped on the
689      * GLE2 canvas using drag'n'drop.
690      */
691     private static class IconTextItem extends CLabel implements MouseTrackListener {
692 
693         private boolean mMouseIn;
694 
IconTextItem(Composite parent, ViewElementDescriptor desc)695         public IconTextItem(Composite parent, ViewElementDescriptor desc) {
696             super(parent, SWT.NONE);
697             mMouseIn = false;
698 
699             setText(desc.getUiName());
700             setImage(desc.getGenericIcon());
701             setToolTipText(desc.getTooltip());
702             addMouseTrackListener(this);
703         }
704 
705         @Override
getStyle()706         public int getStyle() {
707             int style = super.getStyle();
708             if (mMouseIn) {
709                 style |= SWT.SHADOW_IN;
710             }
711             return style;
712         }
713 
714         @Override
mouseEnter(MouseEvent e)715         public void mouseEnter(MouseEvent e) {
716             if (!mMouseIn) {
717                 mMouseIn = true;
718                 redraw();
719             }
720         }
721 
722         @Override
mouseExit(MouseEvent e)723         public void mouseExit(MouseEvent e) {
724             if (mMouseIn) {
725                 mMouseIn = false;
726                 redraw();
727             }
728         }
729 
730         @Override
mouseHover(MouseEvent e)731         public void mouseHover(MouseEvent e) {
732             // pass
733         }
734     }
735 
736     /**
737      * A {@link DragSourceListener} that deals with drag'n'drop of
738      * {@link ElementDescriptor}s.
739      */
740     private class DescDragSourceListener implements DragSourceListener {
741         private final ViewElementDescriptor mDesc;
742         private SimpleElement[] mElements;
743 
DescDragSourceListener(ViewElementDescriptor desc)744         public DescDragSourceListener(ViewElementDescriptor desc) {
745             mDesc = desc;
746         }
747 
748         @Override
dragStart(DragSourceEvent e)749         public void dragStart(DragSourceEvent e) {
750             // See if we can find out the bounds of this element from a preview image.
751             // Preview images are created before the drag source listener is notified
752             // of the started drag.
753             Rect bounds = null;
754             Rect dragBounds = null;
755 
756             createDragImage(e);
757             if (mImage != null && !mIsPlaceholder) {
758                 int width = mImageLayoutBounds.width;
759                 int height = mImageLayoutBounds.height;
760                 assert mImageLayoutBounds.x == 0;
761                 assert mImageLayoutBounds.y == 0;
762                 bounds = new Rect(0, 0, width, height);
763                 double scale = mEditor.getCanvasControl().getScale();
764                 int scaledWidth = (int) (scale * width);
765                 int scaledHeight = (int) (scale * height);
766                 int x = -scaledWidth / 2;
767                 int y = -scaledHeight / 2;
768                 dragBounds = new Rect(x, y, scaledWidth, scaledHeight);
769             }
770 
771             SimpleElement se = new SimpleElement(
772                     SimpleXmlTransfer.getFqcn(mDesc),
773                     null   /* parentFqcn */,
774                     bounds /* bounds */,
775                     null   /* parentBounds */);
776             if (mDesc instanceof PaletteMetadataDescriptor) {
777                 PaletteMetadataDescriptor pm = (PaletteMetadataDescriptor) mDesc;
778                 pm.initializeNew(se);
779             }
780             mElements = new SimpleElement[] { se };
781 
782             // Register this as the current dragged data
783             GlobalCanvasDragInfo dragInfo = GlobalCanvasDragInfo.getInstance();
784             dragInfo.startDrag(
785                     mElements,
786                     null /* selection */,
787                     null /* canvas */,
788                     null /* removeSource */);
789             dragInfo.setDragBounds(dragBounds);
790             dragInfo.setDragBaseline(mBaseline);
791 
792 
793             e.doit = true;
794         }
795 
796         @Override
dragSetData(DragSourceEvent e)797         public void dragSetData(DragSourceEvent e) {
798             // Provide the data for the drop when requested by the other side.
799             if (SimpleXmlTransfer.getInstance().isSupportedType(e.dataType)) {
800                 e.data = mElements;
801             }
802         }
803 
804         @Override
dragFinished(DragSourceEvent e)805         public void dragFinished(DragSourceEvent e) {
806             // Unregister the dragged data.
807             GlobalCanvasDragInfo.getInstance().stopDrag();
808             mElements = null;
809             if (mImage != null) {
810                 mImage.dispose();
811                 mImage = null;
812             }
813         }
814 
815         // TODO: Figure out the right dimensions to use for rendering.
816         // We WILL crop this after rendering, but for performance reasons it would be good
817         // not to make it much larger than necessary since to crop this we rely on
818         // actually scanning pixels.
819 
820         /**
821          * Width of the rendered preview image (before it is cropped), although the actual
822          * width may be smaller (since we also take the device screen's size into account)
823          */
824         private static final int MAX_RENDER_HEIGHT = 400;
825 
826         /**
827          * Height of the rendered preview image (before it is cropped), although the
828          * actual width may be smaller (since we also take the device screen's size into
829          * account)
830          */
831         private static final int MAX_RENDER_WIDTH = 500;
832 
833         /** Amount of alpha to multiply into the image (divided by 256) */
834         private static final int IMG_ALPHA = 128;
835 
836         /** The image shown during the drag */
837         private Image mImage;
838         /** The non-effect bounds of the drag image */
839         private Rectangle mImageLayoutBounds;
840         private int mBaseline = -1;
841 
842         /**
843          * If true, the image is a preview of the view, and if not it is a "fallback"
844          * image of some sort, such as a rendering of the palette item itself
845          */
846         private boolean mIsPlaceholder;
847 
createDragImage(DragSourceEvent event)848         private void createDragImage(DragSourceEvent event) {
849             mBaseline = -1;
850             Pair<Image, Rectangle> preview = renderPreview();
851             if (preview != null) {
852                 mImage = preview.getFirst();
853                 mImageLayoutBounds = preview.getSecond();
854             } else {
855                 mImage = null;
856                 mImageLayoutBounds = null;
857             }
858 
859             mIsPlaceholder = mImage == null;
860             if (mIsPlaceholder) {
861                 // Couldn't render preview (or the preview is a blank image, such as for
862                 // example the preview of an empty layout), so instead create a placeholder
863                 // image
864                 // Render the palette item itself as an image
865                 Control control = ((DragSource) event.widget).getControl();
866                 GC gc = new GC(control);
867                 Point size = control.getSize();
868                 Display display = getDisplay();
869                 final Image image = new Image(display, size.x, size.y);
870                 gc.copyArea(image, 0, 0);
871                 gc.dispose();
872 
873                 BufferedImage awtImage = SwtUtils.convertToAwt(image);
874                 if (awtImage != null) {
875                     awtImage = ImageUtils.createDropShadow(awtImage, 3 /* shadowSize */,
876                             0.7f /* shadowAlpha */, 0x000000 /* shadowRgb */);
877                     mImage = SwtUtils.convertToSwt(display, awtImage, true, IMG_ALPHA);
878                 } else {
879                     ImageData data = image.getImageData();
880                     data.alpha = IMG_ALPHA;
881 
882                     // Changing the ImageData -after- constructing an image on it
883                     // has no effect, so we have to construct a new image. Luckily these
884                     // are tiny images.
885                     mImage = new Image(display, data);
886                 }
887                 image.dispose();
888             }
889 
890             event.image = mImage;
891 
892             if (!mIsPlaceholder) {
893                 // Shift the drag feedback image up such that it's centered under the
894                 // mouse pointer
895                 double scale = mEditor.getCanvasControl().getScale();
896                 event.offsetX = (int) (scale * mImageLayoutBounds.width / 2);
897                 event.offsetY = (int) (scale * mImageLayoutBounds.height / 2);
898             }
899         }
900 
901         /**
902          * Performs the actual rendering of the descriptor into an image and returns the
903          * image as well as the layout bounds of the image (not including drop shadow etc)
904          */
renderPreview()905         private Pair<Image, Rectangle> renderPreview() {
906             ViewMetadataRepository repository = ViewMetadataRepository.get();
907             RenderMode renderMode = repository.getRenderMode(mDesc.getFullClassName());
908             if (renderMode == RenderMode.SKIP) {
909                 return null;
910             }
911 
912             // Create blank XML document
913             Document document = DomUtilities.createEmptyDocument();
914 
915             // Insert our target view's XML into it as a node
916             GraphicalEditorPart editor = getEditor();
917             LayoutEditorDelegate layoutEditorDelegate = editor.getEditorDelegate();
918 
919             String viewName = mDesc.getXmlLocalName();
920             Element element = document.createElement(viewName);
921 
922             // Set up a proper name space
923             Attr attr = document.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI,
924                     "xmlns:android"); //$NON-NLS-1$
925             attr.setValue(ANDROID_URI);
926             element.getAttributes().setNamedItemNS(attr);
927 
928             element.setAttributeNS(ANDROID_URI,
929                     ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT);
930             element.setAttributeNS(ANDROID_URI,
931                     ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT);
932 
933             // This doesn't apply to all, but doesn't seem to cause harm and makes for a
934             // better experience with text-oriented views like buttons and texts
935             element.setAttributeNS(ANDROID_URI, ATTR_TEXT,
936                     DescriptorsUtils.getBasename(mDesc.getUiName()));
937 
938             // Is this a palette variation?
939             if (mDesc instanceof PaletteMetadataDescriptor) {
940                 PaletteMetadataDescriptor pm = (PaletteMetadataDescriptor) mDesc;
941                 pm.initializeNew(element);
942             }
943 
944             document.appendChild(element);
945 
946             // Construct UI model from XML
947             AndroidTargetData data = layoutEditorDelegate.getEditor().getTargetData();
948             DocumentDescriptor documentDescriptor;
949             if (data == null) {
950                 documentDescriptor = new DocumentDescriptor("temp", null/*children*/);//$NON-NLS-1$
951             } else {
952                 documentDescriptor = data.getLayoutDescriptors().getDescriptor();
953             }
954             UiDocumentNode model = (UiDocumentNode) documentDescriptor.createUiNode();
955             model.setEditor(layoutEditorDelegate.getEditor());
956             model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider());
957             model.loadFromXmlNode(document);
958 
959             // Call the create-hooks such that we for example insert mandatory
960             // children into views like the DialerFilter, apply image source attributes
961             // to ImageButtons, etc.
962             LayoutCanvas canvas = editor.getCanvasControl();
963             NodeFactory nodeFactory = canvas.getNodeFactory();
964             UiElementNode parent = model.getUiRoot();
965             UiElementNode child = parent.getUiChildren().get(0);
966             if (child instanceof UiViewElementNode) {
967                 UiViewElementNode childUiNode = (UiViewElementNode) child;
968                 NodeProxy childNode = nodeFactory.create(childUiNode);
969 
970                 // Applying create hooks as part of palette render should
971                 // not trigger model updates
972                 layoutEditorDelegate.getEditor().setIgnoreXmlUpdate(true);
973                 try {
974                     canvas.getRulesEngine().callCreateHooks(layoutEditorDelegate.getEditor(),
975                             null, childNode, InsertType.CREATE_PREVIEW);
976                     childNode.applyPendingChanges();
977                 } catch (Throwable t) {
978                     AdtPlugin.log(t, "Failed calling creation hooks for widget %1$s", viewName);
979                 } finally {
980                     layoutEditorDelegate.getEditor().setIgnoreXmlUpdate(false);
981                 }
982             }
983 
984             Integer overrideBgColor = null;
985             boolean hasTransparency = false;
986             LayoutLibrary layoutLibrary = editor.getLayoutLibrary();
987             if (layoutLibrary != null &&
988                     layoutLibrary.supports(Capability.CUSTOM_BACKGROUND_COLOR)) {
989                 // It doesn't matter what the background color is as long as the alpha
990                 // is 0 (fully transparent). We're using red to make it more obvious if
991                 // for some reason the background is painted when it shouldn't be.
992                 overrideBgColor = new Integer(0x00FF0000);
993             }
994 
995             RenderSession session = null;
996             try {
997                 // Use at most the size of the screen for the preview render.
998                 // This is important since when we fill the size of certain views (like
999                 // a SeekBar), we want it to at most be the width of the screen, and for small
1000                 // screens the RENDER_WIDTH was wider.
1001                 Rect screenBounds = editor.getScreenBounds();
1002                 int renderWidth = Math.min(screenBounds.w, MAX_RENDER_WIDTH);
1003                 int renderHeight = Math.min(screenBounds.h, MAX_RENDER_HEIGHT);
1004                 LayoutLog silentLogger = new LayoutLog();
1005 
1006                 session = RenderService.create(editor)
1007                     .setModel(model)
1008                     .setSize(renderWidth, renderHeight)
1009                     .setLog(silentLogger)
1010                     .setOverrideBgColor(overrideBgColor)
1011                     .setDecorations(false)
1012                     .createRenderSession();
1013             } catch (Throwable t) {
1014                 // Previews can fail for a variety of reasons -- let's not bug
1015                 // the user with it
1016                 return null;
1017             }
1018 
1019             if (session != null) {
1020                 if (session.getResult().isSuccess()) {
1021                     BufferedImage image = session.getImage();
1022                     if (image != null) {
1023                         BufferedImage cropped;
1024                         Rect initialCrop = null;
1025                         ViewInfo viewInfo = null;
1026 
1027                         List<ViewInfo> viewInfoList = session.getRootViews();
1028 
1029                         if (viewInfoList != null && viewInfoList.size() > 0) {
1030                             viewInfo = viewInfoList.get(0);
1031                             mBaseline = viewInfo.getBaseLine();
1032                         }
1033 
1034                         if (viewInfo != null) {
1035                             int x1 = viewInfo.getLeft();
1036                             int x2 = viewInfo.getRight();
1037                             int y2 = viewInfo.getBottom();
1038                             int y1 = viewInfo.getTop();
1039                             initialCrop = new Rect(x1, y1, x2 - x1, y2 - y1);
1040                         }
1041 
1042                         if (hasTransparency) {
1043                             cropped = ImageUtils.cropBlank(image, initialCrop);
1044                         } else {
1045                             // Find out what the "background" color is such that we can properly
1046                             // crop it out of the image. To do this we pick out a pixel in the
1047                             // bottom right unpainted area. Rather than pick the one in the far
1048                             // bottom corner, we pick one as close to the bounds of the view as
1049                             // possible (but still outside of the bounds), such that we can
1050                             // deal with themes like the dialog theme.
1051                             int edgeX = image.getWidth() -1;
1052                             int edgeY = image.getHeight() -1;
1053                             if (viewInfo != null) {
1054                                 if (viewInfo.getRight() < image.getWidth()-1) {
1055                                     edgeX = viewInfo.getRight()+1;
1056                                 }
1057                                 if (viewInfo.getBottom() < image.getHeight()-1) {
1058                                     edgeY = viewInfo.getBottom()+1;
1059                                 }
1060                             }
1061                             int edgeColor = image.getRGB(edgeX, edgeY);
1062                             cropped = ImageUtils.cropColor(image, edgeColor, initialCrop);
1063                         }
1064 
1065                         if (cropped != null) {
1066                             int width = initialCrop != null ? initialCrop.w : cropped.getWidth();
1067                             int height = initialCrop != null ? initialCrop.h : cropped.getHeight();
1068                             boolean needsContrast = hasTransparency
1069                                     && !ImageUtils.containsDarkPixels(cropped);
1070                             cropped = ImageUtils.createDropShadow(cropped,
1071                                     hasTransparency ? 3 : 5 /* shadowSize */,
1072                                     !hasTransparency ? 0.6f : needsContrast ? 0.8f : 0.7f/*alpha*/,
1073                                     0x000000 /* shadowRgb */);
1074 
1075                             double scale = canvas.getScale();
1076                             if (scale != 1L) {
1077                                 cropped = ImageUtils.scale(cropped, scale, scale);
1078                             }
1079 
1080                             Display display = getDisplay();
1081                             int alpha = (!hasTransparency || !needsContrast) ? IMG_ALPHA : -1;
1082                             Image swtImage = SwtUtils.convertToSwt(display, cropped, true, alpha);
1083                             Rectangle imageBounds = new Rectangle(0, 0, width, height);
1084                             return Pair.of(swtImage, imageBounds);
1085                         }
1086                     }
1087                 }
1088 
1089                 session.dispose();
1090             }
1091 
1092             return null;
1093         }
1094 
1095         /**
1096          * Utility method to print out the contents of the given XML document. This is
1097          * really useful when working on the preview code above. I'm including all the
1098          * code inside a constant false, which means the compiler will omit all the code,
1099          * but I'd like to leave it in the code base and by doing it this way rather than
1100          * as commented out code the code won't be accidentally broken.
1101          */
1102         @SuppressWarnings("all")
dumpDocument(Document document)1103         private void dumpDocument(Document document) {
1104             // Diagnostics: print out the XML that we're about to render
1105             if (false) { // Will be omitted by the compiler
1106                 org.apache.xml.serialize.OutputFormat outputFormat =
1107                     new org.apache.xml.serialize.OutputFormat(
1108                             "XML", "ISO-8859-1", true); //$NON-NLS-1$ //$NON-NLS-2$
1109                 outputFormat.setIndent(2);
1110                 outputFormat.setLineWidth(100);
1111                 outputFormat.setIndenting(true);
1112                 outputFormat.setOmitXMLDeclaration(true);
1113                 outputFormat.setOmitDocumentType(true);
1114                 StringWriter stringWriter = new StringWriter();
1115                 // Using FQN here to avoid having an import above, which will result
1116                 // in a deprecation warning, and there isn't a way to annotate a single
1117                 // import element with a SuppressWarnings.
1118                 org.apache.xml.serialize.XMLSerializer serializer =
1119                     new org.apache.xml.serialize.XMLSerializer(stringWriter, outputFormat);
1120                 serializer.setNamespaces(true);
1121                 try {
1122                     serializer.serialize(document.getDocumentElement());
1123                     System.out.println(stringWriter.toString());
1124                 } catch (IOException e) {
1125                     e.printStackTrace();
1126                 }
1127             }
1128         }
1129     }
1130 
1131     /** Action for switching view modes via radio buttons */
1132     private class PaletteModeAction extends Action {
1133         private final PaletteMode mMode;
1134 
PaletteModeAction(PaletteMode mode)1135         PaletteModeAction(PaletteMode mode) {
1136             super(mode.getActionLabel(), IAction.AS_RADIO_BUTTON);
1137             mMode = mode;
1138             boolean selected = mMode == mPaletteMode;
1139             setChecked(selected);
1140             setEnabled(!selected);
1141         }
1142 
1143         @Override
run()1144         public void run() {
1145             if (isEnabled()) {
1146                 mPaletteMode = mMode;
1147                 refreshPalette();
1148                 savePaletteMode();
1149             }
1150         }
1151     }
1152 
1153     /** Action for toggling various checkbox view modes - categories, sorting, etc */
1154     private class ToggleViewOptionAction extends Action {
1155         private final int mAction;
1156         final static int TOGGLE_CATEGORY = 1;
1157         final static int TOGGLE_ALPHABETICAL = 2;
1158         final static int TOGGLE_AUTO_CLOSE = 3;
1159         final static int REFRESH = 4;
1160         final static int RESET = 5;
1161 
ToggleViewOptionAction(String title, int action, boolean checked)1162         ToggleViewOptionAction(String title, int action, boolean checked) {
1163             super(title, (action == REFRESH || action == RESET) ? IAction.AS_PUSH_BUTTON
1164                     : IAction.AS_CHECK_BOX);
1165             mAction = action;
1166             if (checked) {
1167                 setChecked(checked);
1168             }
1169         }
1170 
1171         @Override
run()1172         public void run() {
1173             switch (mAction) {
1174                 case TOGGLE_CATEGORY:
1175                     mCategories = !mCategories;
1176                     refreshPalette();
1177                     break;
1178                 case TOGGLE_ALPHABETICAL:
1179                     mAlphabetical = !mAlphabetical;
1180                     refreshPalette();
1181                     break;
1182                 case TOGGLE_AUTO_CLOSE:
1183                     mAutoClose = !mAutoClose;
1184                     mAccordion.setAutoClose(mAutoClose);
1185                     break;
1186                 case REFRESH:
1187                     mPreviewIconFactory.refresh();
1188                     refreshPalette();
1189                     break;
1190                 case RESET:
1191                     mAlphabetical = false;
1192                     mCategories = true;
1193                     mAutoClose = true;
1194                     mPaletteMode = PaletteMode.SMALL_PREVIEW;
1195                     refreshPalette();
1196                     break;
1197             }
1198             savePaletteMode();
1199         }
1200     }
1201 
addMenu(Control control)1202     private void addMenu(Control control) {
1203         control.addMenuDetectListener(new MenuDetectListener() {
1204             @Override
1205             public void menuDetected(MenuDetectEvent e) {
1206                 showMenu(e.x, e.y);
1207             }
1208         });
1209     }
1210 
showMenu(int x, int y)1211     private void showMenu(int x, int y) {
1212         MenuManager manager = new MenuManager() {
1213             @Override
1214             public boolean isDynamic() {
1215                 return true;
1216             }
1217         };
1218         boolean previews = previewsAvailable();
1219         for (PaletteMode mode : PaletteMode.values()) {
1220             if (mode.isPreview() && !previews) {
1221                 continue;
1222             }
1223             manager.add(new PaletteModeAction(mode));
1224         }
1225         if (mPaletteMode.isPreview()) {
1226             manager.add(new Separator());
1227             manager.add(new ToggleViewOptionAction("Refresh Previews",
1228                     ToggleViewOptionAction.REFRESH,
1229                     false));
1230         }
1231         manager.add(new Separator());
1232         manager.add(new ToggleViewOptionAction("Show Categories",
1233                 ToggleViewOptionAction.TOGGLE_CATEGORY,
1234                 mCategories));
1235         manager.add(new ToggleViewOptionAction("Sort Alphabetically",
1236                 ToggleViewOptionAction.TOGGLE_ALPHABETICAL,
1237                 mAlphabetical));
1238         manager.add(new Separator());
1239         manager.add(new ToggleViewOptionAction("Auto Close Previous",
1240                 ToggleViewOptionAction.TOGGLE_AUTO_CLOSE,
1241                 mAutoClose));
1242         manager.add(new Separator());
1243         manager.add(new ToggleViewOptionAction("Reset",
1244                 ToggleViewOptionAction.RESET,
1245                 false));
1246 
1247         Menu menu = manager.createContextMenu(PaletteControl.this);
1248         menu.setLocation(x, y);
1249         menu.setVisible(true);
1250     }
1251 
1252     private final class ViewFinderListener implements CustomViewFinder.Listener {
1253         private final Composite mParent;
1254 
ViewFinderListener(Composite parent)1255         private ViewFinderListener(Composite parent) {
1256             this.mParent = parent;
1257         }
1258 
1259         @Override
viewsUpdated(Collection<String> customViews, Collection<String> thirdPartyViews)1260         public void viewsUpdated(Collection<String> customViews,
1261                 Collection<String> thirdPartyViews) {
1262             addCustomItems(mParent);
1263             mParent.layout(true);
1264         }
1265     }
1266 }
1267