diff --git a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/TableHelper.java b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/TableHelper.java index f8d457ee0..9d557e04a 100644 --- a/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/TableHelper.java +++ b/tools/ddms/libs/ddmuilib/src/com/android/ddmuilib/TableHelper.java @@ -146,4 +146,58 @@ public final class TableHelper { } } + /** + * Create a TreeColumn with the specified parameters. If a + * PreferenceStore 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 width the width of the column if the preference value is missing + * @param pref_name The preference entry name for column width + * @param prefs The preference store + */ + public static void createTreeColumn(Tree parent, String header, int style, + int width, final String pref_name, + final IPreferenceStore prefs) { + + // create the column + TreeColumn col = new TreeColumn(parent, style); + + // 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.setWidth(width); + + // init the prefs store with the current value + if (prefs != null) { + prefs.setValue(pref_name, width); + } + } else { + col.setWidth(prefs.getInt(pref_name)); + } + + // set the header + col.setText(header); + + // if there is a pref store and a pref entry name, then we setup a + // listener to catch column resize to put store the new width value. + if (prefs != null && pref_name != null) { + col.addControlListener(new ControlListener() { + public void controlMoved(ControlEvent e) { + } + + public void controlResized(ControlEvent e) { + // get the new width + int w = ((TreeColumn)e.widget).getWidth(); + + // store in pref store + prefs.setValue(pref_name, w); + } + }); + } + } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 047c985d7..a52599fae 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -1029,10 +1029,7 @@ public class AdtPlugin extends AbstractUIPlugin { final IAndroidTarget[] targets = sdk.getTargets(); final int n = targets.length; if (n > 0) { - // load the layout devices. - sdk.parseAddOnLayoutDevices(); - - // load the rest of the targes. + // load the rest of the targets. // TODO: make this on-demand. int w = 60 / n; for (IAndroidTarget target : targets) { diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java index f6fa0ea32..302d3db09 100755 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalEditorPart.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener; import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java index 4f4ebb15c..1d9054c00 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java @@ -21,6 +21,7 @@ import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor.UiEditorActions; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand; diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java new file mode 100644 index 000000000..ca377864f --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java @@ -0,0 +1,295 @@ +package com.android.ide.eclipse.adt.internal.editors.layout.configuration; + +import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier; +import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; +import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; +import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQualifier; +import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; +import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; +import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; +import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.IQualifierFilter; +import com.android.sdkuilib.ui.GridDialog; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.graphics.Image; +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.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import java.util.regex.Pattern; + +/** + * Dialog to edit both a {@link LayoutDevice}, and a {@link FolderConfiguration} at the same time. + */ +public class ConfigEditDialog extends GridDialog { + + private static final Pattern FLOAT_PATTERN = Pattern.compile("\\d*(\\.\\d?)?"); + + + private final FolderConfiguration mConfig = new FolderConfiguration(); + + private ConfigurationSelector mConfigSelector; + private Composite mStatusComposite; + private Label mStatusLabel; + private Label mStatusImage; + + private Image mError; + + private String mDeviceName; + private String mConfigName; + private float mXDpi = 0f; + private float mYDpi = 0f; + + + public ConfigEditDialog(Shell parentShell, FolderConfiguration config) { + super(parentShell, 1, false); + mConfig.set(config); + } + + public void setDeviceName(String name) { + mDeviceName = name; + } + + public String getDeviceName() { + return mDeviceName; + } + + public void setXDpi(float xdpi) { + mXDpi = xdpi; + } + + public float getXDpi() { + return mXDpi; + } + + public void setYDpi(float ydpi) { + mYDpi = ydpi; + } + + public float getYDpi() { + return mYDpi; + } + + public void setConfigName(String name) { + mConfigName = name; + } + + public String getConfigName() { + return mConfigName; + } + + public void setConfig(FolderConfiguration config) { + mConfig.set(config); + } + + public void getConfig(FolderConfiguration config) { + config.set(mConfig); + } + + @Override + public void createDialogContent(Composite parent) { + mError = IconFactory.getInstance().getIcon("error"); //$NON-NLS-1$ + + Group deviceGroup = new Group(parent, SWT.NONE); + deviceGroup.setText("Device"); + deviceGroup.setLayoutData(new GridData(GridData.FILL_BOTH)); + deviceGroup.setLayout(new GridLayout(2, false)); + + Label l = new Label(deviceGroup, SWT.None); + l.setText("Name"); + + final Text deviceNameText = new Text(deviceGroup, SWT.BORDER); + deviceNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + if (mDeviceName != null) { + deviceNameText.setText(mDeviceName); + } + deviceNameText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mDeviceName = deviceNameText.getText().trim(); + validateOk(); + } + }); + + + VerifyListener floatVerifier = new VerifyListener() { + public void verifyText(VerifyEvent event) { + // combine the current content and the new text + String text = ((Text)event.widget).getText(); + text = text.substring(0, event.start) + event.text + text.substring(event.end); + + // now make sure it's a match for the regex + event.doit = FLOAT_PATTERN.matcher(text).matches(); + } + }; + + l = new Label(deviceGroup, SWT.None); + l.setText("x dpi"); + + final Text deviceXDpiText = new Text(deviceGroup, SWT.BORDER); + deviceXDpiText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + if (mXDpi != 0f) { + deviceXDpiText.setText(String.format("%.1f", mXDpi)); //$NON-NLS-1$ + } + deviceXDpiText.addVerifyListener(floatVerifier); + deviceXDpiText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mXDpi = Float.parseFloat(deviceXDpiText.getText()); + } + }); + + l = new Label(deviceGroup, SWT.None); + l.setText("y dpi"); + + final Text deviceYDpiText = new Text(deviceGroup, SWT.BORDER); + deviceYDpiText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + if (mYDpi != 0f) { + deviceYDpiText.setText(String.format("%.1f", mYDpi)); //$NON-NLS-1$ + } + deviceYDpiText.addVerifyListener(floatVerifier); + deviceYDpiText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mYDpi = Float.parseFloat(deviceYDpiText.getText()); + } + }); + + Group configGroup = new Group(parent, SWT.NONE); + configGroup.setText("Configuration"); + configGroup.setLayoutData(new GridData(GridData.FILL_BOTH)); + configGroup.setLayout(new GridLayout(2, false)); + + l = new Label(configGroup, SWT.None); + l.setText("Name"); + + final Text configNameText = new Text(configGroup, SWT.BORDER); + configNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + if (mConfigName != null) { + configNameText.setText(mConfigName); + } + configNameText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mConfigName = configNameText.getText().trim(); + validateOk(); + } + }); + + mConfigSelector = new ConfigurationSelector(configGroup); + // configure the selector to be in "device mode" and not accept language/region/version + // since those are selected from a different combo + // FIXME: add version combo. + mConfigSelector.setDeviceMode(true); + mConfigSelector.setQualifierFilter(new IQualifierFilter() { + public boolean accept(ResourceQualifier qualifier) { + if (qualifier instanceof LanguageQualifier || + qualifier instanceof RegionQualifier || + qualifier instanceof VersionQualifier) { + return false; + } + + return true; + } + }); + mConfigSelector.setConfiguration(mConfig); + GridData gd; + mConfigSelector.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 2; + gd.widthHint = ConfigurationSelector.WIDTH_HINT; + gd.heightHint = ConfigurationSelector.HEIGHT_HINT; + + // add a listener to check on the validity of the FolderConfiguration as + // they are built. + mConfigSelector.setOnChangeListener(new Runnable() { + public void run() { + if (mConfigSelector.getState() == ConfigurationState.OK) { + mConfigSelector.getConfiguration(mConfig); + } + + validateOk(); + } + }); + + mStatusComposite = new Composite(parent, SWT.NONE); + mStatusComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + GridLayout gl = new GridLayout(2, false); + mStatusComposite.setLayout(gl); + gl.marginHeight = gl.marginWidth = 0; + + mStatusImage = new Label(mStatusComposite, SWT.NONE); + mStatusLabel = new Label(mStatusComposite, SWT.NONE); + mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + resetStatus(); + } + + @Override + protected Control createContents(Composite parent) { + Control c = super.createContents(parent); + validateOk(); + return c; + } + + /** + * resets the status label to show the file that will be created. + */ + private void resetStatus() { + String displayString = Dialog.shortenText( + String.format("Config: %1$s", mConfig.toString()), + mStatusLabel); + mStatusLabel.setText(displayString); + } + + private void setError(String text) { + String displayString = Dialog.shortenText(text, mStatusLabel); + mStatusLabel.setText(displayString); + mStatusImage.setImage(mError); + getButton(IDialogConstants.OK_ID).setEnabled(false); + } + + private void validateOk() { + // check the device name + if (mDeviceName == null || mDeviceName.length() == 0) { + setError("Device name must not be empty"); + return; + } + + // check the config name + if (mConfigName == null || mConfigName.length() == 0) { + setError("Configuration name must not be empty"); + return; + } + + // and check the config itself + ConfigurationState state = mConfigSelector.getState(); + + switch (state) { + case INVALID_CONFIG: + ResourceQualifier invalidQualifier = mConfigSelector.getInvalidQualifier(); + setError(String.format( + "Invalid Configuration: %1$s has no filter set.", + invalidQualifier.getName())); + return; + case REGION_WITHOUT_LANGUAGE: + setError("The Region qualifier requires the Language qualifier."); + return; + } + + // no error + mStatusImage.setImage(null); + resetStatus(); + getButton(IDialogConstants.OK_ID).setEnabled(true); + + // need to relayout, because of the change in size in mErrorImage. + mStatusComposite.layout(); + } +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java new file mode 100644 index 000000000..6e003a2d2 --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java @@ -0,0 +1,552 @@ +/* + * 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.editors.layout.configuration; + +import com.android.ddmuilib.TableHelper; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; +import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdkuilib.ui.GridDialog; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +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.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; + +import java.util.Map; +import java.util.Map.Entry; + +/** + * Dialog to view the layout devices with action button to create/edit/delete/copy layout devices + * and configs. + * + */ +public class ConfigManagerDialog extends GridDialog { + + private final static String COL_NAME = AdtPlugin.PLUGIN_ID + ".configmanager.name"; //$NON-NLS-1$ + private final static String COL_CONFIG = AdtPlugin.PLUGIN_ID + ".configmanager.config"; //$NON-NLS-1$ + + /** + * enum to represent the different origin of the layout devices. + */ + private static enum DeviceType { + DEFAULT("Default"), + ADDON("Add-on"), + CUSTOM("Custom"); + + private final String mDisplay; + + DeviceType(String display) { + mDisplay = display; + } + + String getDisplayString() { + return mDisplay; + } + } + + /** + * simple class representing the tree selection with the proper types. + */ + private static class DeviceSelection { + public DeviceSelection(DeviceType type, LayoutDevice device, + Entry entry) { + this.type = type; + this.device = device; + this.entry = entry; + } + + final DeviceType type; + final LayoutDevice device; + final Entry entry; + } + + private final LayoutDeviceManager mManager; + + private TreeViewer mTreeViewer; + private Button mNewButton; + private Button mEditButton; + private Button mCopyButton; + private Button mDeleteButton; + + /** + * Content provider of the {@link TreeViewer}. The expected input is + * {@link LayoutDeviceManager}. + * + */ + private final static class DeviceContentProvider implements ITreeContentProvider { + private final static DeviceType[] sCategory = new DeviceType[] { + DeviceType.DEFAULT, DeviceType.ADDON, DeviceType.CUSTOM + }; + + private LayoutDeviceManager mLayoutDeviceManager; + + public DeviceContentProvider() { + } + + public Object[] getElements(Object inputElement) { + return sCategory; + } + + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof DeviceType) { + if (DeviceType.DEFAULT.equals(parentElement)) { + return mLayoutDeviceManager.getDefaultLayoutDevices().toArray(); + } else if (DeviceType.ADDON.equals(parentElement)) { + return mLayoutDeviceManager.getAddOnLayoutDevice().toArray(); + } else if (DeviceType.CUSTOM.equals(parentElement)) { + return mLayoutDeviceManager.getUserLayoutDevices().toArray(); + } + } else if (parentElement instanceof LayoutDevice) { + LayoutDevice device = (LayoutDevice)parentElement; + return device.getConfigs().entrySet().toArray(); + } + + return null; + } + + public Object getParent(Object element) { + // parent cannot be computed. this is fine. + return null; + } + + public boolean hasChildren(Object element) { + if (element instanceof DeviceType) { + if (DeviceType.DEFAULT.equals(element)) { + return mLayoutDeviceManager.getDefaultLayoutDevices().size() > 0; + } else if (DeviceType.ADDON.equals(element)) { + return mLayoutDeviceManager.getAddOnLayoutDevice().size() > 0; + } else if (DeviceType.CUSTOM.equals(element)) { + return mLayoutDeviceManager.getUserLayoutDevices().size() > 0; + } + } else if (element instanceof LayoutDevice) { + LayoutDevice device = (LayoutDevice)element; + return device.getConfigs().size() > 0; + } + + return false; + } + + + public void dispose() { + // nothing to dispose + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof LayoutDeviceManager) { + mLayoutDeviceManager = (LayoutDeviceManager)newInput; + return; + } + + // when the dialog closes we get null input + if (newInput != null) { + throw new IllegalArgumentException( + "ConfigContentProvider requires input to be LayoutDeviceManager"); + } + } + } + + /** + * Label provider for the {@link TreeViewer}. + * Supported elements are {@link DeviceType}, {@link LayoutDevice}, and {@link Entry} (where + * the key is a {@link String} object, and the value is a {@link FolderConfiguration} object). + * + */ + private final static class DeviceLabelProvider implements ITableLabelProvider { + + public String getColumnText(Object element, int columnIndex) { + if (element instanceof DeviceType) { + if (columnIndex == 0) { + return ((DeviceType)element).getDisplayString(); + } + } else if (element instanceof LayoutDevice) { + if (columnIndex == 0) { + return ((LayoutDevice)element).getName(); + } + } else if (element instanceof Entry) { + if (columnIndex == 0) { + return (String)((Entry)element).getKey(); + } else { + return ((Entry)element).getValue().toString(); + } + } + return null; + } + + public Image getColumnImage(Object element, int columnIndex) { + // no image + return null; + } + + public void addListener(ILabelProviderListener listener) { + // no listener + } + + public void removeListener(ILabelProviderListener listener) { + // no listener + } + + public void dispose() { + // nothing to dispose + } + + public boolean isLabelProperty(Object element, String property) { + return false; + } + } + + protected ConfigManagerDialog(Shell parentShell) { + super(parentShell, 2, false); + mManager = Sdk.getCurrent().getLayoutDeviceManager(); + } + + @Override + protected int getShellStyle() { + return super.getShellStyle() | SWT.RESIZE; + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Device Configurations"); + } + + @Override + public void createDialogContent(final Composite parent) { + GridData gd; + GridLayout gl; + + Tree tree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION); + tree.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); + gd.widthHint = 700; + + tree.setHeaderVisible(true); + tree.setLinesVisible(true); + TableHelper.createTreeColumn(tree, "Name", SWT.LEFT, 150, COL_NAME, + AdtPlugin.getDefault().getPreferenceStore()); + TableHelper.createTreeColumn(tree, "Configuration", SWT.LEFT, 500, COL_CONFIG, + AdtPlugin.getDefault().getPreferenceStore()); + + mTreeViewer = new TreeViewer(tree); + mTreeViewer.setContentProvider(new DeviceContentProvider()); + mTreeViewer.setLabelProvider(new DeviceLabelProvider()); + mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS); + mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + setEnabled(getSelection()); + } + }); + + Composite buttons = new Composite(parent, SWT.NONE); + buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + buttons.setLayout(gl = new GridLayout()); + gl.marginHeight = gl.marginWidth = 0; + + mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNewButton.setText("New..."); + mNewButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + DeviceSelection selection = getSelection(); + + ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null); + if (selection.device != null) { + dlg.setDeviceName(selection.device.getName()); + dlg.setXDpi(selection.device.getXDpi()); + dlg.setYDpi(selection.device.getYDpi()); + } + if (selection.entry != null) { + dlg.setConfigName(selection.entry.getKey()); + dlg.setConfig(selection.entry.getValue()); + } + + if (dlg.open() == Window.OK) { + String deviceName = dlg.getDeviceName(); + String configName = dlg.getConfigName(); + FolderConfiguration config = new FolderConfiguration(); + dlg.getConfig(config); + + // first if there was no original device, we create one. + // Because the new button is disabled when something else than "custom" is + // selected, we always add to the user devices without checking. + LayoutDevice d; + if (selection.device == null) { + // FIXME: this doesn't check if the device name is taken. + d = mManager.addUserDevice(deviceName, dlg.getXDpi(), dlg.getYDpi()); + } else { + // search for it. + d = mManager.getUserLayoutDevice(deviceName); + } + + if (d != null) { + // then if there was no config, we add it, otherwise we edit it + // (same method that adds/replace a config). + // FIXME this doesn't check if the name was already taken. + mManager.addUserConfiguration(d, configName, config); + + mTreeViewer.refresh(); + select(d, configName); + } + } + } + }); + + mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mEditButton.setText("Edit..."); + mEditButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + DeviceSelection selection = getSelection(); + ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null); + dlg.setDeviceName(selection.device.getName()); + dlg.setConfigName(selection.entry.getKey()); + dlg.setConfig(selection.entry.getValue()); + + if (dlg.open() == Window.OK) { + String deviceName = dlg.getDeviceName(); + String configName = dlg.getConfigName(); + FolderConfiguration config = new FolderConfiguration(); + dlg.getConfig(config); + + // replace the device if needed. + // FIXME: this doesn't check if the replacement name doesn't exist already. + LayoutDevice d = mManager.replaceUserDevice(selection.device, deviceName, + dlg.getXDpi(), dlg.getYDpi()); + + // and add/replace the config + mManager.replaceUserConfiguration(d, selection.entry.getKey(), configName, + config); + + mTreeViewer.refresh(); + select(d, configName); + } + } + }); + + mCopyButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mCopyButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mCopyButton.setText("Copy"); + mCopyButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + DeviceSelection selection = getSelection(); + + // is the source a default/add-on device, or are we copying a full device? + // if so the target device is a new device. + LayoutDevice targetDevice = selection.device; + if (selection.type == DeviceType.DEFAULT || selection.type == DeviceType.ADDON || + selection.entry == null) { + // create a new device + targetDevice = mManager.addUserDevice( + selection.device.getName() + " Copy", // new name + selection.device.getXDpi(), + selection.device.getYDpi()); + } + + String newConfigName = null; // name of the single new config. used for the select. + + // are we copying the full device? + if (selection.entry == null) { + // get the config from the origin device + Map configs = selection.device.getConfigs(); + + // and copy them in the target device + for (Entry entry : configs.entrySet()) { + // we need to make a copy of the config object, or it could be modified + // in default/addon by editing the version in the new device. + FolderConfiguration copy = new FolderConfiguration(); + copy.set(entry.getValue()); + + // the name can stay the same since we are copying a full device + // and the target device has its own new name. + mManager.addUserConfiguration(targetDevice, entry.getKey(), copy); + } + } else { + // only copy the config. target device is not the same as the selection, don't + // change the config name as we already changed the name of the device. + newConfigName = (selection.device != targetDevice) ? + selection.entry.getKey() : selection.entry.getKey() + " Copy"; + + // copy of the config + FolderConfiguration copy = new FolderConfiguration(); + copy.set(selection.entry.getValue()); + + // and create the config + mManager.addUserConfiguration(targetDevice, newConfigName, copy); + } + + mTreeViewer.refresh(); + + select(targetDevice, newConfigName); + } + }); + + mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mDeleteButton.setText("Delete"); + mDeleteButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + DeviceSelection selection = getSelection(); + + if (selection.entry != null) { + mManager.removeUserConfiguration(selection.device, selection.entry.getKey()); + } else if (selection.device != null) { + mManager.removeUserDevice(selection.device); + } + + mTreeViewer.refresh(); + + // either select the device (if we removed a entry, or the top custom node if + // we removed a device) + select(selection.entry != null ? selection.device : null, null); + } + }); + + Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); + separator.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 2; + + mTreeViewer.setInput(mManager); + setEnabled(null); // no selection at the start + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + // we only want an OK button. + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + } + + /** + * Returns a {@link DeviceSelection} object representing the selected path in the + * {@link TreeViewer} + */ + private DeviceSelection getSelection() { + // get the selection paths + TreeSelection selection = (TreeSelection)mTreeViewer.getSelection(); + TreePath[] paths =selection.getPaths(); + + if (paths.length == 0) { + return null; + } + + TreePath pathSelection = paths[0]; + + DeviceType type = (DeviceType)pathSelection.getFirstSegment(); + LayoutDevice device = null; + Entry entry = null; + switch (pathSelection.getSegmentCount()) { + case 2: // layout device is selected + device = (LayoutDevice)pathSelection.getLastSegment(); + break; + case 3: // config is selected + device = (LayoutDevice)pathSelection.getSegment(1); + entry = (Entry)pathSelection.getLastSegment(); + } + + return new DeviceSelection(type, device, entry); + } + + /** + * Enables/disables the action button based on the {@link DeviceSelection}. + * @param selection the selection + */ + protected void setEnabled(DeviceSelection selection) { + if (selection == null) { + mNewButton.setEnabled(false); + mEditButton.setEnabled(false); + mCopyButton.setEnabled(false); + mDeleteButton.setEnabled(false); + } else { + switch (selection.type) { + case DEFAULT: + case ADDON: + // only allow copy if device is not null + mNewButton.setEnabled(false); + mEditButton.setEnabled(false); + mDeleteButton.setEnabled(false); + mCopyButton.setEnabled(selection.device != null); + break; + case CUSTOM: + mNewButton.setEnabled(true); // always true to create new devices. + mEditButton.setEnabled(selection.entry != null); // only edit config for now + + boolean enabled = selection.device != null; // need at least selected device + mDeleteButton.setEnabled(enabled); // for delete and copy buttons + mCopyButton.setEnabled(enabled); + break; + } + } + } + + /** + * Selects a device and optionally a config. Because this is meant to show newly created/edited + * device/config, it'll only do so for {@link DeviceType#CUSTOM} devices. + * @param device the device to select + * @param configName the config to select (optional) + */ + private void select(LayoutDevice device, String configName) { + Object[] path; + if (device == null) { + // select the "custom" node + path = new Object[] { DeviceType.CUSTOM }; + } else if (configName == null) { + // this is the easy case. no config to select + path = new Object[] { DeviceType.CUSTOM, device }; + } else { + // this is more complex. we have the configName, but the tree contains Entry + // Look for the entry. + Entry match = null; + for (Entry entry : device.getConfigs().entrySet()) { + if (entry.getKey().equals(configName)) { + match = entry; + break; + } + } + + if (match != null) { + path = new Object[] { DeviceType.CUSTOM, device, match }; + } else { + path = new Object[] { DeviceType.CUSTOM, device }; + } + } + + mTreeViewer.setSelection(new TreeSelection(new TreePath(path)), true /*reveal*/); + } +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java index 598b11d62..8c37cc6b9 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java @@ -29,7 +29,8 @@ import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQual import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density; import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier.ScreenOrientation; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.sdk.DeviceConfiguration; +import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; +import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.LanguageRegionVerifier; import com.android.layoutlib.api.IResourceValue; @@ -77,7 +78,7 @@ public class ConfigurationComposite extends Composite { /** The {@link FolderConfiguration} representing the state of the UI controls */ private final FolderConfiguration mCurrentConfig = new FolderConfiguration(); - private List mDevices; + private List mDevices; private final ArrayList mLocaleList = new ArrayList(); @@ -86,7 +87,7 @@ public class ConfigurationComposite extends Composite { private boolean mClipping = true; - private DeviceConfiguration mCurrentDevice; + private LayoutDevice mCurrentDevice; /** * Interface implemented by the part which owns a {@link ConfigurationComposite}. @@ -109,9 +110,6 @@ public class ConfigurationComposite extends Composite { public ConfigurationComposite(IConfigListener listener, Composite parent, int style) { super(parent, style); mListener = listener; - if (Sdk.getCurrent() != null) { - mDevices = Sdk.getCurrent().getLayoutDevices(); - } GridLayout gl; GridData gd; @@ -512,15 +510,25 @@ public class ConfigurationComposite extends Composite { } /** - * Reloads the list of {@link DeviceConfiguration} from the {@link Sdk}. + * Reloads the list of {@link LayoutDevice} from the {@link Sdk}. * @param notifyListener */ public void reloadDevices(boolean notifyListener) { - mDevices = Sdk.getCurrent().getLayoutDevices(); + loadDevices(); initUiWithDevices(); onDeviceChange(notifyListener); } + private void loadDevices() { + mDevices = null; + + Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + LayoutDeviceManager manager = sdk.getLayoutDeviceManager(); + mDevices = manager.getCombinedList(); + } + } + /** * Init the UI with the list of Devices. */ @@ -531,7 +539,7 @@ public class ConfigurationComposite extends Composite { // fill with the devices if (mDevices != null) { - for (DeviceConfiguration device : mDevices) { + for (LayoutDevice device : mDevices) { mDeviceList.add(device.getName()); } mDeviceList.select(0); @@ -548,6 +556,9 @@ public class ConfigurationComposite extends Composite { } } } + + // add the custom item + mDeviceList.add("Custom..."); } /** @@ -575,6 +586,31 @@ public class ConfigurationComposite extends Composite { int deviceIndex = mDeviceList.getSelectionIndex(); if (deviceIndex != -1) { + // check if the user is ask for the custom item + if (deviceIndex == mDeviceList.getItemCount() - 1) { + ConfigManagerDialog dialog = new ConfigManagerDialog(getShell()); + dialog.open(); + + // reload the combo with the new content. + loadDevices(); + initUiWithDevices(); + + // at this point we need to reset the combo to something (hopefully) valid. + // look for the previous selected device + int index = mDevices.indexOf(mCurrentDevice); + if (index != -1) { + mDeviceList.select(index); + } else { + // we should at least have one built-in device, so we select it + mDeviceList.select(0); + } + + // force a redraw + onDeviceChange(true /*recomputeLayout*/); + + return; + } + mCurrentDevice = mDevices.get(deviceIndex); } else { mCurrentDevice = null; diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCreatorDialog.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java similarity index 86% rename from tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCreatorDialog.java rename to tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java index a55f1d056..eb8c5da15 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutCreatorDialog.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.layout; +package com.android.ide.eclipse.adt.internal.editors.layout.configuration; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; @@ -23,21 +23,21 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; import com.android.sdklib.IAndroidTarget; +import com.android.sdkuilib.ui.GridDialog; +import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.TrayDialog; import org.eclipse.swt.SWT; 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.Label; import org.eclipse.swt.widgets.Shell; /** * Dialog to choose a non existing {@link FolderConfiguration}. */ -class LayoutCreatorDialog extends TrayDialog { +public final class LayoutCreatorDialog extends GridDialog { private ConfigurationSelector mSelector; private Composite mStatusComposite; @@ -53,9 +53,9 @@ class LayoutCreatorDialog extends TrayDialog { * @param parentShell the parent {@link Shell}. * @param config The starting configuration. */ - LayoutCreatorDialog(Shell parentShell, String fileName, IAndroidTarget target, + public LayoutCreatorDialog(Shell parentShell, String fileName, IAndroidTarget target, FolderConfiguration config) { - super(parentShell); + super(parentShell, 1, false); mFileName = fileName; mTarget = target; @@ -65,15 +65,11 @@ class LayoutCreatorDialog extends TrayDialog { } @Override - protected Control createDialogArea(Composite parent) { - Composite top = new Composite(parent, SWT.NONE); - top.setLayoutData(new GridData()); - top.setLayout(new GridLayout(1, false)); - - new Label(top, SWT.NONE).setText( + public void createDialogContent(Composite parent) { + new Label(parent, SWT.NONE).setText( String.format("Configuration for the alternate version of %1$s", mFileName)); - mSelector = new ConfigurationSelector(top); + mSelector = new ConfigurationSelector(parent); mSelector.setConfiguration(mConfig); // parent's layout is a GridLayout as specified in the javadoc. @@ -117,7 +113,7 @@ class LayoutCreatorDialog extends TrayDialog { } }); - mStatusComposite = new Composite(top, SWT.NONE); + mStatusComposite = new Composite(parent, SWT.NONE); mStatusComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); GridLayout gl = new GridLayout(2, false); mStatusComposite.setLayout(gl); @@ -127,8 +123,6 @@ class LayoutCreatorDialog extends TrayDialog { mStatusLabel = new Label(mStatusComposite, SWT.NONE); mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); resetStatus(); - - return top; } public void getConfiguration(FolderConfiguration config) { @@ -139,7 +133,9 @@ class LayoutCreatorDialog extends TrayDialog { * resets the status label to show the file that will be created. */ private void resetStatus() { - mStatusLabel.setText(String.format("New File: res/%1$s/%2$s", - mConfig.getFolderName(ResourceFolderType.LAYOUT, mTarget), mFileName)); + String displayString = Dialog.shortenText(String.format("New File: res/%1$s/%2$s", + mConfig.getFolderName(ResourceFolderType.LAYOUT, mTarget), mFileName), + mStatusLabel); + mStatusLabel.setText(displayString); } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java index ae86f48eb..f1ac8cf20 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java @@ -60,8 +60,10 @@ public final class FolderConfiguration implements Comparable mMap = - new HashMap(); - private float mXDpi = Float.NaN; - private float mYDpi = Float.NaN; - - DeviceConfiguration(String name) { - mName = name; - } - - void addConfig(String name, FolderConfiguration config) { - mMap.put(name, config); - } - - void seal() { - mMap = Collections.unmodifiableMap(mMap); - } - - void setXDpi(float xdpi) { - mXDpi = xdpi; - } - - void setYDpi(float ydpi) { - mYDpi = ydpi; - } - - public String getName() { - return mName; - } - - public Map getConfigs() { - return mMap; - } - - /** - * Returns the dpi of the Device screen in X. - * @return the dpi of screen or {@link Float#NaN} if it's not set. - */ - public float getXDpi() { - return mXDpi; - } - - /** - * Returns the dpi of the Device screen in Y. - * @return the dpi of screen or {@link Float#NaN} if it's not set. - */ - public float getYDpi() { - return mYDpi; - } - } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java new file mode 100644 index 000000000..89cac5bae --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java @@ -0,0 +1,116 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Class representing a layout device. + * + * A Layout device is a collection of {@link FolderConfiguration} that can be used to render Android + * layout files. + * + * It also contains a single xdpi/ydpi that is independent of the {@link FolderConfiguration}. + * + * If the device is meant to represent a true device, then most of the FolderConfigurations' content + * should be identical, with only a few qualifiers (orientation, keyboard state) that would differ. + * However it is simpler to reuse the FolderConfiguration class (with the non changing qualifiers + * duplicated in each configuration) as it's what's being used by the rendering library. + * + * To create, edit and delete LayoutDevice objects, see {@link LayoutDeviceManager}. + * The class is not technically immutable but behaves as such outside of its package. + */ +public class LayoutDevice { + + private final String mName; + + /** editable map of the config */ + private Map mEditMap = new HashMap(); + /** unmodifiable map returned by {@link #getConfigs()}. */ + private Map mMap; + private float mXDpi = Float.NaN; + private float mYDpi = Float.NaN; + + LayoutDevice(String name) { + mName = name; + } + + void addConfig(String name, FolderConfiguration config) { + mEditMap.put(name, config); + _seal(); + } + + void addConfigs(Map configs) { + mEditMap.putAll(configs); + _seal(); + } + + void removeConfig(String name) { + mEditMap.remove(name); + _seal(); + } + + /** + * Adds config to the LayoutDevice. This is to be used to add plenty of configurations. + * It must be followed by {@link #_seal()}. + * @param name the name of the config + * @param config the config. + */ + void _addConfig(String name, FolderConfiguration config) { + mEditMap.put(name, config); + } + + void _seal() { + mMap = Collections.unmodifiableMap(mEditMap); + } + + void setXDpi(float xdpi) { + mXDpi = xdpi; + } + + void setYDpi(float ydpi) { + mYDpi = ydpi; + } + + public String getName() { + return mName; + } + + public Map getConfigs() { + return mMap; + } + + /** + * Returns the dpi of the Device screen in X. + * @return the dpi of screen or {@link Float#NaN} if it's not set. + */ + public float getXDpi() { + return mXDpi; + } + + /** + * Returns the dpi of the Device screen in Y. + * @return the dpi of screen or {@link Float#NaN} if it's not set. + */ + public float getYDpi() { + return mYDpi; + } + } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java index 71d7f1866..36932bf71 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java @@ -58,16 +58,16 @@ class LayoutDeviceHandler extends DefaultHandler { * on the endElement, by using the content found in characters(). */ - private List mDevices = new ArrayList(); + private List mDevices = new ArrayList(); - private DeviceConfiguration mCurrentDevice; + private LayoutDevice mCurrentDevice; private FolderConfiguration mDefaultConfig; private FolderConfiguration mCurrentConfig; private final StringBuilder mStringAccumulator = new StringBuilder(); private String mSize1, mSize2; - public List getDevices() { + public List getDevices() { return mDevices; } @@ -79,7 +79,7 @@ class LayoutDeviceHandler extends DefaultHandler { String deviceName = attributes.getValue("", LayoutConfigsXsd.ATTR_NAME); // create a device and add it to the list - mCurrentDevice = new DeviceConfiguration(deviceName); + mCurrentDevice = new LayoutDevice(deviceName); mDevices.add(mCurrentDevice); } else if (LayoutConfigsXsd.NODE_DEFAULT.equals(localName)) { // create a new default config diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java new file mode 100644 index 000000000..03daf401d --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java @@ -0,0 +1,310 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; +import com.android.sdklib.SdkConstants; + +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Validator; + +/** + * Manages the layout devices. + * They can come from 3 sources: built-in, add-ons, user. + */ +public class LayoutDeviceManager { + + /** + * A SAX error handler that captures the errors and warnings. + * This allows us to capture *all* errors and just not get an exception on the first one. + */ + private static class CaptureErrorHandler implements ErrorHandler { + + private final String mSourceLocation; + + private boolean mFoundError = false; + + CaptureErrorHandler(String sourceLocation) { + mSourceLocation = sourceLocation; + } + + public boolean foundError() { + return mFoundError; + } + + /** + * @throws SAXException + */ + public void error(SAXParseException ex) throws SAXException { + mFoundError = true; + AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation); + } + + /** + * @throws SAXException + */ + public void fatalError(SAXParseException ex) throws SAXException { + mFoundError = true; + AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation); + } + + /** + * @throws SAXException + */ + public void warning(SAXParseException ex) throws SAXException { + // ignore those for now. + } + } + + private final SAXParserFactory mParserFactory; + + private List mDefaultLayoutDevices = + new ArrayList(); + private List mAddOnLayoutDevices = + new ArrayList(); + private final List mUserLayoutDevices = + new ArrayList(); + private List mLayoutDevices; + + LayoutDeviceManager() { + mParserFactory = SAXParserFactory.newInstance(); + mParserFactory.setNamespaceAware(true); + } + + public List getCombinedList() { + return mLayoutDevices; + } + + public List getDefaultLayoutDevices() { + return mDefaultLayoutDevices; + } + + public List getAddOnLayoutDevice() { + return mAddOnLayoutDevices; + } + + public List getUserLayoutDevices() { + return mUserLayoutDevices; + } + + public LayoutDevice getUserLayoutDevice(String name) { + for (LayoutDevice d : mUserLayoutDevices) { + if (d.getName().equals(name)) { + return d; + } + } + + return null; + } + + public LayoutDevice addUserDevice(String name, float xdpi, float ydpi) { + LayoutDevice d = new LayoutDevice(name); + d.setXDpi(xdpi); + d.setYDpi(ydpi); + mUserLayoutDevices.add(d); + combineLayoutDevices(); + + return d; + } + + public void removeUserDevice(LayoutDevice device) { + if (mUserLayoutDevices.remove(device)) { + combineLayoutDevices(); + } + } + + /** + * Replaces a device with a new one with new name and/or x/y dpi, and return the new device. + * If the name and dpi values are identical the given device is returned an nothing is done + * @param device the {@link LayoutDevice} to replace + * @param newName the new name. + * @param newXDpi the new X dpi value + * @param newYDpi the new Y dpi value. + * @return the new LayoutDevice + */ + public LayoutDevice replaceUserDevice(LayoutDevice device, String newName, + float newXDpi, float newYDpi) { + if (device.getName().equals(newName) && device.getXDpi() == newXDpi && + device.getYDpi() == newYDpi) { + return device; + } + + // else create a new device + LayoutDevice newDevice = new LayoutDevice(newName); + newDevice.setXDpi(newXDpi); + newDevice.setYDpi(newYDpi); + + // and get the Folderconfiguration + Map configs = device.getConfigs(); + newDevice.addConfigs(configs); + + // replace the old device with the new + mUserLayoutDevices.remove(device); + mUserLayoutDevices.add(newDevice); + combineLayoutDevices(); + + return newDevice; + } + + + /** + * Adds or replaces a configuration in a given {@link LayoutDevice}. + * @param device the device to modify + * @param configName the configuration name to add or replace + * @param config the configuration to set + */ + public void addUserConfiguration(LayoutDevice device, String configName, + FolderConfiguration config) { + // check that the device does belong to the user list. + // the main goal is to make sure that this does not belong to the default/addon list. + if (mUserLayoutDevices.contains(device)) { + device.addConfig(configName, config); + } + } + + /** + * Replaces a configuration in a given {@link LayoutDevice}. + * @param device the device to modify + * @param oldConfigName the name of the config to replace. If null, the new config is simply + * added. + * @param newConfigName the configuration name to add or replace + * @param config the configuration to set + */ + public void replaceUserConfiguration(LayoutDevice device, String oldConfigName, + String newConfigName, FolderConfiguration config) { + // check that the device does belong to the user list. + // the main goal is to make sure that this does not belong to the default/addon list. + if (mUserLayoutDevices.contains(device)) { + // if the old and new config name are different, remove the old one + if (oldConfigName != null && oldConfigName.equals(newConfigName) == false) { + device.removeConfig(oldConfigName); + } + + // and then add the new one + device.addConfig(newConfigName, config); + } + } + + /** + * Removes a configuration from a given user {@link LayoutDevice} + * @param device the device to modify + * @param configName the name of the config to remove + */ + public void removeUserConfiguration(LayoutDevice device, String configName) { + // check that the device does belong to the user list. + // the main goal is to make sure that this does not belong to the default/addon list. + if (mUserLayoutDevices.contains(device)) { + device.removeConfig(configName); + } + } + + void load(String sdkOsLocation) { + // load the default devices + loadDefaultLayoutDevices(sdkOsLocation); + + // load the user devices; + } + + void parseAddOnLayoutDevice(File deviceXml) { + parseLayoutDevices(deviceXml, mAddOnLayoutDevices); + } + + void sealAddonLayoutDevices() { + mAddOnLayoutDevices = Collections.unmodifiableList(mAddOnLayoutDevices); + + combineLayoutDevices(); + } + + /** + * Does the actual parsing of a devices.xml file. + */ + private void parseLayoutDevices(File deviceXml, List list) { + // first we validate the XML + try { + Source source = new StreamSource(new FileReader(deviceXml)); + + CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath()); + + Validator validator = LayoutConfigsXsd.getValidator(errorHandler); + validator.validate(source); + + if (errorHandler.foundError() == false) { + // do the actual parsing + LayoutDeviceHandler handler = new LayoutDeviceHandler(); + + SAXParser parser = mParserFactory.newSAXParser(); + parser.parse(new InputSource(new FileInputStream(deviceXml)), handler); + + // get the parsed devices + list.addAll(handler.getDevices()); + } + } catch (SAXException e) { + AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile()); + } catch (FileNotFoundException e) { + // this shouldn't happen as we check above. + } catch (IOException e) { + AdtPlugin.log(e, "Error reading %1$s", deviceXml.getAbsoluteFile()); + } catch (ParserConfigurationException e) { + AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile()); + } + } + + /** + * Creates some built-it layout devices. + */ + private void loadDefaultLayoutDevices(String sdkOsLocation) { + ArrayList list = new ArrayList(); + File toolsFolder = new File(sdkOsLocation, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER); + if (toolsFolder.isDirectory()) { + File deviceXml = new File(toolsFolder, SdkConstants.FN_DEVICES_XML); + if (deviceXml.isFile()) { + parseLayoutDevices(deviceXml, list); + } + } + mDefaultLayoutDevices = Collections.unmodifiableList(list); + } + + private void combineLayoutDevices() { + ArrayList list = new ArrayList(); + list.addAll(mDefaultLayoutDevices); + list.addAll(mAddOnLayoutDevices); + list.addAll(mUserLayoutDevices); + + mLayoutDevices = Collections.unmodifiableList(list); + } + +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java index 9fd90feb0..6a8bd5bf5 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java @@ -41,31 +41,15 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; -import org.xml.sax.ErrorHandler; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Validator; - /** * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used * at the same time. @@ -89,7 +73,7 @@ public class Sdk implements IProjectListener { new HashMap(); private final String mDocBaseUrl; - private List mLayoutDevices = new ArrayList(); + private final LayoutDeviceManager mLayoutDeviceManager = new LayoutDeviceManager(); /** * Classes implementing this interface will receive notification when targets are changed. @@ -439,6 +423,10 @@ public class Sdk implements IProjectListener { } } + public LayoutDeviceManager getLayoutDeviceManager() { + return mLayoutDeviceManager; + } + private Sdk(SdkManager manager, AvdManager avdManager) { mManager = manager; mAvdManager = avdManager; @@ -451,8 +439,10 @@ public class Sdk implements IProjectListener { mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + SdkConstants.OS_SDK_DOCS_FOLDER); - // load the built-in layout devices - loadDefaultLayoutDevices(); + // load the built-in and user layout devices + mLayoutDeviceManager.load(mManager.getLocation()); + // and the ones from the add-on + loadLayoutDevices(); } /** @@ -505,6 +495,24 @@ public class Sdk implements IProjectListener { return null; } + /** + * Parses the SDK add-ons to look for files called {@link SdkConstants#FN_DEVICES_XML} to + * load {@link LayoutDevice} from them. + */ + private void loadLayoutDevices() { + IAndroidTarget[] targets = mManager.getTargets(); + for (IAndroidTarget target : targets) { + if (target.isPlatform() == false) { + File deviceXml = new File(target.getLocation(), SdkConstants.FN_DEVICES_XML); + if (deviceXml.isFile()) { + mLayoutDeviceManager.parseAddOnLayoutDevice(deviceXml); + } + } + } + + mLayoutDeviceManager.sealAddonLayoutDevices(); + } + public void projectClosed(IProject project) { // get the target project synchronized (AdtPlugin.getDefault().getSdkLockObject()) { @@ -539,128 +547,5 @@ public class Sdk implements IProjectListener { // ignore this. The project will be added to the map the first time the target needs // to be resolved. } - - - // ---------- Device Configuration methods ---------- - - /** - * A SAX error handler that captures the errors and warnings. - * This allows us to capture *all* errors and just not get an exception on the first one. - */ - private static class CaptureErrorHandler implements ErrorHandler { - - private final String mSourceLocation; - - private boolean mFoundError = false; - - CaptureErrorHandler(String sourceLocation) { - mSourceLocation = sourceLocation; - } - - public boolean foundError() { - return mFoundError; - } - - /** - * @throws SAXException - */ - public void error(SAXParseException ex) throws SAXException { - mFoundError = true; - AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation); - } - - /** - * @throws SAXException - */ - public void fatalError(SAXParseException ex) throws SAXException { - mFoundError = true; - AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation); - } - - /** - * @throws SAXException - */ - public void warning(SAXParseException ex) throws SAXException { - // ignore those for now. - } - } - - /** - * Returns the list of {@link DeviceConfiguration} found in the SDK. - */ - public List getLayoutDevices() { - return mLayoutDevices; - } - - /** - * Parses the SDK add-ons to look for files called {@link SdkConstants#FN_DEVICES_XML} to - * load {@link DeviceConfiguration} from them. - */ - public void parseAddOnLayoutDevices() { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - parserFactory.setNamespaceAware(true); - - IAndroidTarget[] targets = mManager.getTargets(); - for (IAndroidTarget target : targets) { - if (target.isPlatform() == false) { - File deviceXml = new File(target.getLocation(), SdkConstants.FN_DEVICES_XML); - if (deviceXml.isFile()) { - parseLayoutDevices(parserFactory, deviceXml); - } - } - } - - mLayoutDevices = Collections.unmodifiableList(mLayoutDevices); - } - - /** - * Does the actual parsing of a devices.xml file. - */ - private void parseLayoutDevices(SAXParserFactory parserFactory, File deviceXml) { - // first we validate the XML - try { - Source source = new StreamSource(new FileReader(deviceXml)); - - CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath()); - - Validator validator = LayoutConfigsXsd.getValidator(errorHandler); - validator.validate(source); - - if (errorHandler.foundError() == false) { - // do the actual parsing - LayoutDeviceHandler handler = new LayoutDeviceHandler(); - - SAXParser parser = parserFactory.newSAXParser(); - parser.parse(new InputSource(new FileInputStream(deviceXml)), handler); - - // get the parsed devices - mLayoutDevices.addAll(handler.getDevices()); - } - } catch (SAXException e) { - AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile()); - } catch (FileNotFoundException e) { - // this shouldn't happen as we check above. - } catch (IOException e) { - AdtPlugin.log(e, "Error reading %1$s", deviceXml.getAbsoluteFile()); - } catch (ParserConfigurationException e) { - AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile()); - } - } - - /** - * Creates some built-it layout devices. - */ - private void loadDefaultLayoutDevices() { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - parserFactory.setNamespaceAware(true); - - File toolsFolder = new File(mManager.getLocation(), SdkConstants.OS_SDK_TOOLS_LIB_FOLDER); - if (toolsFolder.isDirectory()) { - File deviceXml = new File(toolsFolder, SdkConstants.FN_DEVICES_XML); - if (deviceXml.isFile()) { - parseLayoutDevices(parserFactory, deviceXml); - } - } - } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java index 358b6fdfd..9e3acec9e 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java @@ -77,6 +77,7 @@ 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.HashMap; /** @@ -109,6 +110,8 @@ public class ConfigurationSelector extends Composite { private final HashMap, QualifierEditBase> mUiMap = new HashMap, QualifierEditBase>(); private Composite mQualifierEditParent; + private boolean mDeviceMode = false; + private IQualifierFilter mQualifierFilter; /** * Basic of {@link VerifyListener} to only accept digits. @@ -184,6 +187,17 @@ public class ConfigurationSelector extends Composite { OK, INVALID_CONFIG, REGION_WITHOUT_LANGUAGE; } + /** + * A filter for {@link ResourceQualifier}. + * @see ConfigurationSelector#setQualifierFilter(IQualifierFilter) + */ + public interface IQualifierFilter { + /** + * Returns true of the qualifier is accepted. + */ + boolean accept(ResourceQualifier qualifier); + } + public ConfigurationSelector(Composite parent) { super(parent, SWT.NONE); @@ -374,6 +388,28 @@ public class ConfigurationSelector extends Composite { mUiMap.put(VersionQualifier.class, new VersionEdit(mQualifierEditParent)); } + /** + * Sets the device mode. If true then the configuration selector only + * allows to create configuration that are valid on a device (as opposed to resource + * configuration). + * For instance {@link Density#NODPI} is a valid qualifier for a resource configuration but + * this is not valid on a device. + * Default is false. + * @param deviceMode the device mode. + */ + public void setDeviceMode(boolean deviceMode) { + mDeviceMode = deviceMode; + } + + /** + * 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 run() method is called when the @@ -492,7 +528,7 @@ public class ConfigurationSelector extends Composite { /** * Content provider around a {@link FolderConfiguration}. */ - private static class QualifierContentProvider implements IStructuredContentProvider { + private class QualifierContentProvider implements IStructuredContentProvider { private FolderConfiguration mInput; @@ -504,7 +540,20 @@ public class ConfigurationSelector extends Composite { } public Object[] getElements(Object inputElement) { - return mInput.getQualifiers(); + // default easy case + if (mQualifierFilter == null) { + return mInput.getQualifiers(); + } + + // in this case we have to compute the list + ArrayList list = new ArrayList(); + for (ResourceQualifier qual : mInput.getQualifiers()) { + if (mQualifierFilter.accept(qual)) { + list.add(qual); + } + } + + return list.toArray(); } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { @@ -1043,7 +1092,9 @@ public class ConfigurationSelector extends Composite { mDensity = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); Density[] soValues = Density.values(); for (Density value : soValues) { - mDensity.add(value.getDisplayValue()); + if (mDeviceMode == false || value != Density.NODPI) { + mDensity.add(value.getDisplayValue()); + } } mDensity.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); @@ -1055,7 +1106,6 @@ public class ConfigurationSelector extends Composite { onDensityChange(); } }); - } private void onDensityChange() {