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 
ThemeMenuAction(int type, String title, ConfigurationChooser configuration, List<String> themeList)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 
showThemeMenu(ConfigurationChooser configChooser, ToolItem combo, List<String> themeList)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
addMenuItems(Menu menu)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 
filterThemes(String include, String exclude)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 
addMenuItems(Menu menu, List<String> themes)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 
isSelectedTheme(String theme)288     private boolean isSelectedTheme(String theme) {
289         return theme.equals(mConfigChooser.getConfiguration().getTheme());
290     }
291 
addMenuItem(Menu menu, String theme, boolean selected)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 
OpenThemeAction(String title, IFile file, String theme)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
run()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