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