am e19c0298: Merge change Idc447b41 into eclair

Merge commit 'e19c02984fd10c9fa35b036aafc1181cb8384069' into eclair-mr2

* commit 'e19c02984fd10c9fa35b036aafc1181cb8384069':
  UI to manage/create custom Layout Devices.
This commit is contained in:
Xavier Ducrohet
2009-10-14 07:37:28 -07:00
committed by Android Git Automerger
17 changed files with 1478 additions and 277 deletions

View File

@@ -146,4 +146,58 @@ public final class TableHelper {
}
}
/**
* 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 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);
}
});
}
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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<String, FolderConfiguration> entry) {
this.type = type;
this.device = device;
this.entry = entry;
}
final DeviceType type;
final LayoutDevice device;
final Entry<String, FolderConfiguration> 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<String, FolderConfiguration> configs = selection.device.getConfigs();
// and copy them in the target device
for (Entry<String, FolderConfiguration> 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<String, FolderConfiguration> 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<String, FolderConfiguration>)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*/);
}
}

View File

@@ -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<DeviceConfiguration> mDevices;
private List<LayoutDevice> mDevices;
private final ArrayList<ResourceQualifier[] > mLocaleList =
new ArrayList<ResourceQualifier[]>();
@@ -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;

View File

@@ -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);
}
}

View File

@@ -60,8 +60,10 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
* @param config
*/
public void set(FolderConfiguration config) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
mQualifiers[i] = config.mQualifiers[i];
if (config != null) {
for (int i = 0 ; i < INDEX_COUNT ; i++) {
mQualifiers[i] = config.mQualifiers[i];
}
}
}

View File

@@ -86,7 +86,6 @@ public class AndroidTargetData {
private LayoutBridge mLayoutBridge;
private boolean mLayoutBridgeInit = false;
private DeviceConfiguration[] mDevices;
AndroidTargetData(IAndroidTarget androidTarget) {
mTarget = androidTarget;
@@ -114,7 +113,6 @@ public class AndroidTargetData {
String[] intentCategoryValues,
String[] platformLibraries,
IOptionalLibrary[] optionalLibraries,
DeviceConfiguration[] devices,
ProjectResources resources,
LayoutBridge layoutBridge) {
@@ -124,7 +122,6 @@ public class AndroidTargetData {
mMenuDescriptors = menuDescriptors;
mXmlDescriptors = xmlDescriptors;
mEnumValueMap = enumValueMap;
mDevices = devices;
mFrameworkResources = resources;
mLayoutBridge = layoutBridge;
@@ -276,10 +273,6 @@ public class AndroidTargetData {
return mLayoutBridge;
}
public DeviceConfiguration[] getDevices() {
return mDevices;
}
/**
* Sets the permission values
* @param permissionValues the list of permissions

View File

@@ -254,9 +254,6 @@ public final class AndroidTargetParser {
LayoutBridge layoutBridge = loadLayoutBridge();
progress.worked(1);
// get the devices
DeviceConfiguration[] devices = getDevices();
// and finally create the PlatformData with all that we loaded.
targetData.setExtraData(frameworkRepository,
manifestDescriptors,
@@ -271,7 +268,6 @@ public final class AndroidTargetParser {
categories.toArray(new String[categories.size()]),
mAndroidTarget.getPlatformLibraries(),
mAndroidTarget.getOptionalLibraries(),
devices,
resources,
layoutBridge);
@@ -705,10 +701,4 @@ public final class AndroidTargetParser {
return layoutBridge;
}
private DeviceConfiguration[] getDevices() {
// TODO: load this from the target.
return null;
}
}

View File

@@ -1,77 +0,0 @@
/*
* 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;
public class DeviceConfiguration {
private final String mName;
private Map<String, FolderConfiguration> mMap =
new HashMap<String, FolderConfiguration>();
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<String, FolderConfiguration> 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;
}
}

View File

@@ -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<String, FolderConfiguration> mEditMap = new HashMap<String, FolderConfiguration>();
/** unmodifiable map returned by {@link #getConfigs()}. */
private Map<String, FolderConfiguration> 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<String, FolderConfiguration> 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<String, FolderConfiguration> 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;
}
}

View File

@@ -58,16 +58,16 @@ class LayoutDeviceHandler extends DefaultHandler {
* on the endElement, by using the content found in characters().
*/
private List<DeviceConfiguration> mDevices = new ArrayList<DeviceConfiguration>();
private List<LayoutDevice> mDevices = new ArrayList<LayoutDevice>();
private DeviceConfiguration mCurrentDevice;
private LayoutDevice mCurrentDevice;
private FolderConfiguration mDefaultConfig;
private FolderConfiguration mCurrentConfig;
private final StringBuilder mStringAccumulator = new StringBuilder();
private String mSize1, mSize2;
public List<DeviceConfiguration> getDevices() {
public List<LayoutDevice> 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

View File

@@ -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<LayoutDevice> mDefaultLayoutDevices =
new ArrayList<LayoutDevice>();
private List<LayoutDevice> mAddOnLayoutDevices =
new ArrayList<LayoutDevice>();
private final List<LayoutDevice> mUserLayoutDevices =
new ArrayList<LayoutDevice>();
private List<LayoutDevice> mLayoutDevices;
LayoutDeviceManager() {
mParserFactory = SAXParserFactory.newInstance();
mParserFactory.setNamespaceAware(true);
}
public List<LayoutDevice> getCombinedList() {
return mLayoutDevices;
}
public List<LayoutDevice> getDefaultLayoutDevices() {
return mDefaultLayoutDevices;
}
public List<LayoutDevice> getAddOnLayoutDevice() {
return mAddOnLayoutDevices;
}
public List<LayoutDevice> 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<String, FolderConfiguration> 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<LayoutDevice> 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<LayoutDevice> list = new ArrayList<LayoutDevice>();
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<LayoutDevice> list = new ArrayList<LayoutDevice>();
list.addAll(mDefaultLayoutDevices);
list.addAll(mAddOnLayoutDevices);
list.addAll(mUserLayoutDevices);
mLayoutDevices = Collections.unmodifiableList(list);
}
}

View File

@@ -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<IProject, ApkSettings>();
private final String mDocBaseUrl;
private List<DeviceConfiguration> mLayoutDevices = new ArrayList<DeviceConfiguration>();
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<DeviceConfiguration> 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);
}
}
}
}

View File

@@ -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<Class<? extends ResourceQualifier>, QualifierEditBase> mUiMap =
new HashMap<Class<? extends ResourceQualifier>, 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 <code>true</code> 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 <code>run()</code> 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<ResourceQualifier> list = new ArrayList<ResourceQualifier>();
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() {