diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui')
11 files changed, 4526 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java new file mode 100644 index 000000000..44f90822a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java @@ -0,0 +1,1989 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import com.android.SdkConstants; +import com.android.ide.common.resources.LocaleManager; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.DensityQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; +import com.android.ide.common.resources.configuration.LocaleQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; +import com.android.ide.common.resources.configuration.ScreenHeightQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +import com.android.ide.common.resources.configuration.ScreenRatioQualifier; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; +import com.android.ide.common.resources.configuration.ScreenWidthQualifier; +import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; +import com.android.ide.common.resources.configuration.TextInputMethodQualifier; +import com.android.ide.common.resources.configuration.TouchScreenQualifier; +import com.android.ide.common.resources.configuration.UiModeQualifier; +import com.android.ide.common.resources.configuration.VersionQualifier; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.resources.Density; +import com.android.resources.Keyboard; +import com.android.resources.KeyboardState; +import com.android.resources.LayoutDirection; +import com.android.resources.Navigation; +import com.android.resources.NavigationState; +import com.android.resources.NightMode; +import com.android.resources.ResourceEnum; +import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenRatio; +import com.android.resources.ScreenSize; +import com.android.resources.TouchScreen; +import com.android.resources.UiMode; + +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +/** + * Custom UI widget to let user build a Folder configuration. + * <p/> + * To use this, instantiate somewhere in the UI and then: + * <ul> + * <li>Use {@link #setConfiguration(String)} or {@link #setConfiguration(FolderConfiguration)}. + * <li>Retrieve the configuration using {@link #getConfiguration(FolderConfiguration)}. + * </ul> + */ +public class ConfigurationSelector extends Composite { + + public static final int WIDTH_HINT = 600; + public static final int HEIGHT_HINT = 250; + + private Runnable mOnChangeListener; + + private TableViewer mFullTableViewer; + private TableViewer mSelectionTableViewer; + private Button mAddButton; + private Button mRemoveButton; + private StackLayout mStackLayout; + + private boolean mOnRefresh = false; + + private final FolderConfiguration mBaseConfiguration = new FolderConfiguration(); + private final FolderConfiguration mSelectedConfiguration = new FolderConfiguration(); + + private final HashMap<Class<? extends ResourceQualifier>, QualifierEditBase> mUiMap = + new HashMap<Class<? extends ResourceQualifier>, QualifierEditBase>(); + private final SelectorMode mMode; + private Composite mQualifierEditParent; + private IQualifierFilter mQualifierFilter; + + /** + * Basic of {@link VerifyListener} to only accept digits. + */ + private static class DigitVerifier implements VerifyListener { + @Override + public void verifyText(VerifyEvent e) { + // check for digit only. + for (int i = 0 ; i < e.text.length(); i++) { + char letter = e.text.charAt(i); + if (letter < '0' || letter > '9') { + e.doit = false; + return; + } + } + } + } + + /** + * Implementation of {@link VerifyListener} for Country Code qualifiers. + */ + public static class MobileCodeVerifier extends DigitVerifier { + @Override + public void verifyText(VerifyEvent e) { + super.verifyText(e); + + // basic tests passed? + if (e.doit) { + // check the max 3 digits. + if (e.text.length() - e.end + e.start + + ((Text)e.getSource()).getText().length() > 3) { + e.doit = false; + } + } + } + } + + /** + * Implementation of {@link VerifyListener} for the Language and Region qualifiers. + */ + public static class LanguageRegionVerifier implements VerifyListener { + @Override + public void verifyText(VerifyEvent e) { + // check for length + if (e.text.length() - e.end + e.start + ((Combo)e.getSource()).getText().length() > 6) { + e.doit = false; + return; + } + + // check for lower case only. + for (int i = 0 ; i < e.text.length(); i++) { + char letter = e.text.charAt(i); + if (letter == '-') { + if (i+e.start != 2) { + e.doit = false; + return; + } else { + continue; + } + } + if (i+e.start == 3 && letter != 'r') { + e.doit = false; + return; + } + if ((letter < 'a' || letter > 'z') && (letter < 'A' || letter > 'Z')) { + e.doit = false; + return; + } + } + } + } + + /** + * Implementation of {@link VerifyListener} for the Density qualifier. + */ + public static class DensityVerifier extends DigitVerifier { } + + /** + * Implementation of {@link VerifyListener} for the Screen Dimension qualifier. + */ + public static class DimensionVerifier extends DigitVerifier { } + + /** + * Enum for the state of the configuration being created. + */ + public enum ConfigurationState { + OK, INVALID_CONFIG, REGION_WITHOUT_LANGUAGE; + } + + /** + * Behavior mode for the Selector. + * + * @see #DEFAULT + * @see #DEVICE_ONLY + * @see #CONFIG_ONLY + */ + public enum SelectorMode { + /** the default mode */ + DEFAULT, + /** mode forcing the qualifier values to be valid on a device. + * For instance {@link Density#NODPI} is a valid qualifier for a resource configuration but + * this is not valid on a device */ + DEVICE_ONLY, + /** mode where only the specific config can be edited. The user can only select + * which non-empty qualifier to select. */ + CONFIG_ONLY; + } + + /** + * A filter for {@link ResourceQualifier}. + * @see ConfigurationSelector#setQualifierFilter(IQualifierFilter) + */ + public interface IQualifierFilter { + /** + * Returns true of the qualifier is accepted. + */ + boolean accept(ResourceQualifier qualifier); + } + + /** + * Creates the selector. + * <p/> + * The {@link SelectorMode} changes the behavior of the selector depending on what is being + * edited (a device config, a resource config, a given configuration). + * + * @param parent the composite parent. + * @param mode the mode for the selector. + */ + public ConfigurationSelector(Composite parent, SelectorMode mode) { + super(parent, SWT.NONE); + + mMode = mode; + mBaseConfiguration.createDefault(); + + GridLayout gl = new GridLayout(4, false); + gl.marginWidth = gl.marginHeight = 0; + setLayout(gl); + + // first column is the first table + final Table fullTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); + fullTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + fullTable.setHeaderVisible(true); + fullTable.setLinesVisible(true); + + // create the column + final TableColumn fullTableColumn = new TableColumn(fullTable, SWT.LEFT); + // set the header + fullTableColumn.setText("Available Qualifiers"); + + fullTable.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = fullTable.getClientArea(); + fullTableColumn.setWidth(r.width); + } + }); + + mFullTableViewer = new TableViewer(fullTable); + mFullTableViewer.setContentProvider(new QualifierContentProvider()); + // the label provider must return the value of the label only if the mode is + // CONFIG_ONLY + mFullTableViewer.setLabelProvider(new QualifierLabelProvider( + mMode == SelectorMode.CONFIG_ONLY)); + mFullTableViewer.setInput(mBaseConfiguration); + mFullTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + ISelection selection = event.getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection structSelection = (IStructuredSelection)selection; + Object first = structSelection.getFirstElement(); + + if (first instanceof ResourceQualifier) { + mAddButton.setEnabled(true); + return; + } + } + + mAddButton.setEnabled(false); + } + }); + + // 2nd column is the left/right arrow button + Composite buttonComposite = new Composite(this, SWT.NONE); + gl = new GridLayout(1, false); + gl.marginWidth = gl.marginHeight = 0; + buttonComposite.setLayout(gl); + buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + + new Composite(buttonComposite, SWT.NONE); + mAddButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH); + mAddButton.setText("->"); + mAddButton.setEnabled(false); + mAddButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IStructuredSelection selection = + (IStructuredSelection)mFullTableViewer.getSelection(); + + Object first = selection.getFirstElement(); + if (first instanceof ResourceQualifier) { + ResourceQualifier qualifier = (ResourceQualifier)first; + + mBaseConfiguration.removeQualifier(qualifier); + mSelectedConfiguration.addQualifier(qualifier); + + mFullTableViewer.refresh(); + mSelectionTableViewer.refresh(); + mSelectionTableViewer.setSelection(new StructuredSelection(qualifier), true); + + onChange(false /* keepSelection */); + } + } + }); + + mRemoveButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH); + mRemoveButton.setText("<-"); + mRemoveButton.setEnabled(false); + mRemoveButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IStructuredSelection selection = + (IStructuredSelection)mSelectionTableViewer.getSelection(); + + Object first = selection.getFirstElement(); + if (first instanceof ResourceQualifier) { + ResourceQualifier qualifier = (ResourceQualifier)first; + + mSelectedConfiguration.removeQualifier(qualifier); + mBaseConfiguration.addQualifier(qualifier); + + mFullTableViewer.refresh(); + mSelectionTableViewer.refresh(); + + onChange(false /* keepSelection */); + } + } + }); + + // 3rd column is the selected config table + final Table selectionTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); + selectionTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + selectionTable.setHeaderVisible(true); + selectionTable.setLinesVisible(true); + + // create the column + final TableColumn selectionTableColumn = new TableColumn(selectionTable, SWT.LEFT); + // set the header + selectionTableColumn.setText("Chosen Qualifiers"); + + selectionTable.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = selectionTable.getClientArea(); + selectionTableColumn.setWidth(r.width); + } + }); + mSelectionTableViewer = new TableViewer(selectionTable); + mSelectionTableViewer.setContentProvider(new QualifierContentProvider()); + // always show the qualifier value in this case. + mSelectionTableViewer.setLabelProvider(new QualifierLabelProvider( + true /* showQualifierValue */)); + mSelectionTableViewer.setInput(mSelectedConfiguration); + mSelectionTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + // ignore selection changes during resfreshes in some cases. + if (mOnRefresh) { + return; + } + + ISelection selection = event.getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection structSelection = (IStructuredSelection)selection; + + if (structSelection.isEmpty() == false) { + Object first = structSelection.getFirstElement(); + + if (first instanceof ResourceQualifier) { + mRemoveButton.setEnabled(true); + + if (mMode != SelectorMode.CONFIG_ONLY) { + QualifierEditBase composite = mUiMap.get(first.getClass()); + + if (composite != null) { + composite.setQualifier((ResourceQualifier)first); + } + + mStackLayout.topControl = composite; + mQualifierEditParent.layout(); + } + + return; + } + } else { + if (mMode != SelectorMode.CONFIG_ONLY) { + mStackLayout.topControl = null; + mQualifierEditParent.layout(); + } + } + } + + mRemoveButton.setEnabled(false); + } + }); + + if (mMode != SelectorMode.CONFIG_ONLY) { + // 4th column is the detail of the selected qualifier + mQualifierEditParent = new Composite(this, SWT.NONE); + mQualifierEditParent.setLayout(mStackLayout = new StackLayout()); + mQualifierEditParent.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + + // create the UI for all the qualifiers, and associate them to the + // ResourceQualifer class. + mUiMap.put(CountryCodeQualifier.class, new MCCEdit(mQualifierEditParent)); + mUiMap.put(NetworkCodeQualifier.class, new MNCEdit(mQualifierEditParent)); + mUiMap.put(LocaleQualifier.class, new LocaleEdit(mQualifierEditParent)); + mUiMap.put(LayoutDirectionQualifier.class, + new LayoutDirectionEdit(mQualifierEditParent)); + mUiMap.put(SmallestScreenWidthQualifier.class, + new SmallestScreenWidthEdit(mQualifierEditParent)); + mUiMap.put(ScreenWidthQualifier.class, new ScreenWidthEdit(mQualifierEditParent)); + mUiMap.put(ScreenHeightQualifier.class, new ScreenHeightEdit(mQualifierEditParent)); + mUiMap.put(ScreenSizeQualifier.class, new ScreenSizeEdit(mQualifierEditParent)); + mUiMap.put(ScreenRatioQualifier.class, new ScreenRatioEdit(mQualifierEditParent)); + mUiMap.put(ScreenOrientationQualifier.class, new OrientationEdit(mQualifierEditParent)); + mUiMap.put(UiModeQualifier.class, new UiModeEdit(mQualifierEditParent)); + mUiMap.put(NightModeQualifier.class, new NightModeEdit(mQualifierEditParent)); + mUiMap.put(DensityQualifier.class, new DensityEdit(mQualifierEditParent)); + mUiMap.put(TouchScreenQualifier.class, new TouchEdit(mQualifierEditParent)); + mUiMap.put(KeyboardStateQualifier.class, new KeyboardEdit(mQualifierEditParent)); + mUiMap.put(TextInputMethodQualifier.class, new TextInputEdit(mQualifierEditParent)); + mUiMap.put(NavigationStateQualifier.class, + new NavigationStateEdit(mQualifierEditParent)); + mUiMap.put(NavigationMethodQualifier.class, new NavigationEdit(mQualifierEditParent)); + mUiMap.put(ScreenDimensionQualifier.class, + new ScreenDimensionEdit(mQualifierEditParent)); + mUiMap.put(VersionQualifier.class, new VersionEdit(mQualifierEditParent)); + } + } + + /** + * Sets a {@link IQualifierFilter}. If non null, this will restrict the qualifiers that + * can be chosen. + * @param filter the filter to set. + */ + public void setQualifierFilter(IQualifierFilter filter) { + mQualifierFilter = filter; + } + + /** + * Sets a listener to be notified when the configuration changes. + * @param listener A {@link Runnable} whose <code>run()</code> method is called when the + * configuration is changed. The method is called from the UI thread. + */ + public void setOnChangeListener(Runnable listener) { + mOnChangeListener = listener; + } + + /** + * Initialize the UI with a given {@link FolderConfiguration}. This must + * be called from the UI thread. + * @param config The configuration. + */ + public void setConfiguration(FolderConfiguration config) { + + if (mMode != SelectorMode.CONFIG_ONLY) { + mSelectedConfiguration.set(config, true /*nonFakeValuesOnly*/); + + // create the base config, which is the default config minus the qualifiers + // in SelectedConfiguration + mBaseConfiguration.substract(mSelectedConfiguration); + } else { + // set the base config to the edited config. + // reset the config to be empty + mBaseConfiguration.reset(); + mBaseConfiguration.set(config, true /*nonFakeValuesOnly*/); + } + + mSelectionTableViewer.refresh(); + mFullTableViewer.refresh(); + } + + /** + * Initialize the UI with the configuration represented by a resource folder name. + * This must be called from the UI thread. + * + * @param folderSegments the segments of the folder name, + * split using {@link FolderConfiguration#QUALIFIER_SEP}. + * @return true if success, or false if the folder name is not a valid name. + */ + public boolean setConfiguration(String[] folderSegments) { + FolderConfiguration config = FolderConfiguration.getConfig(folderSegments); + + if (config == null) { + return false; + } + + setConfiguration(config); + + return true; + } + + /** + * Initialize the UI with the configuration represented by a resource folder name. + * This must be called from the UI thread. + * @param folderName the name of the folder. + * @return true if success, or false if the folder name is not a valid name. + */ + public boolean setConfiguration(String folderName) { + // split the name of the folder in segments. + String[] folderSegments = folderName.split(SdkConstants.RES_QUALIFIER_SEP); + + return setConfiguration(folderSegments); + } + + /** + * Gets the configuration as setup by the widget. + * @param config the {@link FolderConfiguration} object to be filled with the information + * from the UI. + */ + public void getConfiguration(FolderConfiguration config) { + config.set(mSelectedConfiguration); + } + + /** + * Returns the state of the configuration being edited/created. + */ + public ConfigurationState getState() { + if (mSelectedConfiguration.getInvalidQualifier() != null) { + return ConfigurationState.INVALID_CONFIG; + } + + return ConfigurationState.OK; + } + + /** + * Returns the first invalid qualifier of the configuration being edited/created, + * or <code>null<code> if they are all valid (or if none exists). + * <p/>If {@link #getState()} return {@link ConfigurationState#INVALID_CONFIG} then this will + * not return <code>null</code>. + */ + public ResourceQualifier getInvalidQualifier() { + return mSelectedConfiguration.getInvalidQualifier(); + } + + /** + * Handle changes in the configuration. + * @param keepSelection if <code>true</code> attemps to avoid triggering selection change in + * {@link #mSelectedConfiguration}. + */ + private void onChange(boolean keepSelection) { + ISelection selection = null; + if (keepSelection) { + mOnRefresh = true; + selection = mSelectionTableViewer.getSelection(); + } + + mSelectionTableViewer.refresh(true); + + if (keepSelection) { + mSelectionTableViewer.setSelection(selection); + mOnRefresh = false; + } + + if (mOnChangeListener != null) { + mOnChangeListener.run(); + } + } + + private void fillCombo(Combo combo, ResourceEnum[] resEnums) { + for (ResourceEnum resEnum : resEnums) { + // only add the enum if: + // not in device mode OR (device mode is true and) it's a valid device value. + // Also, always ignore fake values. + if ((mMode == SelectorMode.DEFAULT || resEnum.isValidValueForDevice()) && + resEnum.isFakeValue() == false) { + combo.add(resEnum.getShortDisplayValue()); + } + } + } + + /** + * Content provider around a {@link FolderConfiguration}. + */ + private class QualifierContentProvider implements IStructuredContentProvider { + + private FolderConfiguration mInput; + + public QualifierContentProvider() { + } + + @Override + public void dispose() { + // pass + } + + @Override + public Object[] getElements(Object inputElement) { + // default easy case + if (mQualifierFilter == null) { + return mInput.getQualifiers(); + } + + // in this case we have to compute the list + ArrayList<ResourceQualifier> list = new ArrayList<ResourceQualifier>(); + for (ResourceQualifier qual : mInput.getQualifiers()) { + if (mQualifierFilter.accept(qual)) { + list.add(qual); + } + } + + return list.toArray(); + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + mInput = null; + if (newInput instanceof FolderConfiguration) { + mInput = (FolderConfiguration)newInput; + } + } + } + + /** + * Label provider for {@link ResourceQualifier} objects. + */ + private static class QualifierLabelProvider implements ITableLabelProvider { + + private final boolean mShowQualifierValue; + + public QualifierLabelProvider(boolean showQualifierValue) { + mShowQualifierValue = showQualifierValue; + } + + @Override + public String getColumnText(Object element, int columnIndex) { + // only one column, so we can ignore columnIndex + if (element instanceof ResourceQualifier) { + if (mShowQualifierValue) { + String value = ((ResourceQualifier)element).getShortDisplayValue(); + if (value == null || value.length() == 0) { + return String.format("%1$s (?)", + ((ResourceQualifier)element).getShortName()); + } else { + return value; + } + + } else { + return ((ResourceQualifier)element).getShortName(); + } + } + + return null; + } + + @Override + public Image getColumnImage(Object element, int columnIndex) { + // only one column, so we can ignore columnIndex + if (element instanceof ResourceQualifier) { + return ResourceHelper.getIcon(((ResourceQualifier)element).getClass()); + } + + return null; + } + + @Override + public void addListener(ILabelProviderListener listener) { + // pass + } + + @Override + public void dispose() { + // pass + } + + @Override + public boolean isLabelProperty(Object element, String property) { + // pass + return false; + } + + @Override + public void removeListener(ILabelProviderListener listener) { + // pass + } + } + + /** + * Base class for Edit widget for {@link ResourceQualifier}. + */ + private abstract static class QualifierEditBase extends Composite { + + public QualifierEditBase(Composite parent, String title) { + super(parent, SWT.NONE); + setLayout(new GridLayout(1, false)); + + new Label(this, SWT.NONE).setText(title); + } + + public abstract void setQualifier(ResourceQualifier qualifier); + } + + /** + * Edit widget for {@link CountryCodeQualifier}. + */ + private class MCCEdit extends QualifierEditBase { + + private final Text mText; + + public MCCEdit(Composite parent) { + super(parent, CountryCodeQualifier.NAME); + + mText = new Text(this, SWT.BORDER); + mText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mText.addVerifyListener(new MobileCodeVerifier()); + mText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onTextChange(); + } + }); + + mText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onTextChange(); + } + }); + + new Label(this, SWT.NONE).setText("(3 digit code)"); + } + + private void onTextChange() { + String value = mText.getText(); + + if (value.length() == 0) { + // empty string, means a qualifier with no value. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); + } else { + try { + CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier( + CountryCodeQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mSelectedConfiguration.setCountryCodeQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong + // (for instance not exactly 3 digits). + mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); + } + } catch (NumberFormatException nfe) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + CountryCodeQualifier q = (CountryCodeQualifier)qualifier; + + mText.setText(Integer.toString(q.getCode())); + } + } + + /** + * Edit widget for {@link NetworkCodeQualifier}. + */ + private class MNCEdit extends QualifierEditBase { + private final Text mText; + + public MNCEdit(Composite parent) { + super(parent, NetworkCodeQualifier.NAME); + + mText = new Text(this, SWT.BORDER); + mText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mText.addVerifyListener(new MobileCodeVerifier()); + mText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onTextChange(); + } + }); + mText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onTextChange(); + } + }); + + new Label(this, SWT.NONE).setText("(1-3 digit code)"); + } + + private void onTextChange() { + String value = mText.getText(); + + if (value.length() == 0) { + // empty string, means a qualifier with no value. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); + } else { + try { + NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier( + NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mSelectedConfiguration.setNetworkCodeQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong + // (for instance not exactly 3 digits). + mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); + } + } catch (NumberFormatException nfe) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + NetworkCodeQualifier q = (NetworkCodeQualifier)qualifier; + + mText.setText(Integer.toString(q.getCode())); + } + } + + /** + * Edit widget for {@link LanguageQualifier}. + */ + private class LocaleEdit extends QualifierEditBase { + private final Combo mLanguage; + private final Label mName; + + public LocaleEdit(Composite parent) { + super(parent, LocaleQualifier.NAME); + + mLanguage = new Combo(this, SWT.DROP_DOWN); + List<String> codes = LocaleManager.getLanguageCodes(); + String[] items = codes.toArray(new String[codes.size()]); + Arrays.sort(items); + mLanguage.setItems(items); + + mLanguage.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mLanguage.addVerifyListener(new LanguageRegionVerifier()); + mLanguage.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onLanguageChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onLanguageChange(); + } + }); + mLanguage.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onLanguageChange(); + } + }); + + new Label(this, SWT.NONE).setText("(2 letter code or language-rRegion)"); + + mName = new Label(this, SWT.NONE); + mName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + } + + private void onLanguageChange() { + // update the current config + String value = mLanguage.getText(); + + String newName = ""; + if (value.length() == 2) { + String name = LocaleManager.getLanguageName(value.toLowerCase(Locale.US)); + if (name != null) { + newName = name; + } + } + mName.setText(newName); + + if (value.length() == 0) { + // empty string, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setLocaleQualifier(new LocaleQualifier()); + } else { + LocaleQualifier qualifier = LocaleQualifier.getQualifier(value); + if (qualifier != null) { + mSelectedConfiguration.setLocaleQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + mSelectedConfiguration.setLocaleQualifier(new LocaleQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + LocaleQualifier q = (LocaleQualifier)qualifier; + + String value = q.getValue(); + if (value != null) { + mLanguage.setText(value); + } + } + } + + /** + * Edit widget for {@link LayoutDirectionQualifier}. + */ + private class LayoutDirectionEdit extends QualifierEditBase { + + private final Combo mDirection; + + public LayoutDirectionEdit(Composite parent) { + super(parent, LayoutDirectionQualifier.NAME); + + mDirection = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mDirection, LayoutDirection.values()); + + mDirection.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mDirection.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onDirectionChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onDirectionChange(); + } + }); + } + + protected void onDirectionChange() { + // update the current config + int index = mDirection.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setLayoutDirectionQualifier(new LayoutDirectionQualifier( + LayoutDirection.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setLayoutDirectionQualifier( + new LayoutDirectionQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + LayoutDirectionQualifier q = (LayoutDirectionQualifier)qualifier; + + LayoutDirection value = q.getValue(); + if (value == null) { + mDirection.clearSelection(); + } else { + mDirection.select(LayoutDirection.getIndex(value)); + } + } + } + + + /** + * Edit widget for {@link SmallestScreenWidthQualifier}. + */ + private class SmallestScreenWidthEdit extends QualifierEditBase { + + private final Text mSize; + + public SmallestScreenWidthEdit(Composite parent) { + super(parent, SmallestScreenWidthQualifier.NAME); + + ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onSizeChange(); + } + }; + + FocusAdapter focusListener = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onSizeChange(); + } + }; + + mSize = new Text(this, SWT.BORDER); + mSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mSize.addVerifyListener(new DimensionVerifier()); + mSize.addModifyListener(modifyListener); + mSize.addFocusListener(focusListener); + } + + private void onSizeChange() { + // update the current config + String size = mSize.getText(); + + if (size.length() == 0) { + // if one of the strings is empty, reset to no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setSmallestScreenWidthQualifier( + new SmallestScreenWidthQualifier()); + } else { + SmallestScreenWidthQualifier qualifier = SmallestScreenWidthQualifier.getQualifier( + size); + + if (qualifier != null) { + mSelectedConfiguration.setSmallestScreenWidthQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong, reset the qualifier + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setSmallestScreenWidthQualifier( + new SmallestScreenWidthQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + SmallestScreenWidthQualifier q = (SmallestScreenWidthQualifier)qualifier; + + mSize.setText(Integer.toString(q.getValue())); + } + } + + /** + * Edit widget for {@link ScreenWidthQualifier}. + */ + private class ScreenWidthEdit extends QualifierEditBase { + + private final Text mSize; + + public ScreenWidthEdit(Composite parent) { + super(parent, ScreenWidthQualifier.NAME); + + ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onSizeChange(); + } + }; + + FocusAdapter focusListener = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onSizeChange(); + } + }; + + mSize = new Text(this, SWT.BORDER); + mSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mSize.addVerifyListener(new DimensionVerifier()); + mSize.addModifyListener(modifyListener); + mSize.addFocusListener(focusListener); + } + + private void onSizeChange() { + // update the current config + String size = mSize.getText(); + + if (size.length() == 0) { + // if one of the strings is empty, reset to no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenWidthQualifier(new ScreenWidthQualifier()); + } else { + ScreenWidthQualifier qualifier = ScreenWidthQualifier.getQualifier(size); + + if (qualifier != null) { + mSelectedConfiguration.setScreenWidthQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong, reset the qualifier + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenWidthQualifier( + new ScreenWidthQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenWidthQualifier q = (ScreenWidthQualifier)qualifier; + + mSize.setText(Integer.toString(q.getValue())); + } + } + + /** + * Edit widget for {@link ScreenHeightQualifier}. + */ + private class ScreenHeightEdit extends QualifierEditBase { + + private final Text mSize; + + public ScreenHeightEdit(Composite parent) { + super(parent, ScreenHeightQualifier.NAME); + + ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onSizeChange(); + } + }; + + FocusAdapter focusListener = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onSizeChange(); + } + }; + + mSize = new Text(this, SWT.BORDER); + mSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mSize.addVerifyListener(new DimensionVerifier()); + mSize.addModifyListener(modifyListener); + mSize.addFocusListener(focusListener); + } + + private void onSizeChange() { + // update the current config + String size = mSize.getText(); + + if (size.length() == 0) { + // if one of the strings is empty, reset to no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenHeightQualifier(new ScreenHeightQualifier()); + } else { + ScreenHeightQualifier qualifier = ScreenHeightQualifier.getQualifier(size); + + if (qualifier != null) { + mSelectedConfiguration.setScreenHeightQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong, reset the qualifier + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenHeightQualifier( + new ScreenHeightQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenHeightQualifier q = (ScreenHeightQualifier)qualifier; + + mSize.setText(Integer.toString(q.getValue())); + } + } + + + /** + * Edit widget for {@link ScreenSizeQualifier}. + */ + private class ScreenSizeEdit extends QualifierEditBase { + + private final Combo mSize; + + public ScreenSizeEdit(Composite parent) { + super(parent, ScreenSizeQualifier.NAME); + + mSize = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mSize, ScreenSize.values()); + + mSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mSize.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onScreenSizeChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onScreenSizeChange(); + } + }); + } + + protected void onScreenSizeChange() { + // update the current config + int index = mSize.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setScreenSizeQualifier(new ScreenSizeQualifier( + ScreenSize.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenSizeQualifier( + new ScreenSizeQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenSizeQualifier q = (ScreenSizeQualifier)qualifier; + + ScreenSize value = q.getValue(); + if (value == null) { + mSize.clearSelection(); + } else { + mSize.select(ScreenSize.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link ScreenRatioQualifier}. + */ + private class ScreenRatioEdit extends QualifierEditBase { + + private final Combo mRatio; + + public ScreenRatioEdit(Composite parent) { + super(parent, ScreenRatioQualifier.NAME); + + mRatio = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mRatio, ScreenRatio.values()); + + mRatio.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mRatio.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onScreenRatioChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onScreenRatioChange(); + } + }); + } + + protected void onScreenRatioChange() { + // update the current config + int index = mRatio.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setScreenRatioQualifier(new ScreenRatioQualifier( + ScreenRatio.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenRatioQualifier( + new ScreenRatioQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenRatioQualifier q = (ScreenRatioQualifier)qualifier; + + ScreenRatio value = q.getValue(); + if (value == null) { + mRatio.clearSelection(); + } else { + mRatio.select(ScreenRatio.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link ScreenOrientationQualifier}. + */ + private class OrientationEdit extends QualifierEditBase { + + private final Combo mOrientation; + + public OrientationEdit(Composite parent) { + super(parent, ScreenOrientationQualifier.NAME); + + mOrientation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mOrientation, ScreenOrientation.values()); + + mOrientation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mOrientation.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onOrientationChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onOrientationChange(); + } + }); + } + + protected void onOrientationChange() { + // update the current config + int index = mOrientation.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setScreenOrientationQualifier(new ScreenOrientationQualifier( + ScreenOrientation.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenOrientationQualifier( + new ScreenOrientationQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenOrientationQualifier q = (ScreenOrientationQualifier)qualifier; + + ScreenOrientation value = q.getValue(); + if (value == null) { + mOrientation.clearSelection(); + } else { + mOrientation.select(ScreenOrientation.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link DockModeQualifier}. + */ + private class UiModeEdit extends QualifierEditBase { + + private final Combo mUiMode; + + public UiModeEdit(Composite parent) { + super(parent, UiModeQualifier.NAME); + + mUiMode = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mUiMode, UiMode.values()); + + mUiMode.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mUiMode.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onDockModeChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onDockModeChange(); + } + }); + } + + protected void onDockModeChange() { + // update the current config + int index = mUiMode.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setUiModeQualifier( + new UiModeQualifier(UiMode.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setUiModeQualifier(new UiModeQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + UiModeQualifier q = (UiModeQualifier)qualifier; + + UiMode value = q.getValue(); + if (value == null) { + mUiMode.clearSelection(); + } else { + mUiMode.select(UiMode.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link NightModeQualifier}. + */ + private class NightModeEdit extends QualifierEditBase { + + private final Combo mNightMode; + + public NightModeEdit(Composite parent) { + super(parent, NightModeQualifier.NAME); + + mNightMode = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mNightMode, NightMode.values()); + + mNightMode.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNightMode.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onNightModeChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onNightModeChange(); + } + }); + } + + protected void onNightModeChange() { + // update the current config + int index = mNightMode.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setNightModeQualifier( + new NightModeQualifier(NightMode.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setNightModeQualifier(new NightModeQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + NightModeQualifier q = (NightModeQualifier)qualifier; + + NightMode value = q.getValue(); + if (value == null) { + mNightMode.clearSelection(); + } else { + mNightMode.select(NightMode.getIndex(value)); + } + } + } + + + /** + * Edit widget for {@link DensityQualifier}. + */ + private class DensityEdit extends QualifierEditBase { + private final Combo mDensity; + + public DensityEdit(Composite parent) { + super(parent, DensityQualifier.NAME); + + mDensity = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mDensity, Density.values()); + + mDensity.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mDensity.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onDensityChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onDensityChange(); + } + }); + } + + private void onDensityChange() { + // update the current config + int index = mDensity.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setDensityQualifier(new DensityQualifier( + Density.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setDensityQualifier( + new DensityQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + DensityQualifier q = (DensityQualifier)qualifier; + + Density value = q.getValue(); + if (value == null) { + mDensity.clearSelection(); + } else { + mDensity.select(Density.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link TouchScreenQualifier}. + */ + private class TouchEdit extends QualifierEditBase { + + private final Combo mTouchScreen; + + public TouchEdit(Composite parent) { + super(parent, TouchScreenQualifier.NAME); + + mTouchScreen = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mTouchScreen, TouchScreen.values()); + + mTouchScreen.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mTouchScreen.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onTouchChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onTouchChange(); + } + }); + } + + protected void onTouchChange() { + // update the current config + int index = mTouchScreen.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setTouchTypeQualifier(new TouchScreenQualifier( + TouchScreen.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setTouchTypeQualifier(new TouchScreenQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + TouchScreenQualifier q = (TouchScreenQualifier)qualifier; + + TouchScreen value = q.getValue(); + if (value == null) { + mTouchScreen.clearSelection(); + } else { + mTouchScreen.select(TouchScreen.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link KeyboardStateQualifier}. + */ + private class KeyboardEdit extends QualifierEditBase { + + private final Combo mKeyboardState; + + public KeyboardEdit(Composite parent) { + super(parent, KeyboardStateQualifier.NAME); + + mKeyboardState = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mKeyboardState, KeyboardState.values()); + + mKeyboardState.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mKeyboardState.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onKeyboardChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onKeyboardChange(); + } + }); + } + + protected void onKeyboardChange() { + // update the current config + int index = mKeyboardState.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setKeyboardStateQualifier(new KeyboardStateQualifier( + KeyboardState.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setKeyboardStateQualifier( + new KeyboardStateQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + KeyboardStateQualifier q = (KeyboardStateQualifier)qualifier; + + KeyboardState value = q.getValue(); + if (value == null) { + mKeyboardState.clearSelection(); + } else { + mKeyboardState.select(KeyboardState.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link TextInputMethodQualifier}. + */ + private class TextInputEdit extends QualifierEditBase { + + private final Combo mTextInput; + + public TextInputEdit(Composite parent) { + super(parent, TextInputMethodQualifier.NAME); + + mTextInput = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mTextInput, Keyboard.values()); + + mTextInput.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mTextInput.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onTextInputChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onTextInputChange(); + } + }); + } + + protected void onTextInputChange() { + // update the current config + int index = mTextInput.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setTextInputMethodQualifier(new TextInputMethodQualifier( + Keyboard.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setTextInputMethodQualifier( + new TextInputMethodQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + TextInputMethodQualifier q = (TextInputMethodQualifier)qualifier; + + Keyboard value = q.getValue(); + if (value == null) { + mTextInput.clearSelection(); + } else { + mTextInput.select(Keyboard.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link NavigationStateQualifier}. + */ + private class NavigationStateEdit extends QualifierEditBase { + + private final Combo mNavigationState; + + public NavigationStateEdit(Composite parent) { + super(parent, NavigationStateQualifier.NAME); + + mNavigationState = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mNavigationState, NavigationState.values()); + + mNavigationState.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNavigationState.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onNavigationChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onNavigationChange(); + } + }); + } + + protected void onNavigationChange() { + // update the current config + int index = mNavigationState.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setNavigationStateQualifier( + new NavigationStateQualifier(NavigationState.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setNavigationStateQualifier(new NavigationStateQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + NavigationStateQualifier q = (NavigationStateQualifier)qualifier; + + NavigationState value = q.getValue(); + if (value == null) { + mNavigationState.clearSelection(); + } else { + mNavigationState.select(NavigationState.getIndex(value)); + } + } + } + + + /** + * Edit widget for {@link NavigationMethodQualifier}. + */ + private class NavigationEdit extends QualifierEditBase { + + private final Combo mNavigation; + + public NavigationEdit(Composite parent) { + super(parent, NavigationMethodQualifier.NAME); + + mNavigation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + fillCombo(mNavigation, Navigation.values()); + + mNavigation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNavigation.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onNavigationChange(); + } + @Override + public void widgetSelected(SelectionEvent e) { + onNavigationChange(); + } + }); + } + + protected void onNavigationChange() { + // update the current config + int index = mNavigation.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setNavigationMethodQualifier(new NavigationMethodQualifier( + Navigation.getByIndex(index))); + } else { + // empty selection, means no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setNavigationMethodQualifier( + new NavigationMethodQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + NavigationMethodQualifier q = (NavigationMethodQualifier)qualifier; + + Navigation value = q.getValue(); + if (value == null) { + mNavigation.clearSelection(); + } else { + mNavigation.select(Navigation.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link ScreenDimensionQualifier}. + */ + private class ScreenDimensionEdit extends QualifierEditBase { + + private final Text mSize1; + private final Text mSize2; + + public ScreenDimensionEdit(Composite parent) { + super(parent, ScreenDimensionQualifier.NAME); + + ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onSizeChange(); + } + }; + + FocusAdapter focusListener = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onSizeChange(); + } + }; + + mSize1 = new Text(this, SWT.BORDER); + mSize1.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mSize1.addVerifyListener(new DimensionVerifier()); + mSize1.addModifyListener(modifyListener); + mSize1.addFocusListener(focusListener); + + mSize2 = new Text(this, SWT.BORDER); + mSize2.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mSize2.addVerifyListener(new DimensionVerifier()); + mSize2.addModifyListener(modifyListener); + mSize2.addFocusListener(focusListener); + } + + private void onSizeChange() { + // update the current config + String size1 = mSize1.getText(); + String size2 = mSize2.getText(); + + if (size1.length() == 0 || size2.length() == 0) { + // if one of the strings is empty, reset to no qualifier. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenDimensionQualifier(new ScreenDimensionQualifier()); + } else { + ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1, + size2); + + if (qualifier != null) { + mSelectedConfiguration.setScreenDimensionQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong, reset the qualifier + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setScreenDimensionQualifier( + new ScreenDimensionQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier; + + mSize1.setText(Integer.toString(q.getValue1())); + mSize2.setText(Integer.toString(q.getValue2())); + } + } + + /** + * Edit widget for {@link VersionQualifier}. + */ + private class VersionEdit extends QualifierEditBase { + private final Text mText; + + public VersionEdit(Composite parent) { + super(parent, VersionQualifier.NAME); + + mText = new Text(this, SWT.BORDER); + mText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mText.addVerifyListener(new MobileCodeVerifier()); + mText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + onVersionChange(); + } + }); + mText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onVersionChange(); + } + }); + + new Label(this, SWT.NONE).setText("(Platform API level)"); + } + + private void onVersionChange() { + String value = mText.getText(); + + if (value.length() == 0) { + // empty string, means a qualifier with no value. + // Since the qualifier classes are immutable, and we don't want to + // remove the qualifier from the configuration, we create a new default one. + mSelectedConfiguration.setVersionQualifier(new VersionQualifier()); + } else { + try { + VersionQualifier qualifier = VersionQualifier.getQualifier( + VersionQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mSelectedConfiguration.setVersionQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong + mSelectedConfiguration.setVersionQualifier(new VersionQualifier()); + } + } catch (NumberFormatException nfe) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mSelectedConfiguration.setVersionQualifier(new VersionQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + VersionQualifier q = (VersionQualifier)qualifier; + + mText.setText(Integer.toString(q.getVersion())); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/EclipseUiHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/EclipseUiHelper.java new file mode 100644 index 000000000..f0fd23135 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/EclipseUiHelper.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; + +/** + * Helpers for Eclipse UI related stuff. + */ +public final class EclipseUiHelper { + + /** View Id for the default Eclipse Content Outline view. */ + public static final String CONTENT_OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline"; + /** View Id for the default Eclipse Property Sheet view. */ + public static final String PROPERTY_SHEET_VIEW_ID = "org.eclipse.ui.views.PropertySheet"; + + /** This class never gets instantiated. */ + private EclipseUiHelper() { + } + + /** + * Shows the corresponding view. + * <p/> + * Silently fails in case of error. + * + * @param viewId One of {@link #CONTENT_OUTLINE_VIEW_ID}, {@link #PROPERTY_SHEET_VIEW_ID}. + * @param activate True to force activate (i.e. takes focus), false to just make visible (i.e. + * does not steal focus.) + */ + public static void showView(String viewId, boolean activate) { + IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (win != null) { + IWorkbenchPage page = win.getActivePage(); + if (page != null) { + try { + IViewPart part = page.showView(viewId, + null /* secondaryId */, + activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE); + } catch (PartInitException e) { + // ignore + } + } + } + + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/IUpdateWizardDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/IUpdateWizardDialog.java new file mode 100755 index 000000000..c49b58953 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/IUpdateWizardDialog.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import org.eclipse.jface.wizard.WizardDialog; + + +/** + * An interface that enables a client to update {@link WizardDialog} after its creation. + */ +public interface IUpdateWizardDialog { + /** + * Invoked after {@link WizardDialog#create()} to let the caller update the dialog. + */ + public void updateWizardDialog(WizardDialogEx dialog); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java new file mode 100644 index 000000000..c2a5f71b2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.ui; + +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.resources.ResourceType; + +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.SelectionStatusDialog; + +/** + * Dialog for choosing margins + */ +public class MarginChooser extends SelectionStatusDialog implements Listener { + private GraphicalEditorPart mEditor; + private AndroidTargetData mTargetData; + private Text mLeftField; + private Text mRightField; + private Text mTopField; + private Text mBottomField; + private Text mAllField; + private String mInitialAll; + private String mInitialLeft; + private String mInitialRight; + private String mInitialTop; + private String mInitialBottom; + private Label mErrorLabel; + private String[] mMargins; + + // Client data key for resource buttons pointing to the associated text field + private final static String PROP_TEXTFIELD = "textField"; //$NON-NLS-1$ + + /** + * Constructs a new margin chooser dialog. + * + * @param parent parent widget + * @param editor associated layout editor + * @param targetData current SDK target + * @param all current value for the all margins attribute + * @param left current value for the left margin + * @param right current value for the right margin + * @param top current value for the top margin + * @param bottom current value for the bottom margin + */ + public MarginChooser(Shell parent, GraphicalEditorPart editor, AndroidTargetData targetData, String all, + String left, String right, String top, String bottom) { + super(parent); + setTitle("Edit Margins"); + mEditor = editor; + mTargetData = targetData; + mInitialAll = all; + mInitialLeft = left; + mInitialRight = right; + mInitialTop = top; + mInitialBottom = bottom; + } + + @SuppressWarnings("unused") // SWT constructors have side effects, "new Label" is not unused. + @Override + protected Control createDialogArea(Composite parent) { + Composite container = new Composite(parent, SWT.NONE); + container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + + container.setLayout(new GridLayout(3, false)); + + Label allLabel = new Label(container, SWT.NONE); + allLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + allLabel.setText("All:"); + + mAllField = new Text(container, SWT.BORDER | SWT.LEFT); + mAllField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mAllField.setText(mInitialAll != null ? mInitialAll : ""); //$NON-NLS-1$ + + Button allButton = new Button(container, SWT.NONE); + allButton.setText("Resource..."); + allButton.setData(PROP_TEXTFIELD, mAllField); + + Label label = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL); + label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1)); + + Label leftLabel = new Label(container, SWT.NONE); + leftLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + leftLabel.setText("Left:"); + + mLeftField = new Text(container, SWT.BORDER | SWT.LEFT); + mLeftField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mLeftField.setText(mInitialLeft != null ? mInitialLeft : ""); //$NON-NLS-1$ + + Button leftButton = new Button(container, SWT.NONE); + leftButton.setText("Resource..."); + leftButton.setData(PROP_TEXTFIELD, mLeftField); + + Label rightLabel = new Label(container, SWT.NONE); + rightLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + rightLabel.setText("Right:"); + + mRightField = new Text(container, SWT.BORDER | SWT.LEFT); + mRightField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mRightField.setText(mInitialRight != null ? mInitialRight : ""); //$NON-NLS-1$ + + Button rightButton = new Button(container, SWT.NONE); + rightButton.setText("Resource..."); + rightButton.setData(PROP_TEXTFIELD, mRightField); + + Label topLabel = new Label(container, SWT.NONE); + topLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + topLabel.setText("Top:"); + + mTopField = new Text(container, SWT.BORDER | SWT.LEFT); + mTopField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mTopField.setText(mInitialTop != null ? mInitialTop : ""); //$NON-NLS-1$ + + Button topButton = new Button(container, SWT.NONE); + topButton.setText("Resource..."); + topButton.setData(PROP_TEXTFIELD, mTopField); + + Label bottomLabel = new Label(container, SWT.NONE); + bottomLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + bottomLabel.setText("Bottom:"); + + mBottomField = new Text(container, SWT.BORDER | SWT.LEFT); + mBottomField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mBottomField.setText(mInitialBottom != null ? mInitialBottom : ""); //$NON-NLS-1$ + + Button bottomButton = new Button(container, SWT.NONE); + bottomButton.setText("Resource..."); + bottomButton.setData(PROP_TEXTFIELD, mBottomField); + + allButton.addListener(SWT.Selection, this); + leftButton.addListener(SWT.Selection, this); + rightButton.addListener(SWT.Selection, this); + topButton.addListener(SWT.Selection, this); + bottomButton.addListener(SWT.Selection, this); + + mAllField.addListener(SWT.Modify, this); + mLeftField.addListener(SWT.Modify, this); + mRightField.addListener(SWT.Modify, this); + mTopField.addListener(SWT.Modify, this); + mBottomField.addListener(SWT.Modify, this); + + new Label(container, SWT.NONE); + mErrorLabel = new Label(container, SWT.WRAP); + mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + mErrorLabel.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED)); + return container; + } + + @Override + protected void computeResult() { + mMargins = new String[] { + mAllField.getText().trim(), + mLeftField.getText().trim(), mRightField.getText().trim(), + mTopField.getText().trim(), mBottomField.getText().trim() + }; + } + + /** + * Returns the margins in the order all, left, right, top, bottom + * + * @return the margins in the order all, left, right, top, bottom, never + * null + */ + public String[] getMargins() { + return mMargins; + } + + @Override + public void handleEvent(Event event) { + if (event.type == SWT.Modify) { + // Text field modification -- warn about non-dip numbers + if (event.widget instanceof Text) { + Text text = (Text) event.widget; + String input = text.getText().trim(); + boolean isNumber = false; + try { + if (Integer.parseInt(input) > 0) { + isNumber = true; + } + } catch (NumberFormatException nufe) { + // Users are allowed to enter non-numbers here, not an error + } + if (isNumber) { + String message = String.format("Hint: Use \"%1$sdp\" instead", input); + mErrorLabel.setText(message); + } else { + mErrorLabel.setText(""); + } + } + } else if (event.type == SWT.Selection) { + // Button pressed - open resource chooser + if (event.widget instanceof Button) { + Button button = (Button) event.widget; + Text text = (Text) button.getData(PROP_TEXTFIELD); + + // Open a resource chooser dialog for specified resource type. + ResourceChooser chooser = ResourceChooser.create(mEditor, ResourceType.DIMEN) + .setCurrentResource(text.getText().trim()); + if (chooser.open() == Window.OK) { + text.setText(chooser.getCurrentResource()); + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java new file mode 100644 index 000000000..6c628658a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory; +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; +import com.android.resources.ResourceType; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.FilteredTree; +import org.eclipse.ui.dialogs.PatternFilter; +import org.eclipse.ui.dialogs.SelectionStatusDialog; + +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A dialog to let the user choose a reference to a resource. + * + */ +public class ReferenceChooserDialog extends SelectionStatusDialog { + + private static Pattern sResourcePattern = Pattern.compile("@(.*)/(.+)"); //$NON-NLS-1$ + private static Pattern sInlineIdResourcePattern = Pattern.compile("@\\+id/(.+)"); //$NON-NLS-1$ + + private static IDialogSettings sDialogSettings = new DialogSettings(""); + + private ResourceRepository mProjectResources; + private String mCurrentResource; + private FilteredTree mFilteredTree; + private Button mNewResButton; + private final IProject mProject; + private TreeViewer mTreeViewer; + private ResourcePreviewHelper mPreviewHelper; + + /** + * @param project + * @param parent + */ + public ReferenceChooserDialog(IProject project, ResourceRepository projectResources, + Shell parent) { + super(parent); + mProject = project; + mProjectResources = projectResources; + + int shellStyle = getShellStyle(); + setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE); + + setTitle("Reference Chooser"); + setMessage(String.format("Choose a resource")); + + setDialogBoundsSettings(sDialogSettings, getDialogBoundsStrategy()); + } + + public void setPreviewHelper(ResourcePreviewHelper previewHelper) { + mPreviewHelper = previewHelper; + } + + public void setCurrentResource(String resource) { + mCurrentResource = resource; + } + + public String getCurrentResource() { + return mCurrentResource; + } + + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult() + */ + @Override + protected void computeResult() { + // get the selection + TreePath treeSelection = getSelection(); + if (treeSelection != null) { + if (treeSelection.getSegmentCount() == 2) { + // get the resource type and the resource item + ResourceType resourceType = (ResourceType)treeSelection.getFirstSegment(); + ResourceItem resourceItem = (ResourceItem)treeSelection.getLastSegment(); + + mCurrentResource = resourceItem.getXmlString(resourceType, false /* system */); + } + } + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite top = (Composite)super.createDialogArea(parent); + + // create the standard message area + createMessageArea(top); + + // create the filtered tree + createFilteredTree(top); + + // setup the initial selection + if (mCurrentResource != null) { + setupInitialSelection(); + } + + // create the "New Resource" button + createNewResButtons(top); + + Composite workaround = PropertyFactory.addWorkaround(top); + if (workaround != null) { + workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); + } + + return top; + } + + /** + * Creates the "New Resource" button. + * @param top the parent composite + */ + private void createNewResButtons(Composite top) { + mNewResButton = new Button(top, SWT.NONE); + mNewResButton.addSelectionListener(new OnNewResButtonSelected()); + updateNewResButton(); + } + + private void createFilteredTree(Composite parent) { + mFilteredTree = new FilteredTree(parent, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION, + new PatternFilter()); + + GridData data = new GridData(); + data.widthHint = convertWidthInCharsToPixels(60); + data.heightHint = convertHeightInCharsToPixels(18); + data.grabExcessVerticalSpace = true; + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + data.verticalAlignment = GridData.FILL; + mFilteredTree.setLayoutData(data); + mFilteredTree.setFont(parent.getFont()); + + mTreeViewer = mFilteredTree.getViewer(); + Tree tree = mTreeViewer.getTree(); + + tree.addSelectionListener(new SelectionListener() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + handleDoubleClick(); + } + + @Override + public void widgetSelected(SelectionEvent e) { + handleSelection(); + } + }); + + mTreeViewer.setLabelProvider(new ResourceLabelProvider()); + mTreeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */)); + mTreeViewer.setInput(mProjectResources); + } + + protected void handleSelection() { + validateCurrentSelection(); + updateNewResButton(); + + if (mPreviewHelper != null) { + TreePath treeSelection = getSelection(); + ResourceType type = null; + if (treeSelection != null && treeSelection.getSegmentCount() == 2) { + Object segment = treeSelection.getSegment(0); + if (segment instanceof ResourceType) { + type = (ResourceType) segment; + // Ensure that mCurrentResource is valid + computeResult(); + } + } + + mPreviewHelper.updatePreview(type, mCurrentResource); + } + } + + protected void handleDoubleClick() { + if (validateCurrentSelection()) { + buttonPressed(IDialogConstants.OK_ID); + } + } + + /** + * Returns the selected item in the tree as a {@link TreePath} object. + * @return the <code>TreePath</code> object or <code>null</code> if there was no selection. + */ + private TreePath getSelection() { + ISelection selection = mFilteredTree.getViewer().getSelection(); + if (selection instanceof TreeSelection) { + TreeSelection treeSelection = (TreeSelection)selection; + TreePath[] treePaths = treeSelection.getPaths(); + + // the selection mode is SWT.SINGLE, so we just get the first one. + if (treePaths.length > 0) { + return treePaths[0]; + } + } + + return null; + } + + private boolean validateCurrentSelection() { + TreePath treeSelection = getSelection(); + + IStatus status; + if (treeSelection != null) { + if (treeSelection.getSegmentCount() == 2) { + status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, + IStatus.OK, "", //$NON-NLS-1$ + null); + } else { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + IStatus.ERROR, "You must select a Resource Item", + null); + } + } else { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + IStatus.ERROR, "", //$NON-NLS-1$ + null); + } + + updateStatus(status); + + return status.isOK(); + } + + /** + * Updates the new res button when the list selection changes. + * The name of the button changes depending on the resource. + */ + private void updateNewResButton() { + ResourceType type = getSelectedResourceType(); + + // We only support adding new strings right now + mNewResButton.setEnabled(type == ResourceType.STRING); + + String title = String.format("New %1$s...", + type == null ? "Resource" : type.getDisplayName()); + mNewResButton.setText(title); + mNewResButton.pack(); + } + + /** + * Callback invoked when the mNewResButton is selected by the user. + */ + private class OnNewResButtonSelected extends SelectionAdapter { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + + ResourceType type = getSelectedResourceType(); + + // We currently only support strings + if (type == ResourceType.STRING) { + + ExtractStringRefactoring ref = new ExtractStringRefactoring( + mProject, true /*enforceNew*/); + RefactoringWizard wizard = new ExtractStringWizard(ref, mProject); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + IWorkbench w = PlatformUI.getWorkbench(); + if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) == + IDialogConstants.OK_ID) { + mTreeViewer.refresh(); + + // select it if possible + setupInitialSelection(type, ref.getXmlStringId()); + } + } catch (InterruptedException ex) { + // Interrupted. Pass. + } + } + } + } + + /** + * Returns the {@link ResourceType} of the selected element, if any. + * Returns null if nothing suitable is selected. + */ + private ResourceType getSelectedResourceType() { + ResourceType type = null; + + TreePath selection = getSelection(); + if (selection != null && selection.getSegmentCount() > 0) { + Object first = selection.getFirstSegment(); + if (first instanceof ResourceType) { + type = (ResourceType) first; + } + } + return type; + } + + /** + * Sets up the initial selection. + * <p/> + * This parses {@link #mCurrentResource} to find out the resource type and the resource name. + */ + private void setupInitialSelection() { + // checks the inline id pattern first as it's more restrictive than the other one. + Matcher m = sInlineIdResourcePattern.matcher(mCurrentResource); + if (m.matches()) { + // get the matching name + String resourceName = m.group(1); + + // setup initial selection + setupInitialSelection(ResourceType.ID, resourceName); + } else { + // attempts the inline id pattern + m = sResourcePattern.matcher(mCurrentResource); + if (m.matches()) { + // get the resource type. + ResourceType resourceType = ResourceType.getEnum(m.group(1)); + if (resourceType != null) { + // get the matching name + String resourceName = m.group(2); + + // setup initial selection + setupInitialSelection(resourceType, resourceName); + } + } + } + } + + /** + * Sets up the initial selection based on a {@link ResourceType} and a resource name. + * @param resourceType the resource type. + * @param resourceName the resource name. + */ + private void setupInitialSelection(ResourceType resourceType, String resourceName) { + // get all the resources of this type + Collection<ResourceItem> resourceItems = + mProjectResources.getResourceItemsOfType(resourceType); + + for (ResourceItem resourceItem : resourceItems) { + if (resourceName.equals(resourceItem.getName())) { + // name of the resource match, we select it, + TreePath treePath = new TreePath(new Object[] { resourceType, resourceItem }); + mFilteredTree.getViewer().setSelection( + new TreeSelection(treePath), + true /*reveal*/); + + // and we're done. + return; + } + } + + // if we get here, the resource type is valid, but the resource is missing. + // we select and expand the resource type element. + TreePath treePath = new TreePath(new Object[] { resourceType }); + mFilteredTree.getViewer().setSelection( + new TreeSelection(treePath), + true /*reveal*/); + mFilteredTree.getViewer().setExpandedState(resourceType, true /* expanded */); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java new file mode 100644 index 000000000..ce828cf2a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java @@ -0,0 +1,958 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import static com.android.SdkConstants.ANDROID_PREFIX; +import static com.android.SdkConstants.PREFIX_RESOURCE_REF; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.ide.eclipse.adt.internal.assetstudio.OpenCreateAssetSetWizardAction; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory; +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.resources.ResourceType; +import com.android.utils.Pair; +import com.google.common.collect.Maps; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.window.Window; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; +import org.eclipse.ui.dialogs.SelectionStatusDialog; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A dialog to let the user select a resource based on a resource type. + */ +public class ResourceChooser extends AbstractElementListSelectionDialog implements ModifyListener { + /** The return code from the dialog for the user choosing "Clear" */ + public static final int CLEAR_RETURN_CODE = -5; + /** The dialog button ID for the user choosing "Clear" */ + private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE; + + private Pattern mProjectResourcePattern; + private ResourceType mResourceType; + private final List<ResourceRepository> mProjectResources; + private final ResourceRepository mFrameworkResources; + private Pattern mSystemResourcePattern; + private Button mProjectButton; + private Button mSystemButton; + private Button mNewButton; + private String mCurrentResource; + private final IProject mProject; + private IInputValidator mInputValidator; + + /** Helper object used to draw previews for drawables and colors. */ + private ResourcePreviewHelper mPreviewHelper; + + /** + * Textfield for editing the actual returned value, updated when selection + * changes. Only shown if {@link #mShowValueText} is true. + */ + private Text mEditValueText; + + /** + * Whether the {@link #mEditValueText} textfield should be shown when the dialog is created. + */ + private boolean mShowValueText; + + /** + * Flag indicating whether it's the first time {@link #handleSelectionChanged()} is called. + * This is used to filter out the first selection event, always called by the superclass + * when the widget is created, to distinguish between "the dialog was created" and + * "the user clicked on a selection result", since only the latter should wipe out the + * manual user edit shown in the value text. + */ + private boolean mFirstSelect = true; + + /** + * Label used to show the resolved value in the resource chooser. Only shown + * if the {@link #mResourceResolver} field is set. + */ + private Label mResolvedLabel; + + /** Resource resolver used to show actual values for resources selected. (Optional). */ + private ResourceResolver mResourceResolver; + + /** + * Creates a Resource Chooser dialog. + * @param project Project being worked on + * @param type The type of the resource to choose + * @param projectResources The repository for the project + * @param frameworkResources The Framework resource repository + * @param parent the parent shell + */ + private ResourceChooser( + @NonNull IProject project, + @NonNull ResourceType type, + @NonNull List<ResourceRepository> projectResources, + @Nullable ResourceRepository frameworkResources, + @NonNull Shell parent) { + super(parent, new ResourceLabelProvider()); + mProject = project; + + mResourceType = type; + mProjectResources = projectResources; + mFrameworkResources = frameworkResources; + + mProjectResourcePattern = Pattern.compile( + PREFIX_RESOURCE_REF + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ + + mSystemResourcePattern = Pattern.compile( + ANDROID_PREFIX + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ + + setTitle("Resource Chooser"); + setMessage(String.format("Choose a %1$s resource", + mResourceType.getDisplayName().toLowerCase(Locale.US))); + } + + /** + * Creates a new {@link ResourceChooser} + * + * @param editor the associated layout editor + * @param type the resource type to choose + * @return a new {@link ResourceChooser} + */ + @NonNull + public static ResourceChooser create( + @NonNull GraphicalEditorPart editor, + @NonNull ResourceType type) { + IProject project = editor.getProject(); + Shell parent = editor.getCanvasControl().getShell(); + AndroidTargetData targetData = editor.getEditorDelegate().getEditor().getTargetData(); + ResourceChooser chooser = create(project, type, targetData, parent); + + // When editing Strings, allow editing the value text directly. When we + // get inline editing support (where values entered directly into the + // textual widget are translated automatically into a resource) this can + // go away. + if (type == ResourceType.STRING) { + chooser.setResourceResolver(editor.getResourceResolver()); + chooser.setShowValueText(true); + } else if (type == ResourceType.DIMEN || type == ResourceType.INTEGER) { + chooser.setResourceResolver(editor.getResourceResolver()); + } + + chooser.setPreviewHelper(new ResourcePreviewHelper(chooser, editor)); + return chooser; + } + + /** + * Creates a new {@link ResourceChooser} + * + * @param project the associated project + * @param type the resource type to choose + * @param targetData the associated framework target data + * @param parent the target shell + * @return a new {@link ResourceChooser} + */ + @NonNull + public static ResourceChooser create( + @NonNull IProject project, + @NonNull ResourceType type, + @Nullable AndroidTargetData targetData, + @NonNull Shell parent) { + ResourceManager manager = ResourceManager.getInstance(); + + List<ResourceRepository> projectResources = new ArrayList<ResourceRepository>(); + ProjectResources resources = manager.getProjectResources(project); + projectResources.add(resources); + + // Add in library project resources + ProjectState projectState = Sdk.getProjectState(project); + if (projectState != null) { + List<IProject> libraries = projectState.getFullLibraryProjects(); + if (libraries != null && !libraries.isEmpty()) { + for (IProject library : libraries) { + projectResources.add(manager.getProjectResources(library)); + } + } + } + + ResourceRepository frameworkResources = + targetData != null ? targetData.getFrameworkResources() : null; + return new ResourceChooser(project, type, projectResources, frameworkResources, parent); + } + + /** + * Sets whether this dialog should show the value field as a separate text + * value (and take the resulting value of the dialog from this text field + * rather than from the selection) + * + * @param showValueText if true, show the value text field + * @return this, for constructor chaining + */ + public ResourceChooser setShowValueText(boolean showValueText) { + mShowValueText = showValueText; + + return this; + } + + /** + * Sets the resource resolver to use to show resolved values for the current + * selection + * + * @param resourceResolver the resource resolver to use + * @return this, for constructor chaining + */ + public ResourceChooser setResourceResolver(ResourceResolver resourceResolver) { + mResourceResolver = resourceResolver; + + return this; + } + + /** + * Sets the {@link ResourcePreviewHelper} to use to preview drawable + * resources, if any + * + * @param previewHelper the helper to use + * @return this, for constructor chaining + */ + public ResourceChooser setPreviewHelper(ResourcePreviewHelper previewHelper) { + mPreviewHelper = previewHelper; + + return this; + } + + /** + * Sets the initial dialog size + * + * @param width the initial width + * @param height the initial height + * @return this, for constructor chaining + */ + public ResourceChooser setInitialSize(int width, int height) { + setSize(width, height); + + return this; + } + + @Override + public void create() { + super.create(); + + if (mShowValueText) { + mEditValueText.selectAll(); + mEditValueText.setFocus(); + } + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/); + super.createButtonsForButtonBar(parent); + } + + @Override + protected void buttonPressed(int buttonId) { + super.buttonPressed(buttonId); + + if (buttonId == CLEAR_BUTTON_ID) { + assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL; + setReturnCode(CLEAR_RETURN_CODE); + close(); + } + } + + /** + * Sets the currently selected item + * + * @param resource the resource url for the currently selected item + * @return this, for constructor chaining + */ + public ResourceChooser setCurrentResource(@Nullable String resource) { + mCurrentResource = resource; + + if (mShowValueText && mEditValueText != null) { + mEditValueText.setText(resource); + } + + return this; + } + + /** + * Returns the currently selected url + * + * @return the currently selected url + */ + @Nullable + public String getCurrentResource() { + return mCurrentResource; + } + + /** + * Sets the input validator to use, if any + * + * @param inputValidator the validator + * @return this, for constructor chaining + */ + public ResourceChooser setInputValidator(@Nullable IInputValidator inputValidator) { + mInputValidator = inputValidator; + + return this; + } + + @Override + protected void computeResult() { + if (mShowValueText) { + mCurrentResource = mEditValueText.getText(); + if (mCurrentResource.length() == 0) { + mCurrentResource = null; + } + return; + } + + computeResultFromSelection(); + } + + private void computeResultFromSelection() { + if (getSelectionIndex() == -1) { + mCurrentResource = null; + return; + } + + Object[] elements = getSelectedElements(); + if (elements.length == 1 && elements[0] instanceof ResourceItem) { + ResourceItem item = (ResourceItem)elements[0]; + + mCurrentResource = item.getXmlString(mResourceType, mSystemButton.getSelection()); + + if (mInputValidator != null && mInputValidator.isValid(mCurrentResource) != null) { + mCurrentResource = null; + } + } + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite top = (Composite)super.createDialogArea(parent); + + createMessageArea(top); + + createButtons(top); + createFilterText(top); + createFilteredList(top); + + // create the "New Resource" button + createNewResButtons(top); + + // Optionally create the value text field, if {@link #mShowValueText} is true + createValueField(top); + + setupResourceList(); + selectResourceString(mCurrentResource); + + return top; + } + + /** + * Creates the radio button to switch between project and system resources. + * @param top the parent composite + */ + private void createButtons(Composite top) { + mProjectButton = new Button(top, SWT.RADIO); + mProjectButton.setText("Project Resources"); + mProjectButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + if (mProjectButton.getSelection()) { + // Clear selection before changing the list contents. This works around + // a bug in the superclass where switching to the framework resources, + // choosing one of the last resources, then switching to the project + // resources would cause an exception when calling getSelection() because + // selection state doesn't get cleared when we set new contents on + // the filtered list. + fFilteredList.setSelection(new int[0]); + setupResourceList(); + updateNewButton(false /*isSystem*/); + updateValue(); + } + } + }); + mSystemButton = new Button(top, SWT.RADIO); + mSystemButton.setText("System Resources"); + mSystemButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + if (mSystemButton.getSelection()) { + fFilteredList.setSelection(new int[0]); + setupResourceList(); + updateNewButton(true /*isSystem*/); + updateValue(); + } + } + }); + if (mFrameworkResources == null) { + mSystemButton.setVisible(false); + } + } + + /** + * Creates the "New Resource" button. + * @param top the parent composite + */ + private void createNewResButtons(Composite top) { + mNewButton = new Button(top, SWT.NONE); + + String title = String.format("New %1$s...", mResourceType.getDisplayName()); + if (mResourceType == ResourceType.DRAWABLE) { + title = "Create New Icon..."; + } + mNewButton.setText(title); + + mNewButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + + if (mResourceType == ResourceType.STRING) { + // Special case: Use Extract String refactoring wizard UI + String newName = createNewString(); + selectAddedItem(newName); + } else if (mResourceType == ResourceType.DRAWABLE) { + // Special case: Use the "Create Icon Set" wizard + OpenCreateAssetSetWizardAction action = + new OpenCreateAssetSetWizardAction(mProject); + action.run(); + List<IResource> files = action.getCreatedFiles(); + if (files != null && files.size() > 0) { + String newName = AdtUtils.stripAllExtensions(files.get(0).getName()); + // Recompute the "current resource" to select the new id + ResourceItem[] items = setupResourceList(); + selectItemName(newName, items); + } + } else { + if (ResourceHelper.isValueBasedResourceType(mResourceType)) { + String newName = createNewValue(mResourceType); + if (newName != null) { + selectAddedItem(newName); + } + } else { + String newName = createNewFile(mResourceType); + if (newName != null) { + selectAddedItem(newName); + } + } + } + } + + private void selectAddedItem(@NonNull String newName) { + // Recompute the "current resource" to select the new id + ResourceItem[] items = setupResourceList(); + + // Ensure that the name is in the list. There's a delay after + // an item is added (until the builder runs and processes the delta) + // so if it's not in the list, add it + boolean found = false; + for (ResourceItem item : items) { + if (newName.equals(item.getName())) { + found = true; + break; + } + } + if (!found) { + ResourceItem[] newItems = new ResourceItem[items.length + 1]; + System.arraycopy(items, 0, newItems, 0, items.length); + newItems[items.length] = new ResourceItem(newName); + items = newItems; + Arrays.sort(items); + setListElements(items); + fFilteredList.setEnabled(newItems.length > 0); + } + + selectItemName(newName, items); + } + }); + } + + /** + * Creates the value text field. + * + * @param top the parent composite + */ + private void createValueField(Composite top) { + if (mShowValueText) { + mEditValueText = new Text(top, SWT.BORDER); + if (mCurrentResource != null) { + mEditValueText.setText(mCurrentResource); + } + mEditValueText.addModifyListener(this); + + GridData data = new GridData(); + data.grabExcessVerticalSpace = false; + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + data.verticalAlignment = GridData.BEGINNING; + mEditValueText.setLayoutData(data); + mEditValueText.setFont(top.getFont()); + } + + if (mResourceResolver != null) { + mResolvedLabel = new Label(top, SWT.NONE); + GridData data = new GridData(); + data.grabExcessVerticalSpace = false; + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + data.verticalAlignment = GridData.BEGINNING; + mResolvedLabel.setLayoutData(data); + } + + Composite workaround = PropertyFactory.addWorkaround(top); + if (workaround != null) { + workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1)); + } + } + + private void updateResolvedLabel() { + if (mResourceResolver == null) { + return; + } + + String v = null; + if (mCurrentResource != null) { + v = mCurrentResource; + if (mCurrentResource.startsWith(PREFIX_RESOURCE_REF)) { + ResourceValue value = mResourceResolver.findResValue(mCurrentResource, false); + if (value != null) { + v = value.getValue(); + } + } + } + + if (v == null) { + v = ""; + } + + mResolvedLabel.setText(String.format("Resolved Value: %1$s", v)); + } + + @Override + protected void handleSelectionChanged() { + super.handleSelectionChanged(); + if (mInputValidator != null) { + Object[] elements = getSelectedElements(); + if (elements.length == 1 && elements[0] instanceof ResourceItem) { + ResourceItem item = (ResourceItem)elements[0]; + String current = item.getXmlString(mResourceType, mSystemButton.getSelection()); + String error = mInputValidator.isValid(current); + IStatus status; + if (error != null) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); + } else { + status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); + } + updateStatus(status); + } + } + + updateValue(); + } + + private void updateValue() { + if (mPreviewHelper != null) { + computeResult(); + mPreviewHelper.updatePreview(mResourceType, mCurrentResource); + } + + if (mShowValueText) { + if (mFirstSelect) { + mFirstSelect = false; + mEditValueText.selectAll(); + } else { + computeResultFromSelection(); + mEditValueText.setText(mCurrentResource != null ? mCurrentResource : ""); + } + } + + if (mResourceResolver != null) { + if (!mShowValueText) { + computeResultFromSelection(); + } + updateResolvedLabel(); + } + } + + @Nullable + private String createNewFile(ResourceType type) { + // Show a name/value dialog entering the key name and the value + Shell shell = AdtPlugin.getShell(); + if (shell == null) { + return null; + } + + ResourceNameValidator validator = ResourceNameValidator.create(true /*allowXmlExtension*/, + mProject, mResourceType); + InputDialog d = new InputDialog( + AdtPlugin.getShell(), + "Enter name", // title + "Enter name", + "", //$NON-NLS-1$ + validator); + if (d.open() == Window.OK) { + String name = d.getValue().trim(); + if (name.length() == 0) { + return null; + } + + Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, + null); + if (resource != null) { + return name; + } + } + + return null; + } + + + @Nullable + private String createNewValue(ResourceType type) { + // Show a name/value dialog entering the key name and the value + Shell shell = AdtPlugin.getShell(); + if (shell == null) { + return null; + } + NameValueDialog dialog = new NameValueDialog(shell, getFilter()); + if (dialog.open() != Window.OK) { + return null; + } + + String name = dialog.getName(); + String value = dialog.getValue(); + if (name.length() == 0 || value.length() == 0) { + return null; + } + + Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, value); + if (resource != null) { + return name; + } + + return null; + } + + private String createNewString() { + ExtractStringRefactoring ref = new ExtractStringRefactoring( + mProject, true /*enforceNew*/); + RefactoringWizard wizard = new ExtractStringWizard(ref, mProject); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + IWorkbench w = PlatformUI.getWorkbench(); + if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) == + IDialogConstants.OK_ID) { + return ref.getXmlStringId(); + } + } catch (InterruptedException ex) { + // Interrupted. Pass. + } + + return null; + } + + /** + * Setups the current list. + */ + private ResourceItem[] setupResourceList() { + Collection<ResourceItem> items = null; + if (mProjectButton.getSelection()) { + if (mProjectResources.size() == 1) { + items = mProjectResources.get(0).getResourceItemsOfType(mResourceType); + } else { + Map<String, ResourceItem> merged = Maps.newHashMapWithExpectedSize(200); + for (ResourceRepository repository : mProjectResources) { + for (ResourceItem item : repository.getResourceItemsOfType(mResourceType)) { + if (!merged.containsKey(item.getName())) { + merged.put(item.getName(), item); + } + } + } + items = merged.values(); + } + } else if (mSystemButton.getSelection()) { + items = mFrameworkResources.getResourceItemsOfType(mResourceType); + } + + if (items == null) { + items = Collections.emptyList(); + } + + ResourceItem[] arrayItems = items.toArray(new ResourceItem[items.size()]); + + // sort the array + Arrays.sort(arrayItems); + + setListElements(arrayItems); + fFilteredList.setEnabled(arrayItems.length > 0); + + return arrayItems; + } + + /** + * Select an item by its name, if possible. + */ + private void selectItemName(String itemName, ResourceItem[] items) { + if (itemName == null || items == null) { + return; + } + + for (ResourceItem item : items) { + if (itemName.equals(item.getName())) { + setSelection(new Object[] { item }); + break; + } + } + } + + /** + * Select an item by its full resource string. + * This also selects between project and system repository based on the resource string. + */ + private void selectResourceString(String resourceString) { + boolean isSystem = false; + String itemName = null; + + if (resourceString != null) { + // Is this a system resource? + // If not a system resource or if they are not available, this will be a project res. + Matcher m = mSystemResourcePattern.matcher(resourceString); + if (m.matches()) { + itemName = m.group(1); + isSystem = true; + } + + if (!isSystem && itemName == null) { + // Try to match project resource name + m = mProjectResourcePattern.matcher(resourceString); + if (m.matches()) { + itemName = m.group(1); + } + } + } + + // Update the repository selection + mProjectButton.setSelection(!isSystem); + mSystemButton.setSelection(isSystem); + updateNewButton(isSystem); + + // Update the list + ResourceItem[] items = setupResourceList(); + + // If we have a selection name, select it + if (itemName != null) { + selectItemName(itemName, items); + } + } + + private void updateNewButton(boolean isSystem) { + mNewButton.setEnabled(!isSystem && ResourceHelper.canCreateResourceType(mResourceType)); + } + + // ---- Implements ModifyListener ---- + + @Override + public void modifyText(ModifyEvent e) { + if (e.getSource() == mEditValueText && mResourceResolver != null) { + mCurrentResource = mEditValueText.getText(); + + if (mCurrentResource.startsWith(PREFIX_RESOURCE_REF)) { + if (mProjectResourcePattern.matcher(mCurrentResource).matches() || + mSystemResourcePattern.matcher(mCurrentResource).matches()) { + updateResolvedLabel(); + } + } else { + updateResolvedLabel(); + } + } + } + + /** Dialog asking for a Name/Value pair */ + private class NameValueDialog extends SelectionStatusDialog implements Listener { + private org.eclipse.swt.widgets.Text mNameText; + private org.eclipse.swt.widgets.Text mValueText; + private String mInitialName; + private String mName; + private String mValue; + private ResourceNameValidator mValidator; + + public NameValueDialog(Shell parent, String initialName) { + super(parent); + mInitialName = initialName; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite container = new Composite(parent, SWT.NONE); + container.setLayout(new GridLayout(2, false)); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); + // Wide enough to accommodate the error label + gridData.widthHint = 500; + container.setLayoutData(gridData); + + + Label nameLabel = new Label(container, SWT.NONE); + nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + nameLabel.setText("Name:"); + + mNameText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); + mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + if (mInitialName != null) { + mNameText.setText(mInitialName); + mNameText.selectAll(); + } + + Label valueLabel = new Label(container, SWT.NONE); + valueLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + valueLabel.setText("Value:"); + + mValueText = new org.eclipse.swt.widgets.Text(container, SWT.BORDER); + mValueText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + + mNameText.addListener(SWT.Modify, this); + mValueText.addListener(SWT.Modify, this); + + validate(); + + return container; + } + + @Override + protected void computeResult() { + mName = mNameText.getText().trim(); + mValue = mValueText.getText().trim(); + } + + private String getName() { + return mName; + } + + private String getValue() { + return mValue; + } + + @Override + public void handleEvent(Event event) { + validate(); + } + + private void validate() { + IStatus status; + computeResult(); + if (mName.length() == 0) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a name"); + } else if (mValue.length() == 0) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a value"); + } else { + if (mValidator == null) { + mValidator = ResourceNameValidator.create(false, mProject, mResourceType); + } + String error = mValidator.isValid(mName); + if (error != null) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); + } else { + status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); + } + } + updateStatus(status); + } + } + + /** + * Open the resource chooser for the given type, associated with the given + * editor + * + * @param graphicalEditor the editor associated with the resource to be + * chosen (used to find the associated Android target to be used + * for framework resources etc) + * @param type the resource type to be chosen + * @param currentValue the current value, or null + * @param validator a validator to be used, or null + * @return the chosen resource, null if cancelled and "" if value should be + * cleared + */ + public static String chooseResource( + @NonNull GraphicalEditorPart graphicalEditor, + @NonNull ResourceType type, + String currentValue, IInputValidator validator) { + ResourceChooser chooser = create(graphicalEditor, type). + setCurrentResource(currentValue); + if (validator != null) { + // Ensure wide enough to accommodate validator error message + chooser.setSize(85, 10); + chooser.setInputValidator(validator); + } + int result = chooser.open(); + if (result == ResourceChooser.CLEAR_RETURN_CODE) { + return ""; //$NON-NLS-1$ + } else if (result == Window.OK) { + return chooser.getCurrentResource(); + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java new file mode 100644 index 000000000..d26dfaf4a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.resources.ResourceType; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Content provider for the Resource Explorer TreeView. + * Each level of the tree is represented by a different class. + * <ul> + * <li>{@link ResourceType}. This represents the list of existing Resource Type present + * in the resources. This can be matched to the subclasses inside the class <code>R</code> + * </li> + * <ul> + * <li>{@link ResourceItem}. This represents one resource (which can existing in various alternate + * versions). This is similar to the resource Ids defined as <code>R.sometype.id</code>. + * </li> + * <ul> + * <li>{@link ResourceFile}. (optional) This represents a particular version of the + * {@link ResourceItem}. It is displayed as a list of resource qualifier. + * </li> + * </ul> + * </ul> + * </ul> + * + * @see ResourceLabelProvider + */ +public class ResourceContentProvider implements ITreeContentProvider { + + /** + * The current ProjectResources being displayed. + */ + private ResourceRepository mResources; + + private boolean mFullLevels; + + /** + * Constructs a new content providers for resource display. + * @param fullLevels if <code>true</code> the content provider will suppport all 3 levels. If + * <code>false</code>, only two levels are provided. + */ + public ResourceContentProvider(boolean fullLevels) { + mFullLevels = fullLevels; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof ResourceType) { + Object[] array = mResources.getResourceItemsOfType( + (ResourceType)parentElement).toArray(); + Arrays.sort(array); + return array; + } else if (mFullLevels && parentElement instanceof ResourceItem) { + return ((ResourceItem)parentElement).getSourceFileArray(); + } + return null; + } + + @Override + public Object getParent(Object element) { + // pass + return null; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof ResourceType) { + return mResources.hasResourcesOfType((ResourceType)element); + } else if (mFullLevels && element instanceof ResourceItem) { + return ((ResourceItem)element).hasAlternates(); + } + return false; + } + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof ResourceRepository) { + if ((ResourceRepository)inputElement == mResources) { + // get the top level resources. + List<ResourceType> types = mResources.getAvailableResourceTypes(); + Collections.sort(types); + return types.toArray(); + } + } + + return new Object[0]; + } + + @Override + public void dispose() { + // pass + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof ResourceRepository) { + mResources = (ResourceRepository)newInput; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java new file mode 100644 index 000000000..f48423c84 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; +import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.io.IFileWrapper; +import com.android.io.IAbstractFile; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.ViewPart; + +import java.util.Iterator; + +/** + * Resource Explorer View. + * <p/> + * This contains a basic Tree view, and uses a TreeViewer to handle the data. + * <p/> + * The view listener to change in selection in the workbench, and update to show the resource + * of the project of the current selected item (either item in the package explorer, or of the + * current editor). + * + * @see ResourceContentProvider + */ +public class ResourceExplorerView extends ViewPart implements ISelectionListener, + IResourceEventListener { + + // Note: keep using the obsolete SdkConstants.EDITORS_NAMESPACE (which used + // to be the Editors Plugin ID) to keep existing preferences functional. + private final static String PREFS_COLUMN_RES = + AdtConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col1"; //$NON-NLS-1$ + private final static String PREFS_COLUMN_2 = + AdtConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col2"; //$NON-NLS-1$ + + private Tree mTree; + private TreeViewer mTreeViewer; + + private IProject mCurrentProject; + + public ResourceExplorerView() { + } + + @Override + public void createPartControl(Composite parent) { + mTree = new Tree(parent, SWT.SINGLE | SWT.VIRTUAL); + mTree.setLayoutData(new GridData(GridData.FILL_BOTH)); + mTree.setHeaderVisible(true); + mTree.setLinesVisible(true); + + final IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + + // create 2 columns. The main one with the resources, and an "info" column. + createTreeColumn(mTree, "Resources", SWT.LEFT, + "abcdefghijklmnopqrstuvwxz", -1, PREFS_COLUMN_RES, store); //$NON-NLS-1$ + createTreeColumn(mTree, "Info", SWT.LEFT, + "0123456789", -1, PREFS_COLUMN_2, store); //$NON-NLS-1$ + + // create the jface wrapper + mTreeViewer = new TreeViewer(mTree); + + mTreeViewer.setContentProvider(new ResourceContentProvider(true /* fullLevels */)); + mTreeViewer.setLabelProvider(new ResourceLabelProvider()); + + // listen to selection change in the workbench. + IWorkbenchPage page = getSite().getPage(); + + page.addSelectionListener(this); + + // init with current selection + selectionChanged(getSite().getPart(), page.getSelection()); + + // add support for double click. + mTreeViewer.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + ISelection sel = event.getSelection(); + + if (sel instanceof IStructuredSelection) { + IStructuredSelection selection = (IStructuredSelection) sel; + + if (selection.size() == 1) { + Object element = selection.getFirstElement(); + + // if it's a resourceFile, we directly open it. + if (element instanceof ResourceFile) { + try { + IAbstractFile iAbstractFile = ((ResourceFile)element).getFile(); + if (iAbstractFile instanceof IFileWrapper) { + IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), + ((IFileWrapper)iAbstractFile).getIFile()); + } + } catch (PartInitException e) { + } + } else if (element instanceof ResourceItem) { + // if it's a ResourceItem, we open the first file, but only if + // there's no alternate files. + ResourceItem item = (ResourceItem)element; + + if (item.isEditableDirectly()) { + ResourceFile[] files = item.getSourceFileArray(); + if (files[0] != null) { + try { + IAbstractFile iAbstractFile = files[0].getFile(); + if (iAbstractFile instanceof IFileWrapper) { + IDE.openEditor( + getSite().getWorkbenchWindow().getActivePage(), + ((IFileWrapper)iAbstractFile).getIFile()); + } + } catch (PartInitException e) { + } + } + } + } + } + } + } + }); + + // set up the resource manager to send us resource change notification + AdtPlugin.getDefault().getResourceMonitor().addResourceEventListener(this); + } + + @Override + public void dispose() { + AdtPlugin.getDefault().getResourceMonitor().removeResourceEventListener(this); + + super.dispose(); + } + + @Override + public void setFocus() { + mTree.setFocus(); + } + + /** + * Processes a new selection. + */ + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + // first we test if the part is an editor. + if (part instanceof IEditorPart) { + // if it is, we check if it's a file editor. + IEditorInput input = ((IEditorPart)part).getEditorInput(); + + if (input instanceof IFileEditorInput) { + // from the file editor we can get the IFile object, and from it, the IProject. + IFile file = ((IFileEditorInput)input).getFile(); + + // get the file project + IProject project = file.getProject(); + + handleProjectSelection(project); + } + } else if (selection instanceof IStructuredSelection) { + // if it's not an editor, we look for structured selection. + for (Iterator<?> it = ((IStructuredSelection) selection).iterator(); + it.hasNext();) { + Object element = it.next(); + IProject project = null; + + // if we are in the navigator or package explorer, the selection could contain a + // IResource object. + if (element instanceof IResource) { + project = ((IResource) element).getProject(); + } else if (element instanceof IJavaElement) { + // if we are in the package explorer on a java element, we handle that too. + IJavaElement javaElement = (IJavaElement)element; + IJavaProject javaProject = javaElement.getJavaProject(); + if (javaProject != null) { + project = javaProject.getProject(); + } + } else if (element instanceof IAdaptable) { + // finally we try to get a project object from IAdaptable. + project = (IProject) ((IAdaptable) element) + .getAdapter(IProject.class); + } + + // if we found a project, handle it, and return. + if (project != null) { + if (handleProjectSelection(project)) { + return; + } + } + } + } + } + + /** + * Handles a project selection. + * @param project the new selected project + * @return true if the project could be processed. + */ + private boolean handleProjectSelection(IProject project) { + try { + // if it's an android project, then we get its resources, and feed them + // to the tree viewer. + if (project.hasNature(AdtConstants.NATURE_DEFAULT)) { + if (mCurrentProject != project) { + ProjectResources projRes = ResourceManager.getInstance().getProjectResources( + project); + if (projRes != null) { + mTreeViewer.setInput(projRes); + mCurrentProject = project; + return true; + } + } + } + } catch (CoreException e) { + } + + return false; + } + + /** + * Create a TreeColumn with the specified parameters. If a + * <code>PreferenceStore</code> object and a preference entry name String + * object are provided then the column will listen to change in its width + * and update the preference store accordingly. + * + * @param parent The Table parent object + * @param header The header string + * @param style The column style + * @param sample_text A sample text to figure out column width if preference + * value is missing + * @param fixedSize a fixed size. If != -1 the column is non resizable + * @param pref_name The preference entry name for column width + * @param prefs The preference store + */ + public void createTreeColumn(Tree parent, String header, int style, + String sample_text, int fixedSize, final String pref_name, + final IPreferenceStore prefs) { + + // create the column + TreeColumn col = new TreeColumn(parent, style); + + if (fixedSize != -1) { + col.setWidth(fixedSize); + col.setResizable(false); + } else { + // if there is no pref store or the entry is missing, we use the sample + // text and pack the column. + // Otherwise we just read the width from the prefs and apply it. + if (prefs == null || prefs.contains(pref_name) == false) { + col.setText(sample_text); + col.pack(); + + // init the prefs store with the current value + if (prefs != null) { + prefs.setValue(pref_name, col.getWidth()); + } + } else { + col.setWidth(prefs.getInt(pref_name)); + } + + // if there is a pref store and a pref entry name, then we setup a + // listener to catch column resize to put the new width value into the store. + if (prefs != null && pref_name != null) { + col.addControlListener(new ControlListener() { + @Override + public void controlMoved(ControlEvent e) { + } + + @Override + public void controlResized(ControlEvent e) { + // get the new width + int w = ((TreeColumn)e.widget).getWidth(); + + // store in pref store + prefs.setValue(pref_name, w); + } + }); + } + } + + // set the header + col.setText(header); + } + + /** + * Processes a start in a resource event change. + */ + @Override + public void resourceChangeEventStart() { + // pass + } + + /** + * Processes the end of a resource change event. + */ + @Override + public void resourceChangeEventEnd() { + try { + mTree.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (mTree.isDisposed() == false) { + mTreeViewer.refresh(); + } + } + }); + } catch (SWTException e) { + // display is disposed. nothing to do. + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java new file mode 100644 index 000000000..5bf8615d8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceItem; +import com.android.resources.ResourceType; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +/** + * Label provider for the Resource Explorer TreeView. + * Each level of the tree is represented by a different class. + * <ul> + * <li>{@link ResourceType}. This represents the list of existing Resource Type present + * in the resources. This can be matched to the subclasses inside the class <code>R</code> + * </li> + * <ul> + * <li>{@link ResourceItem}. This represents one resource. The actual type can be + * {@link ConfigurableResourceItem} (which can exist in various alternate versions), + * or {@link IdResourceItem}. + * This is similar to the resource Ids defined as <code>R.sometype.id</code>. + * </li> + * <ul> + * <li>{@link ResourceFile}. This represents a particular version of the {@link ResourceItem}. + * It is displayed as a list of resource qualifier. + * </li> + * </ul> + * </ul> + * </ul> + * + * @see ResourceContentProvider + */ +public class ResourceLabelProvider implements ILabelProvider, ITableLabelProvider { + private Image mWarningImage; + + public ResourceLabelProvider() { + mWarningImage = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( + ISharedImages.IMG_OBJS_WARN_TSK).createImage(); + } + + /** + * @see #getColumnImage(Object, int) + */ + @Override + public Image getImage(Object element) { + // pass + return null; + } + + /** + * @see #getColumnText(Object, int) + */ + @Override + public String getText(Object element) { + return getColumnText(element, 0); + } + + @Override + public void addListener(ILabelProviderListener listener) { + // pass + } + + @Override + public void dispose() { + mWarningImage.dispose(); + } + + @Override + public boolean isLabelProperty(Object element, String property) { + return false; + } + + @Override + public void removeListener(ILabelProviderListener listener) { + // pass + } + + @Override + public Image getColumnImage(Object element, int columnIndex) { + if (columnIndex == 1) { + if (element instanceof ResourceItem) { + ResourceItem item = (ResourceItem)element; + if (item.hasDefault() == false) { + return mWarningImage; + } + } + } + return null; + } + + @Override + public String getColumnText(Object element, int columnIndex) { + switch (columnIndex) { + case 0: + if (element instanceof ResourceType) { + return ((ResourceType)element).getDisplayName(); + } else if (element instanceof ResourceItem) { + return ((ResourceItem)element).getName(); + } else if (element instanceof ResourceFile) { + return ((ResourceFile)element).getFolder().getConfiguration().toDisplayString(); + } + break; + case 1: + if (element instanceof ResourceItem) { + ResourceItem item = (ResourceItem)element; + if (item.isDeclaredInline()) { + return "Declared inline"; + } else { + int count = item.getAlternateCount(); + if (count > 0) { + if (item.hasDefault()) { + count++; + } + return String.format("%1$d version(s)", count); + } + } + } + return null; + } + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java new file mode 100644 index 000000000..afd1df92d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.ui; + +import static com.android.SdkConstants.DOT_9PNG; +import static com.android.utils.SdkUtils.endsWithIgnoreCase; + +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.resources.ResourceType; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.dialogs.DialogTray; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +/** + * The {@link ResourcePreviewHelper} provides help to {@link TrayDialog} resource choosers + * where some resources (such as drawables and colors) are previewed in the tray area. + */ +public class ResourcePreviewHelper { + /** + * The width of the preview rendering + * <p> + * TODO: Make the preview rendering resize if the tray area is resized + */ + private static final int WIDTH = 100; + /** The height of the preview rendering */ + private static final int HEIGHT = 100; + + private final GraphicalEditorPart mEditor; + private final TrayDialog mTrayDialog; + + private boolean mShowingPreview; + private DialogTray mPreviewTray; + private ImageControl mPreviewImageControl; + + /** + * Constructs a new {@link ResourcePreviewHelper}. + * <p> + * TODO: Add support for performing previews without an associated graphical editor, + * such as previewing icons from the manifest form editor; just pick default + * configuration settings in that case. + * + * @param trayDialog the associated tray-capable dialog + * @param editor a graphical editor. This is currently needed in order to provide + * configuration data for the rendering. + */ + public ResourcePreviewHelper(TrayDialog trayDialog, GraphicalEditorPart editor) { + this.mTrayDialog = trayDialog; + this.mEditor = editor; + } + + /** + * Updates the preview based on the current selection and resource type, possibly + * hiding or opening the tray in the process. + * + * @param type the resource type for the selected resource + * @param resource the full resource url + */ + public void updatePreview(ResourceType type, String resource) { + boolean showPreview = type == ResourceType.DRAWABLE || type == ResourceType.COLOR; + if (showPreview) { + if (mPreviewTray == null) { + mPreviewTray = new DialogTray() { + @Override + protected Control createContents(Composite parent) { + // This creates a centered image control + Composite panel = new Composite(parent, SWT.NONE); + panel.setLayout(new GridLayout(3, false)); + Label dummy1 = new Label(panel, SWT.NONE); + dummy1.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true, 1, 1)); + mPreviewImageControl = new ImageControl(panel, SWT.NONE, SwtUtils + .createEmptyImage(parent.getDisplay(), WIDTH, HEIGHT)); + GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1); + gd.widthHint = WIDTH; + gd.heightHint = HEIGHT; + mPreviewImageControl.setLayoutData(gd); + Label dummy2 = new Label(panel, SWT.NONE); + dummy2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true, 1, 1)); + + return panel; + } + + }; + } + + if (!mShowingPreview) { + mTrayDialog.openTray(mPreviewTray); + } + + BufferedImage image = null; + try { + if (type == ResourceType.COLOR) { + ResourceResolver resources = mEditor.getResourceResolver(); + ResourceValue value = resources.findResValue(resource, false); + if (value != null) { + RGB color = ResourceHelper.resolveColor(resources, value); + if (color != null) { + image = ImageUtils.createColoredImage(WIDTH, HEIGHT, color); + } + } + } else { + assert type == ResourceType.DRAWABLE; + + ResourceResolver resources = mEditor.getResourceResolver(); + ResourceValue drawable = resources.findResValue(resource, false); + if (drawable != null) { + String path = drawable.getValue(); + + // Special-case image files (other than 9-patch files) and render these + // directly, in order to provide proper aspect ratio handling and + // to handle scaling to show the full contents: + if (ImageUtils.hasImageExtension(path) + && !endsWithIgnoreCase(path, DOT_9PNG)) { + File file = new File(path); + if (file.exists()) { + try { + image = ImageIO.read(file); + int width = image.getWidth(); + int height = image.getHeight(); + if (width > WIDTH || height > HEIGHT) { + double xScale = WIDTH / (double) width; + double yScale = HEIGHT / (double) height; + double scale = Math.min(xScale, yScale); + image = ImageUtils.scale(image, scale, scale); + } + } catch (IOException e) { + AdtPlugin.log(e, "Can't read preview image %1$s", path); + } + } + } + + if (image == null) { + RenderService renderService = RenderService.create(mEditor); + renderService.setOverrideRenderSize(WIDTH, HEIGHT); + image = renderService.renderDrawable(drawable); + } + } + } + } catch (Throwable t) { + // Some kind of rendering error occurred. However, we don't want to use + // AdtPlugin.log(t, "Can't generate preview for %1$s", resource); + // because if it's a severe type of error (such as an InternalError shown + // in issue #18623) then a dialog will pop up and interfere with the + // preview, so just log a warning (unfortunately without the trace) instead. + AdtPlugin.log(IStatus.WARNING, "Can't generate preview for %1$s", resource); + } + + Display display = mEditor.getSite().getShell().getDisplay(); + if (image != null) { + mPreviewImageControl.setImage(SwtUtils.convertToSwt(display, image, true, -1)); + } else { + mPreviewImageControl.setImage(SwtUtils.createEmptyImage(display, WIDTH, HEIGHT)); + } + mPreviewImageControl.redraw(); + } else if (mPreviewTray != null && mShowingPreview) { + mTrayDialog.closeTray(); + } + mShowingPreview = showPreview; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/WizardDialogEx.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/WizardDialogEx.java new file mode 100755 index 000000000..ee1ac9725 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/WizardDialogEx.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.ui; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Shell; + +/** + * A {@link WizardDialog} that gives access to some inner controls. + */ +public final class WizardDialogEx extends WizardDialog { + + /** + * @see WizardDialog#WizardDialog(Shell, IWizard) + */ + public WizardDialogEx(Shell parentShell, IWizard newWizard) { + super(parentShell, newWizard); + } + + /** + * Returns the cancel button. + * <p/> + * Note: there is already a protected, deprecated method that does the same thing. + * To avoid overriding a deprecated method, the name as be changed to ...Ex. + */ + public Button getCancelButtonEx() { + return getButton(IDialogConstants.CANCEL_ID); + } +} |