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