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.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 
    140         public PaletteDecor(GraphicalEditorPart editor) {
    141             mEditorPart = editor;
    142         }
    143 
    144         public String getTitle() {
    145             return "Palette";
    146         }
    147 
    148         public Image getImage() {
    149             return IconFactory.getInstance().getIcon("editor_palette");  //$NON-NLS-1$
    150         }
    151 
    152         public void createControl(Composite parent) {
    153             mControl = new PaletteControl(parent, mEditorPart);
    154         }
    155 
    156         public Control getControl() {
    157             return mControl;
    158         }
    159 
    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 
    198         PaletteMode(String actionLabel, boolean wrap) {
    199             mActionLabel = actionLabel;
    200             mWrap = wrap;
    201         }
    202 
    203         public String getActionLabel() {
    204             return mActionLabel;
    205         }
    206 
    207         public boolean getWrap() {
    208             return mWrap;
    209         }
    210 
    211         public boolean isPreview() {
    212             return this == PREVIEW || this == SMALL_PREVIEW || this == TINY_PREVIEW;
    213         }
    214 
    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      */
    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 */
    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      */
    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 */
    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 
    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
    308     protected void checkSubclass() {
    309         // Disable the check that prevents subclassing of SWT components
    310     }
    311 
    312     @Override
    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      */
    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      */
    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      */
    349     public String getCurrentDevice() {
    350         return mCurrentDevice;
    351     }
    352 
    353     /** Returns true if previews in the palette should be made available */
    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      */
    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 
    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 
    594     /* package */ GraphicalEditorPart getEditor() {
    595         return mEditor;
    596     }
    597 
    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 
    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
    694         public int getStyle() {
    695             int style = super.getStyle();
    696             if (mMouseIn) {
    697                 style |= SWT.SHADOW_IN;
    698             }
    699             return style;
    700         }
    701 
    702         public void mouseEnter(MouseEvent e) {
    703             if (!mMouseIn) {
    704                 mMouseIn = true;
    705                 redraw();
    706             }
    707         }
    708 
    709         public void mouseExit(MouseEvent e) {
    710             if (mMouseIn) {
    711                 mMouseIn = false;
    712                 redraw();
    713             }
    714         }
    715 
    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 
    729         public DescDragSourceListener(ViewElementDescriptor desc) {
    730             mDesc = desc;
    731         }
    732 
    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 
    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 
    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 
    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          */
    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")
   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 
   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
   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 
   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
   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 
   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 
   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 
   1242         private ViewFinderListener(Composite parent) {
   1243             this.mParent = parent;
   1244         }
   1245 
   1246         public void viewsUpdated(Collection<String> customViews,
   1247                 Collection<String> thirdPartyViews) {
   1248             addCustomItems(mParent);
   1249             mParent.layout(true);
   1250         }
   1251     }
   1252 }
   1253