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