• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.eclipse.adt.AdtPlugin;
21 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
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.sdkuilib.ui.GridDialog;
26 
27 import org.eclipse.jface.dialogs.IDialogConstants;
28 import org.eclipse.jface.viewers.ILabelProviderListener;
29 import org.eclipse.jface.viewers.ISelectionChangedListener;
30 import org.eclipse.jface.viewers.ITableLabelProvider;
31 import org.eclipse.jface.viewers.ITreeContentProvider;
32 import org.eclipse.jface.viewers.SelectionChangedEvent;
33 import org.eclipse.jface.viewers.TreePath;
34 import org.eclipse.jface.viewers.TreeSelection;
35 import org.eclipse.jface.viewers.TreeViewer;
36 import org.eclipse.jface.viewers.Viewer;
37 import org.eclipse.jface.window.Window;
38 import org.eclipse.swt.SWT;
39 import org.eclipse.swt.events.SelectionAdapter;
40 import org.eclipse.swt.events.SelectionEvent;
41 import org.eclipse.swt.graphics.Image;
42 import org.eclipse.swt.layout.GridData;
43 import org.eclipse.swt.layout.GridLayout;
44 import org.eclipse.swt.widgets.Button;
45 import org.eclipse.swt.widgets.Composite;
46 import org.eclipse.swt.widgets.Label;
47 import org.eclipse.swt.widgets.Shell;
48 import org.eclipse.swt.widgets.Tree;
49 
50 import java.util.Map;
51 import java.util.Map.Entry;
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 
DeviceType(String display)73         DeviceType(String display) {
74             mDisplay = display;
75         }
76 
getDisplayString()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 {
DeviceSelection(DeviceType type, LayoutDevice device, Entry<String, FolderConfiguration> entry)86         public DeviceSelection(DeviceType type, LayoutDevice device,
87                 Entry<String, FolderConfiguration> entry) {
88             this.type = type;
89             this.device = device;
90             this.entry = entry;
91         }
92 
93         final DeviceType type;
94         final LayoutDevice device;
95         final Entry<String, FolderConfiguration> entry;
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 
DeviceContentProvider()118         public DeviceContentProvider() {
119         }
120 
getElements(Object inputElement)121         public Object[] getElements(Object inputElement) {
122             return sCategory;
123         }
124 
getChildren(Object parentElement)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().entrySet().toArray();
137             }
138 
139             return null;
140         }
141 
getParent(Object element)142         public Object getParent(Object element) {
143             // parent cannot be computed. this is fine.
144             return null;
145         }
146 
hasChildren(Object element)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 
dispose()165         public void dispose() {
166             // nothing to dispose
167         }
168 
inputChanged(Viewer viewer, Object oldInput, Object newInput)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 Entry} (where
186      * the key is a {@link String} object, and the value is a {@link FolderConfiguration} object).
187      *
188      */
189     private final static class DeviceLabelProvider implements ITableLabelProvider {
190 
getColumnText(Object element, int columnIndex)191         public String getColumnText(Object element, int columnIndex) {
192             if (element instanceof DeviceType) {
193                 if (columnIndex == 0) {
194                     return ((DeviceType)element).getDisplayString();
195                 }
196             } else if (element instanceof LayoutDevice) {
197                 if (columnIndex == 0) {
198                     return ((LayoutDevice)element).getName();
199                 }
200             } else if (element instanceof Entry<?, ?>) {
201                 if (columnIndex == 0) {
202                     return (String)((Entry<?,?>)element).getKey();
203                 } else {
204                     return ((Entry<?,?>)element).getValue().toString();
205                 }
206             }
207             return null;
208         }
209 
getColumnImage(Object element, int columnIndex)210         public Image getColumnImage(Object element, int columnIndex) {
211             // no image
212             return null;
213         }
214 
addListener(ILabelProviderListener listener)215         public void addListener(ILabelProviderListener listener) {
216             // no listener
217         }
218 
removeListener(ILabelProviderListener listener)219         public void removeListener(ILabelProviderListener listener) {
220             // no listener
221         }
222 
dispose()223         public void dispose() {
224             // nothing to dispose
225         }
226 
isLabelProperty(Object element, String property)227         public boolean isLabelProperty(Object element, String property) {
228             return false;
229         }
230     }
231 
ConfigManagerDialog(Shell parentShell)232     protected ConfigManagerDialog(Shell parentShell) {
233         super(parentShell, 2, false);
234         mManager = Sdk.getCurrent().getLayoutDeviceManager();
235     }
236 
237     @Override
getShellStyle()238     protected int getShellStyle() {
239         return super.getShellStyle() | SWT.RESIZE;
240     }
241 
242     @Override
configureShell(Shell newShell)243     protected void configureShell(Shell newShell) {
244         super.configureShell(newShell);
245         newShell.setText("Device Configurations");
246     }
247 
248     @Override
createDialogContent(final Composite parent)249     public void createDialogContent(final Composite parent) {
250         GridData gd;
251         GridLayout gl;
252 
253         Tree tree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION);
254         tree.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
255         gd.widthHint = 700;
256 
257         tree.setHeaderVisible(true);
258         tree.setLinesVisible(true);
259         TableHelper.createTreeColumn(tree, "Name", SWT.LEFT, 150, COL_NAME,
260                 AdtPlugin.getDefault().getPreferenceStore());
261         TableHelper.createTreeColumn(tree, "Configuration", SWT.LEFT, 500, COL_CONFIG,
262                 AdtPlugin.getDefault().getPreferenceStore());
263 
264         mTreeViewer = new TreeViewer(tree);
265         mTreeViewer.setContentProvider(new DeviceContentProvider());
266         mTreeViewer.setLabelProvider(new DeviceLabelProvider());
267         mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
268         mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
269             public void selectionChanged(SelectionChangedEvent event) {
270                 setEnabled(getSelection());
271             }
272         });
273 
274         Composite buttons = new Composite(parent, SWT.NONE);
275         buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
276         buttons.setLayout(gl = new GridLayout());
277         gl.marginHeight = gl.marginWidth = 0;
278 
279         mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
280         mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
281         mNewButton.setText("New...");
282         mNewButton.addSelectionListener(new SelectionAdapter() {
283             @Override
284             public void widgetSelected(SelectionEvent e) {
285                 DeviceSelection selection = getSelection();
286 
287                 ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null);
288                 if (selection.device != null) {
289                     dlg.setDeviceName(selection.device.getName());
290                     dlg.setXDpi(selection.device.getXDpi());
291                     dlg.setYDpi(selection.device.getYDpi());
292                 }
293                 if (selection.entry != null) {
294                     dlg.setConfigName(selection.entry.getKey());
295                     dlg.setConfig(selection.entry.getValue());
296                 }
297 
298                 if (dlg.open() == Window.OK) {
299                     String deviceName = dlg.getDeviceName();
300                     String configName = dlg.getConfigName();
301                     FolderConfiguration config = new FolderConfiguration();
302                     dlg.getConfig(config);
303 
304                     // first if there was no original device, we create one.
305                     // Because the new button is disabled when something else than "custom" is
306                     // selected, we always add to the user devices without checking.
307                     LayoutDevice d;
308                     if (selection.device == null) {
309                         // FIXME: this doesn't check if the device name is taken.
310                         d = mManager.addUserDevice(deviceName, dlg.getXDpi(), dlg.getYDpi());
311                     } else {
312                         // search for it.
313                         d = mManager.getUserLayoutDevice(deviceName);
314                     }
315 
316                     if (d != null) {
317                         // then if there was no config, we add it, otherwise we edit it
318                         // (same method that adds/replace a config).
319                         // FIXME this doesn't check if the name was already taken.
320                         mManager.addUserConfiguration(d, configName, config);
321 
322                         mTreeViewer.refresh();
323                         select(d, configName);
324                     }
325                 }
326             }
327         });
328 
329         mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
330         mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
331         mEditButton.setText("Edit...");
332         mEditButton.addSelectionListener(new SelectionAdapter() {
333             @Override
334             public void widgetSelected(SelectionEvent e) {
335                 DeviceSelection selection = getSelection();
336                 ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null);
337                 dlg.setDeviceName(selection.device.getName());
338                 dlg.setXDpi(selection.device.getXDpi());
339                 dlg.setYDpi(selection.device.getYDpi());
340                 dlg.setConfigName(selection.entry.getKey());
341                 dlg.setConfig(selection.entry.getValue());
342 
343                 if (dlg.open() == Window.OK) {
344                     String deviceName = dlg.getDeviceName();
345                     String configName = dlg.getConfigName();
346                     FolderConfiguration config = new FolderConfiguration();
347                     dlg.getConfig(config);
348 
349                     // replace the device if needed.
350                     // FIXME: this doesn't check if the replacement name doesn't exist already.
351                     LayoutDevice d = mManager.replaceUserDevice(selection.device, deviceName,
352                             dlg.getXDpi(), dlg.getYDpi());
353 
354                     // and add/replace the config
355                     mManager.replaceUserConfiguration(d, selection.entry.getKey(), configName,
356                             config);
357 
358                     mTreeViewer.refresh();
359                     select(d, configName);
360                 }
361             }
362         });
363 
364         mCopyButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
365         mCopyButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
366         mCopyButton.setText("Copy");
367         mCopyButton.addSelectionListener(new SelectionAdapter() {
368             @Override
369             public void widgetSelected(SelectionEvent e) {
370                 DeviceSelection selection = getSelection();
371 
372                 // is the source a default/add-on device, or are we copying a full device?
373                 // if so the target device is a new device.
374                 LayoutDevice targetDevice = selection.device;
375                 if (selection.type == DeviceType.DEFAULT || selection.type == DeviceType.ADDON ||
376                         selection.entry == null) {
377                     // create a new device
378                     targetDevice = mManager.addUserDevice(
379                             selection.device.getName() + " Copy", // new name
380                             selection.device.getXDpi(),
381                             selection.device.getYDpi());
382                 }
383 
384                 String newConfigName = null; // name of the single new config. used for the select.
385 
386                 // are we copying the full device?
387                 if (selection.entry == null) {
388                     // get the config from the origin device
389                     Map<String, FolderConfiguration> configs = selection.device.getConfigs();
390 
391                     // and copy them in the target device
392                     for (Entry<String, FolderConfiguration> entry : configs.entrySet()) {
393                         // we need to make a copy of the config object, or it could be modified
394                         // in default/addon by editing the version in the new device.
395                         FolderConfiguration copy = new FolderConfiguration();
396                         copy.set(entry.getValue());
397 
398                         // the name can stay the same since we are copying a full device
399                         // and the target device has its own new name.
400                         mManager.addUserConfiguration(targetDevice, entry.getKey(), copy);
401                     }
402                 } else {
403                     // only copy the config. target device is not the same as the selection, don't
404                     // change the config name as we already changed the name of the device.
405                     newConfigName = (selection.device != targetDevice) ?
406                             selection.entry.getKey() : selection.entry.getKey() + " Copy";
407 
408                     // copy of the config
409                     FolderConfiguration copy = new FolderConfiguration();
410                     copy.set(selection.entry.getValue());
411 
412                     // and create the config
413                     mManager.addUserConfiguration(targetDevice, newConfigName, copy);
414                 }
415 
416                 mTreeViewer.refresh();
417 
418                 select(targetDevice, newConfigName);
419             }
420         });
421 
422         mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
423         mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
424         mDeleteButton.setText("Delete");
425         mDeleteButton.addSelectionListener(new SelectionAdapter() {
426             @Override
427             public void widgetSelected(SelectionEvent e) {
428                 DeviceSelection selection = getSelection();
429 
430                 if (selection.entry != null) {
431                     mManager.removeUserConfiguration(selection.device, selection.entry.getKey());
432                 } else if (selection.device != null) {
433                     mManager.removeUserDevice(selection.device);
434                 }
435 
436                 mTreeViewer.refresh();
437 
438                 // either select the device (if we removed a entry, or the top custom node if
439                 // we removed a device)
440                 select(selection.entry != null ? selection.device : null, null);
441             }
442         });
443 
444         Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
445         separator.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
446         gd.horizontalSpan = 2;
447 
448         mTreeViewer.setInput(mManager);
449         setEnabled(null); // no selection at the start
450     }
451 
452     @Override
createButtonsForButtonBar(Composite parent)453     protected void createButtonsForButtonBar(Composite parent) {
454         // we only want an OK button.
455         createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
456     }
457 
458     /**
459      * Returns a {@link DeviceSelection} object representing the selected path in the
460      * {@link TreeViewer}
461      */
getSelection()462     private DeviceSelection getSelection() {
463         // get the selection paths
464         TreeSelection selection = (TreeSelection)mTreeViewer.getSelection();
465         TreePath[] paths =selection.getPaths();
466 
467         if (paths.length == 0) {
468             return null;
469         }
470 
471         TreePath pathSelection = paths[0];
472 
473         DeviceType type = (DeviceType)pathSelection.getFirstSegment();
474         LayoutDevice device = null;
475         Entry<String, FolderConfiguration> entry = null;
476         switch (pathSelection.getSegmentCount()) {
477             case 2: // layout device is selected
478                 device = (LayoutDevice)pathSelection.getLastSegment();
479                 break;
480             case 3: // config is selected
481                 device = (LayoutDevice)pathSelection.getSegment(1);
482                 entry = (Entry<String, FolderConfiguration>)pathSelection.getLastSegment();
483         }
484 
485         return new DeviceSelection(type, device, entry);
486     }
487 
488     /**
489      * Enables/disables the action button based on the {@link DeviceSelection}.
490      * @param selection the selection
491      */
setEnabled(DeviceSelection selection)492     protected void setEnabled(DeviceSelection selection) {
493         if (selection == null) {
494             mNewButton.setEnabled(false);
495             mEditButton.setEnabled(false);
496             mCopyButton.setEnabled(false);
497             mDeleteButton.setEnabled(false);
498         } else {
499             switch (selection.type) {
500                 case DEFAULT:
501                 case ADDON:
502                     // only allow copy if device is not null
503                     mNewButton.setEnabled(false);
504                     mEditButton.setEnabled(false);
505                     mDeleteButton.setEnabled(false);
506                     mCopyButton.setEnabled(selection.device != null);
507                     break;
508                 case CUSTOM:
509                     mNewButton.setEnabled(true); // always true to create new devices.
510                     mEditButton.setEnabled(selection.entry != null); // only edit config for now
511 
512                     boolean enabled = selection.device != null; // need at least selected device
513                     mDeleteButton.setEnabled(enabled);          // for delete and copy buttons
514                     mCopyButton.setEnabled(enabled);
515                     break;
516             }
517         }
518     }
519 
520     /**
521      * Selects a device and optionally a config. Because this is meant to show newly created/edited
522      * device/config, it'll only do so for {@link DeviceType#CUSTOM} devices.
523      * @param device the device to select
524      * @param configName the config to select (optional)
525      */
select(LayoutDevice device, String configName)526     private void select(LayoutDevice device, String configName) {
527         Object[] path;
528         if (device == null) {
529             // select the "custom" node
530             path = new Object[] { DeviceType.CUSTOM };
531         } else if (configName == null) {
532             // this is the easy case. no config to select
533             path = new Object[] { DeviceType.CUSTOM, device };
534         } else {
535             // this is more complex. we have the configName, but the tree contains Entry<?,?>
536             // Look for the entry.
537             Entry<?, ?> match = null;
538             for (Entry<?, ?> entry : device.getConfigs().entrySet()) {
539                 if (entry.getKey().equals(configName)) {
540                     match = entry;
541                     break;
542                 }
543             }
544 
545             if (match != null) {
546                 path = new Object[] { DeviceType.CUSTOM, device, match };
547             } else {
548                 path = new Object[] { DeviceType.CUSTOM, device };
549             }
550         }
551 
552         mTreeViewer.setSelection(new TreeSelection(new TreePath(path)), true /*reveal*/);
553     }
554 }
555