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