Home | History | Annotate | Download | only in configuration
      1 /*
      2  * Copyright (C) 2012 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.configuration;
     18 
     19 import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
     20 
     21 import com.android.ide.eclipse.adt.internal.editors.Hyperlinks;
     22 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SubmenuAction;
     23 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     24 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.ActivityAttributes;
     25 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     26 import com.android.sdklib.IAndroidTarget;
     27 
     28 import org.eclipse.core.resources.IFile;
     29 import org.eclipse.core.resources.IProject;
     30 import org.eclipse.jface.action.Action;
     31 import org.eclipse.jface.action.ActionContributionItem;
     32 import org.eclipse.jface.action.IAction;
     33 import org.eclipse.jface.action.MenuManager;
     34 import org.eclipse.jface.action.Separator;
     35 import org.eclipse.jface.text.hyperlink.IHyperlink;
     36 import org.eclipse.swt.graphics.Point;
     37 import org.eclipse.swt.graphics.Rectangle;
     38 import org.eclipse.swt.widgets.Menu;
     39 import org.eclipse.swt.widgets.ToolItem;
     40 
     41 import java.util.ArrayList;
     42 import java.util.Collections;
     43 import java.util.HashSet;
     44 import java.util.List;
     45 import java.util.Set;
     46 
     47 /**
     48  * Action which creates a submenu displaying available themes
     49  */
     50 class ThemeMenuAction extends SubmenuAction {
     51     private static final String DEVICE_LIGHT_PREFIX =
     52             ANDROID_STYLE_RESOURCE_PREFIX + "Theme.DeviceDefault.Light";  //$NON-NLS-1$
     53     private static final String HOLO_LIGHT_PREFIX =
     54             ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Holo.Light";           //$NON-NLS-1$
     55     private static final String DEVICE_PREFIX =
     56             ANDROID_STYLE_RESOURCE_PREFIX + "Theme.DeviceDefault";        //$NON-NLS-1$
     57     private static final String HOLO_PREFIX =
     58             ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Holo";                 //$NON-NLS-1$
     59     private static final String LIGHT_PREFIX =
     60             ANDROID_STYLE_RESOURCE_PREFIX +"Theme.Light";                 //$NON-NLS-1$
     61     private static final String THEME_PREFIX =
     62             ANDROID_STYLE_RESOURCE_PREFIX +"Theme";                       //$NON-NLS-1$
     63 
     64     // Constants used to indicate what type of menu is being shown, such that
     65     // the submenus can lazily construct their contents
     66     private static final int MENU_MANIFEST = 1;
     67     private static final int MENU_PROJECT = 2;
     68     private static final int MENU_THEME = 3;
     69     private static final int MENU_THEME_LIGHT = 4;
     70     private static final int MENU_HOLO = 5;
     71     private static final int MENU_HOLO_LIGHT = 6;
     72     private static final int MENU_DEVICE = 7;
     73     private static final int MENU_DEVICE_LIGHT = 8;
     74     private static final int MENU_ALL = 9;
     75 
     76     private final ConfigurationChooser mConfigChooser;
     77     private final List<String> mThemeList;
     78     /** Type of menu; one of the constants {@link #MENU_ALL} etc */
     79     private final int mType;
     80 
     81     ThemeMenuAction(int type, String title, ConfigurationChooser configuration,
     82             List<String> themeList) {
     83         super(title);
     84         mType = type;
     85         mConfigChooser = configuration;
     86         mThemeList = themeList;
     87     }
     88 
     89     static void showThemeMenu(ConfigurationChooser configChooser, ToolItem combo,
     90             List<String> themeList) {
     91         MenuManager manager = new MenuManager();
     92 
     93         // First show the currently selected theme (grayed out since you can't
     94         // reselect it)
     95         Configuration configuration = configChooser.getConfiguration();
     96         String currentTheme = configuration.getTheme();
     97         String currentName = null;
     98         if (currentTheme != null) {
     99             currentName = ResourceHelper.styleToTheme(currentTheme);
    100             SelectThemeAction action = new SelectThemeAction(configChooser,
    101                     currentName,
    102                     currentTheme,
    103                     true /* selected */);
    104             action.setEnabled(false);
    105             manager.add(action);
    106             manager.add(new Separator());
    107         }
    108 
    109         String preferred = configuration.computePreferredTheme();
    110         if (preferred != null && !preferred.equals(currentTheme)) {
    111             manager.add(new SelectThemeAction(configChooser,
    112                     ResourceHelper.styleToTheme(preferred),
    113                     preferred, false /* selected */));
    114             manager.add(new Separator());
    115         }
    116 
    117         IAndroidTarget target = configuration.getTarget();
    118         int apiLevel = target != null ? target.getVersion().getApiLevel() : 1;
    119         boolean hasHolo = apiLevel >= 11;   // Honeycomb
    120         boolean hasDeviceDefault = apiLevel >= 14; // ICS
    121 
    122         // TODO: Add variations of the current theme here, e.g.
    123         // if you're using Theme.Holo, add Theme.Holo.Dialog, Theme.Holo.Panel,
    124         // Theme.Holo.Wallpaper etc
    125 
    126         manager.add(new ThemeMenuAction(MENU_PROJECT, "Project Themes",
    127                 configChooser, themeList));
    128         manager.add(new ThemeMenuAction(MENU_MANIFEST, "Manifest Themes",
    129                 configChooser, themeList));
    130 
    131         manager.add(new Separator());
    132 
    133         if (hasHolo) {
    134             manager.add(new ThemeMenuAction(MENU_HOLO, "Holo",
    135                     configChooser, themeList));
    136             manager.add(new ThemeMenuAction(MENU_HOLO_LIGHT, "Holo.Light",
    137                     configChooser, themeList));
    138         }
    139         if (hasDeviceDefault) {
    140             manager.add(new ThemeMenuAction(MENU_DEVICE, "DeviceDefault",
    141                     configChooser, themeList));
    142             manager.add(new ThemeMenuAction(MENU_DEVICE_LIGHT, "DeviceDefault.Light",
    143                     configChooser, themeList));
    144         }
    145         manager.add(new ThemeMenuAction(MENU_THEME, "Theme",
    146                 configChooser, themeList));
    147         manager.add(new ThemeMenuAction(MENU_THEME_LIGHT, "Theme.Light",
    148                 configChooser, themeList));
    149 
    150         // TODO: Add generic types like Wallpaper, Dialog, Alert, etc here, with
    151         // submenus for picking it within each theme category?
    152 
    153         manager.add(new Separator());
    154         manager.add(new ThemeMenuAction(MENU_ALL, "All",
    155                 configChooser, themeList));
    156 
    157         if (currentTheme != null) {
    158             assert currentName != null;
    159             manager.add(new Separator());
    160             String title = String.format("Open %1$s Declaration...", currentName);
    161             manager.add(new OpenThemeAction(title, configChooser.getEditedFile(), currentTheme));
    162         }
    163 
    164         Menu menu = manager.createContextMenu(configChooser.getShell());
    165 
    166         Rectangle bounds = combo.getBounds();
    167         Point location = new Point(bounds.x, bounds.y + bounds.height);
    168         location = combo.getParent().toDisplay(location);
    169         menu.setLocation(location.x, location.y);
    170         menu.setVisible(true);
    171     }
    172 
    173     @Override
    174     protected void addMenuItems(Menu menu) {
    175         switch (mType) {
    176             case MENU_ALL:
    177                 addMenuItems(menu, mThemeList);
    178                 break;
    179 
    180             case MENU_MANIFEST: {
    181                 IProject project = mConfigChooser.getEditedFile().getProject();
    182                 ManifestInfo manifest = ManifestInfo.get(project);
    183                 Configuration configuration = mConfigChooser.getConfiguration();
    184                 String activity = configuration.getActivity();
    185                 if (activity != null) {
    186                     ActivityAttributes attributes = manifest.getActivityAttributes(activity);
    187                     if (attributes != null) {
    188                         String theme = attributes.getTheme();
    189                         if (theme != null) {
    190                             addMenuItem(menu, theme, isSelectedTheme(theme));
    191                         }
    192                     }
    193                 }
    194 
    195                 String manifestTheme = manifest.getManifestTheme();
    196                 boolean found = false;
    197                 Set<String> allThemes = new HashSet<String>();
    198                 if (manifestTheme != null) {
    199                     found = true;
    200                     allThemes.add(manifestTheme);
    201                 }
    202                 for (ActivityAttributes info : manifest.getActivityAttributesMap().values()) {
    203                     if (info.getTheme() != null) {
    204                         found = true;
    205                         allThemes.add(info.getTheme());
    206                     }
    207                 }
    208                 List<String> sorted = new ArrayList<String>(allThemes);
    209                 Collections.sort(sorted);
    210                 String current = configuration.getTheme();
    211                 for (String theme : sorted) {
    212                     boolean selected = theme.equals(current);
    213                     addMenuItem(menu, theme, selected);
    214                 }
    215                 if (!found) {
    216                     addDisabledMessageItem("No themes are registered in the manifest");
    217                 }
    218                 break;
    219             }
    220             case MENU_PROJECT: {
    221                 int size = mThemeList.size();
    222                 List<String> themes = new ArrayList<String>(size);
    223                 for (int i = 0; i < size; i++) {
    224                     String theme = mThemeList.get(i);
    225                     if (ResourceHelper.isProjectStyle(theme)) {
    226                         themes.add(theme);
    227                     }
    228                 }
    229                 if (themes.isEmpty()) {
    230                     addDisabledMessageItem("There are no local theme styles in the project");
    231                 } else {
    232                     addMenuItems(menu, themes);
    233                 }
    234                 break;
    235             }
    236             case MENU_THEME: {
    237                 // Can't just use the usual filterThemes() call here because we need
    238                 // to exclude on multiple prefixes: Holo, DeviceDefault, Light, ...
    239                 List<String> themes = new ArrayList<String>(mThemeList.size());
    240                 for (String theme : mThemeList) {
    241                     if (theme.startsWith(THEME_PREFIX)
    242                             && !theme.startsWith(LIGHT_PREFIX)
    243                             && !theme.startsWith(HOLO_PREFIX)
    244                             && !theme.startsWith(DEVICE_PREFIX)) {
    245                         themes.add(theme);
    246                     }
    247                 }
    248 
    249                 addMenuItems(menu, themes);
    250                 break;
    251             }
    252             case MENU_THEME_LIGHT:
    253                 addMenuItems(menu, filterThemes(LIGHT_PREFIX, null));
    254                 break;
    255             case MENU_HOLO:
    256                 addMenuItems(menu, filterThemes(HOLO_PREFIX, HOLO_LIGHT_PREFIX));
    257                 break;
    258             case MENU_HOLO_LIGHT:
    259                 addMenuItems(menu, filterThemes(HOLO_LIGHT_PREFIX, null));
    260                 break;
    261             case MENU_DEVICE:
    262                 addMenuItems(menu, filterThemes(DEVICE_PREFIX, DEVICE_LIGHT_PREFIX));
    263                 break;
    264             case MENU_DEVICE_LIGHT:
    265                 addMenuItems(menu, filterThemes(DEVICE_LIGHT_PREFIX, null));
    266                 break;
    267         }
    268     }
    269 
    270     private List<String> filterThemes(String include, String exclude) {
    271         List<String> themes = new ArrayList<String>(mThemeList.size());
    272         for (String theme : mThemeList) {
    273             if (theme.startsWith(include) && (exclude == null || !theme.startsWith(exclude))) {
    274                 themes.add(theme);
    275             }
    276         }
    277 
    278         return themes;
    279     }
    280 
    281     private void addMenuItems(Menu menu, List<String> themes) {
    282         String current = mConfigChooser.getConfiguration().getTheme();
    283         for (String theme : themes) {
    284             addMenuItem(menu, theme, theme.equals(current));
    285         }
    286     }
    287 
    288     private boolean isSelectedTheme(String theme) {
    289         return theme.equals(mConfigChooser.getConfiguration().getTheme());
    290     }
    291 
    292     private void addMenuItem(Menu menu, String theme, boolean selected) {
    293         String title = ResourceHelper.styleToTheme(theme);
    294         SelectThemeAction action = new SelectThemeAction(mConfigChooser, title, theme, selected);
    295         new ActionContributionItem(action).fill(menu, -1);
    296     }
    297 
    298     private static class OpenThemeAction extends Action {
    299         private final String mTheme;
    300         private final IFile mFile;
    301 
    302         private OpenThemeAction(String title, IFile file, String theme) {
    303             super(title, IAction.AS_PUSH_BUTTON);
    304             mFile = file;
    305             mTheme = theme;
    306         }
    307 
    308         @Override
    309         public void run() {
    310             IProject project = mFile.getProject();
    311             IHyperlink[] links = Hyperlinks.getResourceLinks(null, mTheme, project, null);
    312             if (links != null && links.length > 0) {
    313                 IHyperlink link = links[0];
    314                 link.open();
    315             }
    316         }
    317     }
    318 }
    319