Home | History | Annotate | Download | only in configuration
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
     18 
     19 import com.android.ddmuilib.TableHelper;
     20 import com.android.ide.common.resources.configuration.FolderConfiguration;
     21 import com.android.ide.eclipse.adt.AdtPlugin;
     22 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
     23 import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager;
     24 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     25 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
     26 import com.android.sdkuilib.ui.GridDialog;
     27 
     28 import org.eclipse.jface.dialogs.IDialogConstants;
     29 import org.eclipse.jface.viewers.ILabelProviderListener;
     30 import org.eclipse.jface.viewers.ISelectionChangedListener;
     31 import org.eclipse.jface.viewers.ITableLabelProvider;
     32 import org.eclipse.jface.viewers.ITreeContentProvider;
     33 import org.eclipse.jface.viewers.SelectionChangedEvent;
     34 import org.eclipse.jface.viewers.TreePath;
     35 import org.eclipse.jface.viewers.TreeSelection;
     36 import org.eclipse.jface.viewers.TreeViewer;
     37 import org.eclipse.jface.viewers.Viewer;
     38 import org.eclipse.jface.window.Window;
     39 import org.eclipse.swt.SWT;
     40 import org.eclipse.swt.events.SelectionAdapter;
     41 import org.eclipse.swt.events.SelectionEvent;
     42 import org.eclipse.swt.graphics.Image;
     43 import org.eclipse.swt.layout.GridData;
     44 import org.eclipse.swt.layout.GridLayout;
     45 import org.eclipse.swt.widgets.Button;
     46 import org.eclipse.swt.widgets.Composite;
     47 import org.eclipse.swt.widgets.Label;
     48 import org.eclipse.swt.widgets.Shell;
     49 import org.eclipse.swt.widgets.Tree;
     50 
     51 import java.util.List;
     52 
     53 /**
     54  * Dialog to view the layout devices with action button to create/edit/delete/copy layout devices
     55  * and configs.
     56  *
     57  */
     58 public class ConfigManagerDialog extends GridDialog {
     59 
     60     private final static String COL_NAME = AdtPlugin.PLUGIN_ID + ".configmanager.name"; //$NON-NLS-1$
     61     private final static String COL_CONFIG = AdtPlugin.PLUGIN_ID + ".configmanager.config"; //$NON-NLS-1$
     62 
     63     /**
     64      * enum to represent the different origin of the layout devices.
     65      */
     66     private static enum DeviceType {
     67         DEFAULT("Default"),
     68         ADDON("Add-on"),
     69         CUSTOM("Custom");
     70 
     71         private final String mDisplay;
     72 
     73         DeviceType(String display) {
     74             mDisplay = display;
     75         }
     76 
     77         String getDisplayString() {
     78             return mDisplay;
     79         }
     80     }
     81 
     82     /**
     83      * simple class representing the tree selection with the proper types.
     84      */
     85     private static class DeviceSelection {
     86         public DeviceSelection(DeviceType type, LayoutDevice device,
     87                 DeviceConfig config) {
     88             this.type = type;
     89             this.device = device;
     90             this.config = config;
     91         }
     92 
     93         final DeviceType type;
     94         final LayoutDevice device;
     95         final DeviceConfig config;
     96     }
     97 
     98     private final LayoutDeviceManager mManager;
     99 
    100     private TreeViewer mTreeViewer;
    101     private Button mNewButton;
    102     private Button mEditButton;
    103     private Button mCopyButton;
    104     private Button mDeleteButton;
    105 
    106     /**
    107      * Content provider of the {@link TreeViewer}. The expected input is
    108      * {@link LayoutDeviceManager}.
    109      *
    110      */
    111     private final static class DeviceContentProvider implements ITreeContentProvider {
    112         private final static DeviceType[] sCategory = new DeviceType[] {
    113             DeviceType.DEFAULT, DeviceType.ADDON, DeviceType.CUSTOM
    114         };
    115 
    116         private LayoutDeviceManager mLayoutDeviceManager;
    117 
    118         public DeviceContentProvider() {
    119         }
    120 
    121         public Object[] getElements(Object inputElement) {
    122             return sCategory;
    123         }
    124 
    125         public Object[] getChildren(Object parentElement) {
    126             if (parentElement instanceof DeviceType) {
    127                 if (DeviceType.DEFAULT.equals(parentElement)) {
    128                     return mLayoutDeviceManager.getDefaultLayoutDevices().toArray();
    129                 } else if (DeviceType.ADDON.equals(parentElement)) {
    130                     return mLayoutDeviceManager.getAddOnLayoutDevice().toArray();
    131                 } else if (DeviceType.CUSTOM.equals(parentElement)) {
    132                     return mLayoutDeviceManager.getUserLayoutDevices().toArray();
    133                 }
    134             } else if (parentElement instanceof LayoutDevice) {
    135                 LayoutDevice device = (LayoutDevice)parentElement;
    136                 return device.getConfigs().toArray();
    137             }
    138 
    139             return null;
    140         }
    141 
    142         public Object getParent(Object element) {
    143             // parent cannot be computed. this is fine.
    144             return null;
    145         }
    146 
    147         public boolean hasChildren(Object element) {
    148             if (element instanceof DeviceType) {
    149                 if (DeviceType.DEFAULT.equals(element)) {
    150                     return mLayoutDeviceManager.getDefaultLayoutDevices().size() > 0;
    151                 } else if (DeviceType.ADDON.equals(element)) {
    152                     return mLayoutDeviceManager.getAddOnLayoutDevice().size() > 0;
    153                 } else if (DeviceType.CUSTOM.equals(element)) {
    154                     return mLayoutDeviceManager.getUserLayoutDevices().size() > 0;
    155                 }
    156             } else if (element instanceof LayoutDevice) {
    157                 LayoutDevice device = (LayoutDevice)element;
    158                 return device.getConfigs().size() > 0;
    159             }
    160 
    161             return false;
    162         }
    163 
    164 
    165         public void dispose() {
    166             // nothing to dispose
    167         }
    168 
    169         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    170             if (newInput instanceof LayoutDeviceManager) {
    171                 mLayoutDeviceManager = (LayoutDeviceManager)newInput;
    172                 return;
    173             }
    174 
    175             // when the dialog closes we get null input
    176             if (newInput != null) {
    177                 throw new IllegalArgumentException(
    178                         "ConfigContentProvider requires input to be LayoutDeviceManager");
    179             }
    180         }
    181     }
    182 
    183     /**
    184      * Label provider for the {@link TreeViewer}.
    185      * Supported elements are {@link DeviceType}, {@link LayoutDevice}, and {@link DeviceConfig}.
    186      *
    187      */
    188     private final static class DeviceLabelProvider implements ITableLabelProvider {
    189 
    190         public String getColumnText(Object element, int columnIndex) {
    191             if (element instanceof DeviceType) {
    192                 if (columnIndex == 0) {
    193                     return ((DeviceType)element).getDisplayString();
    194                 }
    195             } else if (element instanceof LayoutDevice) {
    196                 if (columnIndex == 0) {
    197                     return ((LayoutDevice)element).getName();
    198                 }
    199             } else if (element instanceof DeviceConfig) {
    200                 if (columnIndex == 0) {
    201                     return ((DeviceConfig)element).getName();
    202                 } else {
    203                     return ((DeviceConfig)element).getConfig().toString();
    204                 }
    205             }
    206             return null;
    207         }
    208 
    209         public Image getColumnImage(Object element, int columnIndex) {
    210             // no image
    211             return null;
    212         }
    213 
    214         public void addListener(ILabelProviderListener listener) {
    215             // no listener
    216         }
    217 
    218         public void removeListener(ILabelProviderListener listener) {
    219             // no listener
    220         }
    221 
    222         public void dispose() {
    223             // nothing to dispose
    224         }
    225 
    226         public boolean isLabelProperty(Object element, String property) {
    227             return false;
    228         }
    229     }
    230 
    231     protected ConfigManagerDialog(Shell parentShell) {
    232         super(parentShell, 2, false);
    233         mManager = Sdk.getCurrent().getLayoutDeviceManager();
    234     }
    235 
    236     @Override
    237     protected int getShellStyle() {
    238         return super.getShellStyle() | SWT.RESIZE;
    239     }
    240 
    241     @Override
    242     protected void configureShell(Shell newShell) {
    243         super.configureShell(newShell);
    244         newShell.setText("Device Configurations");
    245     }
    246 
    247     @Override
    248     public void createDialogContent(final Composite parent) {
    249         GridData gd;
    250         GridLayout gl;
    251 
    252         Tree tree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION);
    253         tree.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
    254         gd.widthHint = 700;
    255 
    256         tree.setHeaderVisible(true);
    257         tree.setLinesVisible(true);
    258         TableHelper.createTreeColumn(tree, "Name", SWT.LEFT, 150, COL_NAME,
    259                 AdtPlugin.getDefault().getPreferenceStore());
    260         TableHelper.createTreeColumn(tree, "Configuration", SWT.LEFT, 500, COL_CONFIG,
    261                 AdtPlugin.getDefault().getPreferenceStore());
    262 
    263         mTreeViewer = new TreeViewer(tree);
    264         mTreeViewer.setContentProvider(new DeviceContentProvider());
    265         mTreeViewer.setLabelProvider(new DeviceLabelProvider());
    266         mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
    267         mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
    268             public void selectionChanged(SelectionChangedEvent event) {
    269                 setEnabled(getSelection());
    270             }
    271         });
    272 
    273         Composite buttons = new Composite(parent, SWT.NONE);
    274         buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
    275         buttons.setLayout(gl = new GridLayout());
    276         gl.marginHeight = gl.marginWidth = 0;
    277 
    278         mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    279         mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    280         mNewButton.setText("New...");
    281         mNewButton.addSelectionListener(new SelectionAdapter() {
    282             @Override
    283             public void widgetSelected(SelectionEvent e) {
    284                 DeviceSelection selection = getSelection();
    285 
    286                 ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null);
    287                 if (selection.device != null) {
    288                     dlg.setDeviceName(selection.device.getName());
    289                     dlg.setXDpi(selection.device.getXDpi());
    290                     dlg.setYDpi(selection.device.getYDpi());
    291                 }
    292                 if (selection.config != null) {
    293                     dlg.setConfigName(selection.config.getName());
    294                     dlg.setConfig(selection.config.getConfig());
    295                 }
    296 
    297                 if (dlg.open() == Window.OK) {
    298                     String deviceName = dlg.getDeviceName();
    299                     String configName = dlg.getConfigName();
    300                     FolderConfiguration config = new FolderConfiguration();
    301                     dlg.getConfig(config);
    302 
    303                     // first if there was no original device, we create one.
    304                     // Because the new button is disabled when something else than "custom" is
    305                     // selected, we always add to the user devices without checking.
    306                     LayoutDevice d;
    307                     if (selection.device == null) {
    308                         // FIXME: this doesn't check if the device name is taken.
    309                         d = mManager.addUserDevice(deviceName, dlg.getXDpi(), dlg.getYDpi());
    310                     } else {
    311                         // search for it.
    312                         d = mManager.getUserLayoutDevice(deviceName);
    313                     }
    314 
    315                     if (d != null) {
    316                         // then if there was no config, we add it, otherwise we edit it
    317                         // (same method that adds/replace a config).
    318                         // FIXME this doesn't check if the name was already taken.
    319                         mManager.addUserConfiguration(d, configName, config);
    320 
    321                         mTreeViewer.refresh();
    322                         select(d, configName);
    323                     }
    324                 }
    325             }
    326         });
    327 
    328         mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    329         mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    330         mEditButton.setText("Edit...");
    331         mEditButton.addSelectionListener(new SelectionAdapter() {
    332             @Override
    333             public void widgetSelected(SelectionEvent e) {
    334                 DeviceSelection selection = getSelection();
    335                 ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null);
    336                 dlg.setDeviceName(selection.device.getName());
    337                 dlg.setXDpi(selection.device.getXDpi());
    338                 dlg.setYDpi(selection.device.getYDpi());
    339                 dlg.setConfigName(selection.config.getName());
    340                 dlg.setConfig(selection.config.getConfig());
    341 
    342                 if (dlg.open() == Window.OK) {
    343                     String deviceName = dlg.getDeviceName();
    344                     String configName = dlg.getConfigName();
    345                     FolderConfiguration config = new FolderConfiguration();
    346                     dlg.getConfig(config);
    347 
    348                     // replace the device if needed.
    349                     // FIXME: this doesn't check if the replacement name doesn't exist already.
    350                     LayoutDevice d = mManager.replaceUserDevice(selection.device, deviceName,
    351                             dlg.getXDpi(), dlg.getYDpi());
    352 
    353                     // and add/replace the config
    354                     mManager.replaceUserConfiguration(d, selection.config.getName(), configName,
    355                             config);
    356 
    357                     mTreeViewer.refresh();
    358                     select(d, configName);
    359                 }
    360             }
    361         });
    362 
    363         mCopyButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    364         mCopyButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    365         mCopyButton.setText("Copy");
    366         mCopyButton.addSelectionListener(new SelectionAdapter() {
    367             @Override
    368             public void widgetSelected(SelectionEvent e) {
    369                 DeviceSelection selection = getSelection();
    370 
    371                 // is the source a default/add-on device, or are we copying a full device?
    372                 // if so the target device is a new device.
    373                 LayoutDevice targetDevice = selection.device;
    374                 if (selection.type == DeviceType.DEFAULT || selection.type == DeviceType.ADDON ||
    375                         selection.config == null) {
    376                     // create a new device
    377                     targetDevice = mManager.addUserDevice(
    378                             selection.device.getName() + " Copy", // new name
    379                             selection.device.getXDpi(),
    380                             selection.device.getYDpi());
    381                 }
    382 
    383                 String newConfigName = null; // name of the single new config. used for the select.
    384 
    385                 // are we copying the full device?
    386                 if (selection.config == null) {
    387                     // get the config from the origin device
    388                     List<DeviceConfig> configs = selection.device.getConfigs();
    389 
    390                     // and copy them in the target device
    391                     for (DeviceConfig config : configs) {
    392                         // we need to make a copy of the config object, or it could be modified
    393                         // in default/addon by editing the version in the new device.
    394                         FolderConfiguration copy = new FolderConfiguration();
    395                         copy.set(config.getConfig());
    396 
    397                         // the name can stay the same since we are copying a full device
    398                         // and the target device has its own new name.
    399                         mManager.addUserConfiguration(targetDevice, config.getName(), copy);
    400                     }
    401                 } else {
    402                     // only copy the config. target device is not the same as the selection, don't
    403                     // change the config name as we already changed the name of the device.
    404                     newConfigName = (selection.device != targetDevice) ?
    405                             selection.config.getName() : selection.config.getName() + " Copy";
    406 
    407                     // copy of the config
    408                     FolderConfiguration copy = new FolderConfiguration();
    409                     copy.set(selection.config.getConfig());
    410 
    411                     // and create the config
    412                     mManager.addUserConfiguration(targetDevice, newConfigName, copy);
    413                 }
    414 
    415                 mTreeViewer.refresh();
    416 
    417                 select(targetDevice, newConfigName);
    418             }
    419         });
    420 
    421         mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    422         mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    423         mDeleteButton.setText("Delete");
    424         mDeleteButton.addSelectionListener(new SelectionAdapter() {
    425             @Override
    426             public void widgetSelected(SelectionEvent e) {
    427                 DeviceSelection selection = getSelection();
    428 
    429                 if (selection.config != null) {
    430                     mManager.removeUserConfiguration(selection.device, selection.config.getName());
    431                 } else if (selection.device != null) {
    432                     mManager.removeUserDevice(selection.device);
    433                 }
    434 
    435                 mTreeViewer.refresh();
    436 
    437                 // either select the device (if we removed a entry, or the top custom node if
    438                 // we removed a device)
    439                 select(selection.config != null ? selection.device : null, null);
    440             }
    441         });
    442 
    443         Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
    444         separator.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
    445         gd.horizontalSpan = 2;
    446 
    447         mTreeViewer.setInput(mManager);
    448         setEnabled(null); // no selection at the start
    449     }
    450 
    451     @Override
    452     protected void createButtonsForButtonBar(Composite parent) {
    453         // we only want an OK button.
    454         createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
    455     }
    456 
    457     /**
    458      * Returns a {@link DeviceSelection} object representing the selected path in the
    459      * {@link TreeViewer}
    460      */
    461     private DeviceSelection getSelection() {
    462         // get the selection paths
    463         TreeSelection selection = (TreeSelection)mTreeViewer.getSelection();
    464         TreePath[] paths =selection.getPaths();
    465 
    466         if (paths.length == 0) {
    467             return null;
    468         }
    469 
    470         TreePath pathSelection = paths[0];
    471 
    472         DeviceType type = (DeviceType)pathSelection.getFirstSegment();
    473         LayoutDevice device = null;
    474         DeviceConfig config = null;
    475         switch (pathSelection.getSegmentCount()) {
    476             case 2: // layout device is selected
    477                 device = (LayoutDevice)pathSelection.getLastSegment();
    478                 break;
    479             case 3: // config is selected
    480                 device = (LayoutDevice)pathSelection.getSegment(1);
    481                 config = (DeviceConfig)pathSelection.getLastSegment();
    482         }
    483 
    484         return new DeviceSelection(type, device, config);
    485     }
    486 
    487     /**
    488      * Enables/disables the action button based on the {@link DeviceSelection}.
    489      * @param selection the selection
    490      */
    491     protected void setEnabled(DeviceSelection selection) {
    492         if (selection == null) {
    493             mNewButton.setEnabled(false);
    494             mEditButton.setEnabled(false);
    495             mCopyButton.setEnabled(false);
    496             mDeleteButton.setEnabled(false);
    497         } else {
    498             switch (selection.type) {
    499                 case DEFAULT:
    500                 case ADDON:
    501                     // only allow copy if device is not null
    502                     mNewButton.setEnabled(false);
    503                     mEditButton.setEnabled(false);
    504                     mDeleteButton.setEnabled(false);
    505                     mCopyButton.setEnabled(selection.device != null);
    506                     break;
    507                 case CUSTOM:
    508                     mNewButton.setEnabled(true); // always true to create new devices.
    509                     mEditButton.setEnabled(selection.config != null); // only edit config for now
    510 
    511                     boolean enabled = selection.device != null; // need at least selected device
    512                     mDeleteButton.setEnabled(enabled);          // for delete and copy buttons
    513                     mCopyButton.setEnabled(enabled);
    514                     break;
    515             }
    516         }
    517     }
    518 
    519     /**
    520      * Selects a device and optionally a config. Because this is meant to show newly created/edited
    521      * device/config, it'll only do so for {@link DeviceType#CUSTOM} devices.
    522      * @param device the device to select
    523      * @param configName the config to select (optional)
    524      */
    525     private void select(LayoutDevice device, String configName) {
    526         Object[] path;
    527         if (device == null) {
    528             // select the "custom" node
    529             path = new Object[] { DeviceType.CUSTOM };
    530         } else if (configName == null) {
    531             // this is the easy case. no config to select
    532             path = new Object[] { DeviceType.CUSTOM, device };
    533         } else {
    534             // this is more complex. we have the configName, but the tree contains DeviceConfig
    535             // Look for the entry.
    536             DeviceConfig match = null;
    537             for (DeviceConfig config : device.getConfigs()) {
    538                 if (config.getName().equals(configName)) {
    539                     match = config;
    540                     break;
    541                 }
    542             }
    543 
    544             if (match != null) {
    545                 path = new Object[] { DeviceType.CUSTOM, device, match };
    546             } else {
    547                 path = new Object[] { DeviceType.CUSTOM, device };
    548             }
    549         }
    550 
    551         mTreeViewer.setSelection(new TreeSelection(new TreePath(path)), true /*reveal*/);
    552     }
    553 }
    554