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