• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 com.android.ide.eclipse.adt.internal.editors.IconFactory;
20 import com.android.ide.eclipse.adt.internal.resources.ResourceType;
21 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
22 import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
23 import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier;
24 import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier;
25 import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
26 import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier;
27 import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier;
28 import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQualifier;
29 import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density;
30 import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier.ScreenOrientation;
31 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
32 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
33 import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager;
34 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
35 import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.LanguageRegionVerifier;
36 import com.android.layoutlib.api.IResourceValue;
37 import com.android.layoutlib.api.IStyleResourceValue;
38 
39 import org.eclipse.draw2d.geometry.Rectangle;
40 import org.eclipse.swt.SWT;
41 import org.eclipse.swt.events.SelectionAdapter;
42 import org.eclipse.swt.events.SelectionEvent;
43 import org.eclipse.swt.events.SelectionListener;
44 import org.eclipse.swt.layout.GridData;
45 import org.eclipse.swt.layout.GridLayout;
46 import org.eclipse.swt.widgets.Button;
47 import org.eclipse.swt.widgets.Combo;
48 import org.eclipse.swt.widgets.Composite;
49 import org.eclipse.swt.widgets.Label;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 import java.util.SortedSet;
57 
58 /**
59  * A composite that displays the current configuration displayed in a Graphical Layout Editor.
60  */
61 public class ConfigurationComposite extends Composite {
62 
63     private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
64 
65     private Button mClippingButton;
66     private Label mCurrentLayoutLabel;
67 
68     private Combo mLocale;
69     private Combo mDeviceList;
70     private Combo mDeviceConfigs;
71     private Combo mThemeCombo;
72     private Button mCreateButton;
73 
74 
75     private int mPlatformThemeCount = 0;
76     private boolean mDisableUpdates = false;
77 
78     /** The {@link FolderConfiguration} representing the state of the UI controls */
79     private final FolderConfiguration mCurrentConfig = new FolderConfiguration();
80 
81     private List<LayoutDevice> mDevices;
82 
83     private final ArrayList<ResourceQualifier[] > mLocaleList =
84         new ArrayList<ResourceQualifier[]>();
85 
86     private final IConfigListener mListener;
87 
88     private boolean mClipping = true;
89 
90     private LayoutDevice mCurrentDevice;
91 
92     /**
93      * Interface implemented by the part which owns a {@link ConfigurationComposite}.
94      * This notifies the owners when the configuration change.
95      * The owner must also provide methods to provide the configuration that will
96      * be displayed.
97      */
98     public interface IConfigListener {
onConfigurationChange()99         void onConfigurationChange();
onThemeChange()100         void onThemeChange();
onCreate()101         void onCreate();
OnClippingChange()102         void OnClippingChange();
103 
getProjectResources()104         ProjectResources getProjectResources();
getFrameworkResources()105         ProjectResources getFrameworkResources();
getConfiguredProjectResources()106         Map<String, Map<String, IResourceValue>> getConfiguredProjectResources();
getConfiguredFrameworkResources()107         Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources();
108     }
109 
ConfigurationComposite(IConfigListener listener, Composite parent, int style)110     public ConfigurationComposite(IConfigListener listener, Composite parent, int style) {
111         super(parent, style);
112         mListener = listener;
113 
114         GridLayout gl;
115         GridData gd;
116         int cols = 10; // device*2+config*2+locale*2+separator*2+theme+createBtn
117 
118         // ---- First line: collapse button, clipping button, editing config display.
119         Composite labelParent = new Composite(this, SWT.NONE);
120         labelParent.setLayout(gl = new GridLayout(3, false));
121         gl.marginWidth = gl.marginHeight = 0;
122         labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
123         gd.horizontalSpan = cols;
124 
125         new Label(labelParent, SWT.NONE).setText("Editing config:");
126         mCurrentLayoutLabel = new Label(labelParent, SWT.NONE);
127         mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
128         gd.widthHint = 50;
129 
130         mClippingButton = new Button(labelParent, SWT.TOGGLE | SWT.FLAT);
131         mClippingButton.setSelection(mClipping);
132         mClippingButton.setToolTipText("Toggles screen clipping on/off");
133         mClippingButton.setImage(IconFactory.getInstance().getIcon("clipping")); //$NON-NLS-1$
134         mClippingButton.addSelectionListener(new SelectionAdapter() {
135             @Override
136             public void widgetSelected(SelectionEvent e) {
137                 OnClippingChange();
138             }
139         });
140 
141         // ---- 2nd line: device/config/locale/theme Combos, create button.
142 
143         setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
144         setLayout(gl = new GridLayout(cols, false));
145         gl.marginHeight = 0;
146         gl.horizontalSpacing = 0;
147 
148         new Label(this, SWT.NONE).setText("Devices");
149         mDeviceList = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
150         mDeviceList.setLayoutData(new GridData(
151                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
152         mDeviceList.addSelectionListener(new SelectionAdapter() {
153             @Override
154             public void widgetSelected(SelectionEvent e) {
155                 onDeviceChange(true /* recomputeLayout*/);
156             }
157         });
158 
159         new Label(this, SWT.NONE).setText("Config");
160         mDeviceConfigs = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
161         mDeviceConfigs.setLayoutData(new GridData(
162                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
163         mDeviceConfigs.addSelectionListener(new SelectionAdapter() {
164             @Override
165              public void widgetSelected(SelectionEvent e) {
166                 onDeviceConfigChange();
167             }
168         });
169 
170         new Label(this, SWT.NONE).setText("Locale");
171         mLocale = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
172         mLocale.setLayoutData(new GridData(
173                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
174         mLocale.addVerifyListener(new LanguageRegionVerifier());
175         mLocale.addSelectionListener(new SelectionListener() {
176             public void widgetDefaultSelected(SelectionEvent e) {
177                 onLocaleChange();
178             }
179             public void widgetSelected(SelectionEvent e) {
180                 onLocaleChange();
181             }
182         });
183 
184         // first separator
185         Label separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL);
186         separator.setLayoutData(gd = new GridData(
187                 GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
188         gd.heightHint = 0;
189 
190         mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN);
191         mThemeCombo.setEnabled(false);
192         updateUIFromResources();
193         mThemeCombo.addSelectionListener(new SelectionAdapter() {
194             @Override
195             public void widgetSelected(SelectionEvent e) {
196                 onThemeChange();
197             }
198         });
199 
200         // second separator
201         separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL);
202         separator.setLayoutData(gd = new GridData(
203                 GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
204         gd.heightHint = 0;
205 
206         mCreateButton = new Button(this, SWT.PUSH | SWT.FLAT);
207         mCreateButton.setText("Create...");
208         mCreateButton.setEnabled(false);
209         mCreateButton.addSelectionListener(new SelectionAdapter() {
210             @Override
211             public void widgetSelected(SelectionEvent e) {
212                 if (mListener != null) {
213                     mListener.onCreate();
214                 }
215             }
216         });
217 
218         initUiWithDevices();
219 
220         onDeviceConfigChange();
221     }
222 
223 
getCurrentConfig()224     public FolderConfiguration getCurrentConfig() {
225         return mCurrentConfig;
226     }
227 
getCurrentConfig(FolderConfiguration config)228     public void getCurrentConfig(FolderConfiguration config) {
229         config.set(mCurrentConfig);
230     }
231 
232     /**
233      * Returns the currently selected {@link Density}. This is guaranteed to be non null.
234      */
getDensity()235     public Density getDensity() {
236         if (mCurrentConfig != null) {
237             PixelDensityQualifier qual = mCurrentConfig.getPixelDensityQualifier();
238             if (qual != null) {
239                 // just a sanity check
240                 Density d = qual.getValue();
241                 if (d != Density.NODPI) {
242                     return d;
243                 }
244             }
245         }
246 
247         // no config? return medium as the default density.
248         return Density.MEDIUM;
249     }
250 
251     /**
252      * Returns the current device xdpi.
253      */
getXDpi()254     public float getXDpi() {
255         if (mCurrentDevice != null) {
256             float dpi = mCurrentDevice.getXDpi();
257             if (Float.isNaN(dpi) == false) {
258                 return dpi;
259             }
260         }
261 
262         // get the pixel density as the density.
263         return getDensity().getDpiValue();
264     }
265 
266     /**
267      * Returns the current device ydpi.
268      */
getYDpi()269     public float getYDpi() {
270         if (mCurrentDevice != null) {
271             float dpi = mCurrentDevice.getYDpi();
272             if (Float.isNaN(dpi) == false) {
273                 return dpi;
274             }
275         }
276 
277         // get the pixel density as the density.
278         return getDensity().getDpiValue();
279     }
280 
getScreenBounds()281     public Rectangle getScreenBounds() {
282         // get the orientation from the current device config
283         ScreenOrientationQualifier qual = mCurrentConfig.getScreenOrientationQualifier();
284         ScreenOrientation orientation = ScreenOrientation.PORTRAIT;
285         if (qual != null) {
286             orientation = qual.getValue();
287         }
288 
289         // get the device screen dimension
290         ScreenDimensionQualifier qual2 = mCurrentConfig.getScreenDimensionQualifier();
291         int s1, s2;
292         if (qual2 != null) {
293             s1 = qual2.getValue1();
294             s2 = qual2.getValue2();
295         } else {
296             s1 = 480;
297             s2 = 320;
298         }
299 
300         switch (orientation) {
301             default:
302             case PORTRAIT:
303                 return new Rectangle(0, 0, s2, s1);
304             case LANDSCAPE:
305                 return new Rectangle(0, 0, s1, s2);
306             case SQUARE:
307                 return new Rectangle(0, 0, s1, s1);
308         }
309     }
310 
311     /**
312      * Updates the UI from values in the resources, such as languages, regions, themes, etc...
313      * This must be called from the UI thread.
314      */
updateUIFromResources()315     public void updateUIFromResources() {
316         if (mListener == null) {
317             return; // can't do anything w/o it.
318         }
319 
320         ProjectResources frameworkProject = mListener.getFrameworkResources();
321 
322         mDisableUpdates = true;
323 
324         // Reset stuff
325         int selection = mThemeCombo.getSelectionIndex();
326         mThemeCombo.removeAll();
327         mPlatformThemeCount = 0;
328 
329         mLocale.removeAll();
330         mLocaleList.clear();
331 
332         SortedSet<String> languages = null;
333         ArrayList<String> themes = new ArrayList<String>();
334 
335         // get the themes, and languages from the Framework.
336         if (frameworkProject != null) {
337             // get the configured resources for the framework
338             Map<String, Map<String, IResourceValue>> frameworResources =
339                 mListener.getConfiguredFrameworkResources();
340 
341             if (frameworResources != null) {
342                 // get the styles.
343                 Map<String, IResourceValue> styles = frameworResources.get(
344                         ResourceType.STYLE.getName());
345 
346 
347                 // collect the themes out of all the styles.
348                 for (IResourceValue value : styles.values()) {
349                     String name = value.getName();
350                     if (name.startsWith("Theme.") || name.equals("Theme")) {
351                         themes.add(value.getName());
352                         mPlatformThemeCount++;
353                     }
354                 }
355 
356                 // sort them and add them to the combo
357                 Collections.sort(themes);
358 
359                 for (String theme : themes) {
360                     mThemeCombo.add(theme);
361                 }
362 
363                 mPlatformThemeCount = themes.size();
364                 themes.clear();
365             }
366         }
367 
368         // now get the themes and languages from the project.
369         ProjectResources project = mListener.getProjectResources();
370         // in cases where the opened file is not linked to a project, this could be null.
371         if (project != null) {
372             // get the configured resources for the project
373             Map<String, Map<String, IResourceValue>> configuredProjectRes =
374                 mListener.getConfiguredProjectResources();
375 
376             if (configuredProjectRes != null) {
377                 // get the styles.
378                 Map<String, IResourceValue> styleMap = configuredProjectRes.get(
379                         ResourceType.STYLE.getName());
380 
381                 if (styleMap != null) {
382                     // collect the themes out of all the styles, ie styles that extend,
383                     // directly or indirectly a platform theme.
384                     for (IResourceValue value : styleMap.values()) {
385                         if (isTheme(value, styleMap)) {
386                             themes.add(value.getName());
387                         }
388                     }
389 
390                     // sort them and add them the to the combo.
391                     if (mPlatformThemeCount > 0 && themes.size() > 0) {
392                         mThemeCombo.add(THEME_SEPARATOR);
393                     }
394 
395                     Collections.sort(themes);
396 
397                     for (String theme : themes) {
398                         mThemeCombo.add(theme);
399                     }
400                 }
401             }
402 
403             // now get the languages from the project.
404             languages = project.getLanguages();
405         }
406 
407         // add the languages to the Combo
408         mLocale.add("Default");
409         mLocaleList.add(new ResourceQualifier[] { null, null });
410 
411         if (project != null && languages != null && languages.size() > 0) {
412             for (String language : languages) {
413                 // first the language alone
414                 mLocale.add(language);
415                 LanguageQualifier qual = new LanguageQualifier(language);
416                 mLocaleList.add(new ResourceQualifier[] { qual, null });
417 
418                 // now find the matching regions and add them
419                 SortedSet<String> regions = project.getRegions(language);
420                 for (String region : regions) {
421                     mLocale.add(String.format("%1$s_%2$s", language, region)); //$NON-NLS-1$
422                     RegionQualifier qual2 = new RegionQualifier(region);
423                     mLocaleList.add(new ResourceQualifier[] { qual, qual2 });
424                 }
425 
426             }
427         }
428 
429         mDisableUpdates = false;
430 
431         // handle default selection of themes
432         if (mThemeCombo.getItemCount() > 0) {
433             mThemeCombo.setEnabled(true);
434             if (selection == -1) {
435                 selection = 0;
436             }
437 
438             if (mThemeCombo.getItemCount() <= selection) {
439                 mThemeCombo.select(0);
440             } else {
441                 mThemeCombo.select(selection);
442             }
443         } else {
444             mThemeCombo.setEnabled(false);
445         }
446 
447         mThemeCombo.getParent().layout();
448     }
449 
450     /**
451      * Returns the current theme, or null if the combo has no selection.
452      */
getTheme()453     public String getTheme() {
454         int themeIndex = mThemeCombo.getSelectionIndex();
455         if (themeIndex != -1) {
456             return mThemeCombo.getItem(themeIndex);
457         }
458 
459         return null;
460     }
461 
462     /**
463      * Returns whether the current theme selection is a project theme.
464      * <p/>The returned value is meaningless if {@link #getTheme()} returns <code>null</code>.
465      * @return true for project theme, false for framework theme
466      */
isProjectTheme()467     public boolean isProjectTheme() {
468         return mThemeCombo.getSelectionIndex() >= mPlatformThemeCount;
469     }
470 
getClipping()471     public boolean getClipping() {
472         return mClipping;
473     }
474 
setEnabledCreate(boolean enabled)475     public void setEnabledCreate(boolean enabled) {
476         mCreateButton.setEnabled(enabled);
477     }
478 
setClippingSupport(boolean b)479     public void setClippingSupport(boolean b) {
480         mClippingButton.setEnabled(b);
481         if (b) {
482             mClippingButton.setToolTipText("Toggles screen clipping on/off");
483         } else {
484             mClipping = true;
485             mClippingButton.setSelection(true);
486             mClippingButton.setToolTipText("Non clipped rendering is not supported");
487         }
488     }
489 
490     /**
491      * Update the UI controls state with a given {@link FolderConfiguration}.
492      * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
493      * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
494      * the UI control is not modified. However if the value in the control is not the default value,
495      * a warning icon is shown.
496      * @param config The {@link FolderConfiguration} to set.
497      * @param force Whether the UI should be changed to exactly match the received configuration.
498      */
setConfiguration(FolderConfiguration config, boolean force)499     public void setConfiguration(FolderConfiguration config, boolean force) {
500         mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets.
501 
502         // TODO: find a device that can display this particular config or create a custom one if needed.
503 
504 
505         // update the string showing the folder name
506         String current = config.toDisplayString();
507         mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
508 
509         mDisableUpdates = false;
510     }
511 
512     /**
513      * Reloads the list of {@link LayoutDevice} from the {@link Sdk}.
514      * @param notifyListener
515      */
reloadDevices(boolean notifyListener)516     public void reloadDevices(boolean notifyListener) {
517         loadDevices();
518         initUiWithDevices();
519         onDeviceChange(notifyListener);
520     }
521 
loadDevices()522     private void loadDevices() {
523         mDevices = null;
524 
525         Sdk sdk = Sdk.getCurrent();
526         if (sdk != null) {
527             LayoutDeviceManager manager = sdk.getLayoutDeviceManager();
528             mDevices = manager.getCombinedList();
529         }
530     }
531 
532     /**
533      * Init the UI with the list of Devices.
534      */
initUiWithDevices()535     private void initUiWithDevices() {
536         // remove older devices if applicable
537         mDeviceList.removeAll();
538         mDeviceConfigs.removeAll();
539 
540         // fill with the devices
541         if (mDevices != null) {
542             for (LayoutDevice device : mDevices) {
543                 mDeviceList.add(device.getName());
544             }
545             mDeviceList.select(0);
546 
547             if (mDevices.size() > 0) {
548                 Map<String, FolderConfiguration> configs = mDevices.get(0).getConfigs();
549                 Set<String> configNames = configs.keySet();
550                 for (String name : configNames) {
551                     mDeviceConfigs.add(name);
552                 }
553                 mDeviceConfigs.select(0);
554                 if (configNames.size() == 1) {
555                     mDeviceConfigs.setEnabled(false);
556                 }
557             }
558         }
559 
560         // add the custom item
561         mDeviceList.add("Custom...");
562     }
563 
564     /**
565      * Call back for language combo selection
566      */
onLocaleChange()567     private void onLocaleChange() {
568         // because mLanguage triggers onLanguageChange at each modification, the filling
569         // of the combo with data will trigger notifications, and we don't want that.
570         if (mDisableUpdates == true) {
571             return;
572         }
573 
574         int localeIndex = mLocale.getSelectionIndex();
575         ResourceQualifier[] localeQualifiers = mLocaleList.get(localeIndex);
576 
577         mCurrentConfig.setLanguageQualifier((LanguageQualifier)localeQualifiers[0]); // language
578         mCurrentConfig.setRegionQualifier((RegionQualifier)localeQualifiers[1]); // region
579 
580         if (mListener != null) {
581             mListener.onConfigurationChange();
582         }
583     }
584 
onDeviceChange(boolean recomputeLayout)585     private void onDeviceChange(boolean recomputeLayout) {
586 
587         int deviceIndex = mDeviceList.getSelectionIndex();
588         if (deviceIndex != -1) {
589             // check if the user is ask for the custom item
590             if (deviceIndex == mDeviceList.getItemCount() - 1) {
591                 ConfigManagerDialog dialog = new ConfigManagerDialog(getShell());
592                 dialog.open();
593 
594                 // save the user devices
595                 Sdk.getCurrent().getLayoutDeviceManager().save();
596 
597                 // reload the combo with the new content.
598                 loadDevices();
599                 initUiWithDevices();
600 
601                 // at this point we need to reset the combo to something (hopefully) valid.
602                 // look for the previous selected device
603                 int index = mDevices.indexOf(mCurrentDevice);
604                 if (index != -1) {
605                     mDeviceList.select(index);
606                 } else {
607                     // we should at least have one built-in device, so we select it
608                     mDeviceList.select(0);
609                 }
610 
611                 // force a redraw
612                 onDeviceChange(true /*recomputeLayout*/);
613 
614                 return;
615             }
616 
617             mCurrentDevice = mDevices.get(deviceIndex);
618         } else {
619             mCurrentDevice = null;
620         }
621 
622         mDeviceConfigs.removeAll();
623 
624         if (mCurrentDevice != null) {
625             Set<String> configNames = mCurrentDevice.getConfigs().keySet();
626             for (String name : configNames) {
627                 mDeviceConfigs.add(name);
628             }
629 
630             mDeviceConfigs.select(0);
631             mDeviceConfigs.setEnabled(configNames.size() > 1);
632         }
633         if (recomputeLayout) {
634             onDeviceConfigChange();
635         }
636     }
637 
onDeviceConfigChange()638     private void onDeviceConfigChange() {
639         if (mCurrentDevice != null) {
640             int configIndex = mDeviceConfigs.getSelectionIndex();
641             String name = mDeviceConfigs.getItem(configIndex);
642             FolderConfiguration config = mCurrentDevice.getConfigs().get(name);
643 
644             // get the current qualifiers from the current config
645             LanguageQualifier lang = mCurrentConfig.getLanguageQualifier();
646             RegionQualifier region = mCurrentConfig.getRegionQualifier();
647             VersionQualifier version = mCurrentConfig.getVersionQualifier();
648 
649             // replace the config with the one from the device
650             mCurrentConfig.set(config);
651 
652             // and put back the rest of the qualifiers
653             mCurrentConfig.addQualifier(lang);
654             mCurrentConfig.addQualifier(region);
655             mCurrentConfig.addQualifier(version);
656 
657             if (mListener != null) {
658                 mListener.onConfigurationChange();
659             }
660         }
661     }
662 
onThemeChange()663     private void onThemeChange() {
664         int themeIndex = mThemeCombo.getSelectionIndex();
665         if (themeIndex != -1) {
666             String theme = mThemeCombo.getItem(themeIndex);
667 
668             if (theme.equals(THEME_SEPARATOR)) {
669                 mThemeCombo.select(0);
670             }
671 
672             if (mListener != null) {
673                 mListener.onThemeChange();
674             }
675         }
676     }
677 
OnClippingChange()678     protected void OnClippingChange() {
679         mClipping = mClippingButton.getSelection();
680         if (mListener != null) {
681             mListener.OnClippingChange();
682         }
683     }
684 
685     /**
686      * Returns whether the given <var>style</var> is a theme.
687      * This is done by making sure the parent is a theme.
688      * @param value the style to check
689      * @param styleMap the map of styles for the current project. Key is the style name.
690      * @return True if the given <var>style</var> is a theme.
691      */
isTheme(IResourceValue value, Map<String, IResourceValue> styleMap)692     private boolean isTheme(IResourceValue value, Map<String, IResourceValue> styleMap) {
693         if (value instanceof IStyleResourceValue) {
694             IStyleResourceValue style = (IStyleResourceValue)value;
695 
696             boolean frameworkStyle = false;
697             String parentStyle = style.getParentStyle();
698             if (parentStyle == null) {
699                 // if there is no specified parent style we look an implied one.
700                 // For instance 'Theme.light' is implied child style of 'Theme',
701                 // and 'Theme.light.fullscreen' is implied child style of 'Theme.light'
702                 String name = style.getName();
703                 int index = name.lastIndexOf('.');
704                 if (index != -1) {
705                     parentStyle = name.substring(0, index);
706                 }
707             } else {
708                 // remove the useless @ if it's there
709                 if (parentStyle.startsWith("@")) {
710                     parentStyle = parentStyle.substring(1);
711                 }
712 
713                 // check for framework identifier.
714                 if (parentStyle.startsWith("android:")) {
715                     frameworkStyle = true;
716                     parentStyle = parentStyle.substring("android:".length());
717                 }
718 
719                 // at this point we could have the format style/<name>. we want only the name
720                 if (parentStyle.startsWith("style/")) {
721                     parentStyle = parentStyle.substring("style/".length());
722                 }
723             }
724 
725             if (parentStyle != null) {
726                 if (frameworkStyle) {
727                     // if the parent is a framework style, it has to be 'Theme' or 'Theme.*'
728                     return parentStyle.equals("Theme") || parentStyle.startsWith("Theme.");
729                 } else {
730                     // if it's a project style, we check this is a theme.
731                     value = styleMap.get(parentStyle);
732                     if (value != null) {
733                         return isTheme(value, styleMap);
734                     }
735                 }
736             }
737         }
738 
739         return false;
740     }
741 }
742 
743