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.resources.ResourceHelper;
     25 import com.android.sdklib.IAndroidTarget;
     26 
     27 import org.eclipse.core.resources.IFile;
     28 import org.eclipse.core.resources.IProject;
     29 import org.eclipse.jface.action.Action;
     30 import org.eclipse.jface.action.ActionContributionItem;
     31 import org.eclipse.jface.action.IAction;
     32 import org.eclipse.jface.action.MenuManager;
     33 import org.eclipse.jface.action.Separator;
     34 import org.eclipse.jface.text.hyperlink.IHyperlink;
     35 import org.eclipse.swt.graphics.Point;
     36 import org.eclipse.swt.graphics.Rectangle;
     37 import org.eclipse.swt.widgets.Menu;
     38 import org.eclipse.swt.widgets.ToolItem;
     39 
     40 import java.util.ArrayList;
     41 import java.util.Collections;
     42 import java.util.HashSet;
     43 import java.util.List;
     44 import java.util.Map;
     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                 Map<String, String> activityThemes = manifest.getActivityThemes();
    184                 Configuration configuration = mConfigChooser.getConfiguration();
    185                 String activity = configuration.getActivity();
    186                 if (activity != null) {
    187                     String theme = activityThemes.get(activity);
    188                     if (theme != null) {
    189                         addMenuItem(menu, theme, isSelectedTheme(theme));
    190                     }
    191                 }
    192 
    193                 String manifestTheme = manifest.getManifestTheme();
    194                 if (activityThemes.size() > 0 || manifestTheme != null) {
    195                     Set<String> allThemes = new HashSet<String>(activityThemes.values());
    196                     if (manifestTheme != null) {
    197                         allThemes.add(manifestTheme);
    198                     }
    199                     List<String> sorted = new ArrayList<String>(allThemes);
    200                     Collections.sort(sorted);
    201                     String current = configuration.getTheme();
    202                     for (String theme : sorted) {
    203                         boolean selected = theme.equals(current);
    204                         addMenuItem(menu, theme, selected);
    205                     }
    206                 } else {
    207                     addDisabledMessageItem("No themes are registered in the manifest");
    208                 }
    209                 break;
    210             }
    211             case MENU_PROJECT: {
    212                 int size = mThemeList.size();
    213                 List<String> themes = new ArrayList<String>(size);
    214                 for (int i = 0; i < size; i++) {
    215                     String theme = mThemeList.get(i);
    216                     if (ResourceHelper.isProjectStyle(theme)) {
    217                         themes.add(theme);
    218                     }
    219                 }
    220                 if (themes.isEmpty()) {
    221                     addDisabledMessageItem("There are no local theme styles in the project");
    222                 } else {
    223                     addMenuItems(menu, themes);
    224                 }
    225                 break;
    226             }
    227             case MENU_THEME: {
    228                 // Can't just use the usual filterThemes() call here because we need
    229                 // to exclude on multiple prefixes: Holo, DeviceDefault, Light, ...
    230                 List<String> themes = new ArrayList<String>(mThemeList.size());
    231                 for (String theme : mThemeList) {
    232                     if (theme.startsWith(THEME_PREFIX)
    233                             && !theme.startsWith(LIGHT_PREFIX)
    234                             && !theme.startsWith(HOLO_PREFIX)
    235                             && !theme.startsWith(DEVICE_PREFIX)) {
    236                         themes.add(theme);
    237                     }
    238                 }
    239 
    240                 addMenuItems(menu, themes);
    241                 break;
    242             }
    243             case MENU_THEME_LIGHT:
    244                 addMenuItems(menu, filterThemes(LIGHT_PREFIX, null));
    245                 break;
    246             case MENU_HOLO:
    247                 addMenuItems(menu, filterThemes(HOLO_PREFIX, HOLO_LIGHT_PREFIX));
    248                 break;
    249             case MENU_HOLO_LIGHT:
    250                 addMenuItems(menu, filterThemes(HOLO_LIGHT_PREFIX, null));
    251                 break;
    252             case MENU_DEVICE:
    253                 addMenuItems(menu, filterThemes(DEVICE_PREFIX, DEVICE_LIGHT_PREFIX));
    254                 break;
    255             case MENU_DEVICE_LIGHT:
    256                 addMenuItems(menu, filterThemes(DEVICE_LIGHT_PREFIX, null));
    257                 break;
    258         }
    259     }
    260 
    261     private List<String> filterThemes(String include, String exclude) {
    262         List<String> themes = new ArrayList<String>(mThemeList.size());
    263         for (String theme : mThemeList) {
    264             if (theme.startsWith(include) && (exclude == null || !theme.startsWith(exclude))) {
    265                 themes.add(theme);
    266             }
    267         }
    268 
    269         return themes;
    270     }
    271 
    272     private void addMenuItems(Menu menu, List<String> themes) {
    273         String current = mConfigChooser.getConfiguration().getTheme();
    274         for (String theme : themes) {
    275             addMenuItem(menu, theme, theme.equals(current));
    276         }
    277     }
    278 
    279     private boolean isSelectedTheme(String theme) {
    280         return theme.equals(mConfigChooser.getConfiguration().getTheme());
    281     }
    282 
    283     private void addMenuItem(Menu menu, String theme, boolean selected) {
    284         String title = ResourceHelper.styleToTheme(theme);
    285         SelectThemeAction action = new SelectThemeAction(mConfigChooser, title, theme, selected);
    286         new ActionContributionItem(action).fill(menu, -1);
    287     }
    288 
    289     private static class OpenThemeAction extends Action {
    290         private final String mTheme;
    291         private final IFile mFile;
    292 
    293         private OpenThemeAction(String title, IFile file, String theme) {
    294             super(title, IAction.AS_PUSH_BUTTON);
    295             mFile = file;
    296             mTheme = theme;
    297         }
    298 
    299         @Override
    300         public void run() {
    301             IProject project = mFile.getProject();
    302             IHyperlink[] links = Hyperlinks.getResourceLinks(null, mTheme, project, null);
    303             if (links != null && links.length > 0) {
    304                 IHyperlink link = links[0];
    305                 link.open();
    306             }
    307         }
    308     }
    309 }
    310