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