blob: 61d870bea5cfd16fbb6bb4c263cc204f7d9926ec [file] [log] [blame]
/*
* 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.setXDpi(selection.device.getXDpi());
dlg.setYDpi(selection.device.getYDpi());
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*/);
}
}