1 /* 2 * Copyright (C) 2010 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.properties; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper; 21 import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.IProjectChooserFilter; 22 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 23 import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; 24 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 25 import com.android.sdklib.internal.project.ProjectProperties; 26 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; 27 28 import org.eclipse.core.resources.IProject; 29 import org.eclipse.core.runtime.IPath; 30 import org.eclipse.jdt.core.IJavaProject; 31 import org.eclipse.swt.SWT; 32 import org.eclipse.swt.events.ControlAdapter; 33 import org.eclipse.swt.events.ControlEvent; 34 import org.eclipse.swt.events.DisposeEvent; 35 import org.eclipse.swt.events.DisposeListener; 36 import org.eclipse.swt.events.SelectionAdapter; 37 import org.eclipse.swt.events.SelectionEvent; 38 import org.eclipse.swt.graphics.Image; 39 import org.eclipse.swt.graphics.Rectangle; 40 import org.eclipse.swt.layout.GridData; 41 import org.eclipse.swt.layout.GridLayout; 42 import org.eclipse.swt.widgets.Button; 43 import org.eclipse.swt.widgets.Composite; 44 import org.eclipse.swt.widgets.Label; 45 import org.eclipse.swt.widgets.Table; 46 import org.eclipse.swt.widgets.TableColumn; 47 import org.eclipse.swt.widgets.TableItem; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Set; 52 53 54 /** 55 * Self-contained UI to edit the library dependencies of a Project. 56 */ 57 final class LibraryProperties { 58 59 private Composite mTop; 60 private Table mTable; 61 private Image mMatchIcon; 62 private Image mErrorIcon; 63 private Button mAddButton; 64 private Button mRemoveButton; 65 private Button mUpButton; 66 private Button mDownButton; 67 private ProjectChooserHelper mProjectChooser; 68 69 /** 70 * Original ProjectState being edited. This is read-only. 71 * @see #mPropertiesWorkingCopy 72 */ 73 private ProjectState mState; 74 /** 75 * read-write copy of the properties being edited. 76 */ 77 private ProjectPropertiesWorkingCopy mPropertiesWorkingCopy; 78 79 private final List<ItemData> mItemDataList = new ArrayList<ItemData>(); 80 private boolean mMustSave = false; 81 82 /** 83 * Internal struct to store library info in the table item. 84 */ 85 private final static class ItemData { 86 String relativePath; 87 IProject project; 88 } 89 90 /** 91 * {@link IProjectChooserFilter} implementation that dynamically ignores libraries 92 * that are already dependencies. 93 */ 94 IProjectChooserFilter mFilter = new IProjectChooserFilter() { 95 @Override 96 public boolean accept(IProject project) { 97 // first check if it's a library 98 ProjectState state = Sdk.getProjectState(project); 99 if (state != null) { 100 if (state.isLibrary() == false || project == mState.getProject()) { 101 return false; 102 } 103 104 // then check if the library is not already part of the dependencies. 105 for (ItemData data : mItemDataList) { 106 if (data.project == project) { 107 return false; 108 } 109 } 110 111 return true; 112 } 113 114 return false; 115 } 116 117 @Override 118 public boolean useCache() { 119 return false; 120 } 121 }; 122 LibraryProperties(Composite parent)123 LibraryProperties(Composite parent) { 124 125 mMatchIcon = AdtPlugin.getImageDescriptor("/icons/match.png").createImage(); //$NON-NLS-1$ 126 mErrorIcon = AdtPlugin.getImageDescriptor("/icons/error.png").createImage(); //$NON-NLS-1$ 127 128 // Layout has 2 column 129 mTop = new Composite(parent, SWT.NONE); 130 mTop.setLayout(new GridLayout(2, false)); 131 mTop.setLayoutData(new GridData(GridData.FILL_BOTH)); 132 mTop.setFont(parent.getFont()); 133 mTop.addDisposeListener(new DisposeListener() { 134 @Override 135 public void widgetDisposed(DisposeEvent e) { 136 mMatchIcon.dispose(); 137 mErrorIcon.dispose(); 138 } 139 }); 140 141 mTable = new Table(mTop, SWT.BORDER | SWT.FULL_SELECTION | SWT.SINGLE); 142 mTable.setLayoutData(new GridData(GridData.FILL_BOTH)); 143 mTable.setHeaderVisible(true); 144 mTable.setLinesVisible(false); 145 mTable.addSelectionListener(new SelectionAdapter() { 146 @Override 147 public void widgetSelected(SelectionEvent e) { 148 resetEnabled(); 149 } 150 }); 151 152 final TableColumn column0 = new TableColumn(mTable, SWT.NONE); 153 column0.setText("Reference"); 154 final TableColumn column1 = new TableColumn(mTable, SWT.NONE); 155 column1.setText("Project"); 156 157 Composite buttons = new Composite(mTop, SWT.NONE); 158 buttons.setLayout(new GridLayout()); 159 buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL)); 160 161 mProjectChooser = new ProjectChooserHelper(parent.getShell(), mFilter); 162 163 mAddButton = new Button(buttons, SWT.PUSH | SWT.FLAT); 164 mAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 165 mAddButton.setText("Add..."); 166 mAddButton.addSelectionListener(new SelectionAdapter() { 167 @Override 168 public void widgetSelected(SelectionEvent e) { 169 IJavaProject javaProject = mProjectChooser.chooseJavaProject(null /*projectName*/, 170 "Please select a library project"); 171 if (javaProject != null) { 172 IProject iProject = javaProject.getProject(); 173 IPath relativePath = iProject.getLocation().makeRelativeTo( 174 mState.getProject().getLocation()); 175 176 addItem(relativePath.toString(), iProject, -1); 177 resetEnabled(); 178 mMustSave = true; 179 } 180 } 181 }); 182 183 mRemoveButton = new Button(buttons, SWT.PUSH | SWT.FLAT); 184 mRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 185 mRemoveButton.setText("Remove"); 186 mRemoveButton.addSelectionListener(new SelectionAdapter() { 187 @Override 188 public void widgetSelected(SelectionEvent e) { 189 // selection is ensured and in single mode. 190 TableItem selection = mTable.getSelection()[0]; 191 ItemData data = (ItemData) selection.getData(); 192 mItemDataList.remove(data); 193 mTable.remove(mTable.getSelectionIndex()); 194 resetEnabled(); 195 mMustSave = true; 196 } 197 }); 198 199 Label l = new Label(buttons, SWT.SEPARATOR | SWT.HORIZONTAL); 200 l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 201 202 mUpButton = new Button(buttons, SWT.PUSH | SWT.FLAT); 203 mUpButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 204 mUpButton.setText("Up"); 205 mUpButton.addSelectionListener(new SelectionAdapter() { 206 @Override 207 public void widgetSelected(SelectionEvent e) { 208 int index = mTable.getSelectionIndex(); 209 ItemData data = mItemDataList.remove(index); 210 mTable.remove(index); 211 212 // add at a lower index. 213 addItem(data.relativePath, data.project, index - 1); 214 215 // reset the selection 216 mTable.select(index - 1); 217 resetEnabled(); 218 mMustSave = true; 219 } 220 }); 221 222 mDownButton = new Button(buttons, SWT.PUSH | SWT.FLAT); 223 mDownButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 224 mDownButton.setText("Down"); 225 mDownButton.addSelectionListener(new SelectionAdapter() { 226 @Override 227 public void widgetSelected(SelectionEvent e) { 228 int index = mTable.getSelectionIndex(); 229 ItemData data = mItemDataList.remove(index); 230 mTable.remove(index); 231 232 // add at a higher index. 233 addItem(data.relativePath, data.project, index + 1); 234 235 // reset the selection 236 mTable.select(index + 1); 237 resetEnabled(); 238 mMustSave = true; 239 } 240 }); 241 242 adjustColumnsWidth(mTable, column0, column1); 243 } 244 245 /** 246 * Sets or reset the content. 247 * @param state the {@link ProjectState} to display. This is read-only. 248 * @param propertiesWorkingCopy the working copy of {@link ProjectProperties} to modify. 249 */ setContent(ProjectState state, ProjectPropertiesWorkingCopy propertiesWorkingCopy)250 void setContent(ProjectState state, ProjectPropertiesWorkingCopy propertiesWorkingCopy) { 251 mState = state; 252 mPropertiesWorkingCopy = propertiesWorkingCopy; 253 254 // reset content 255 mTable.removeAll(); 256 mItemDataList.clear(); 257 258 // get the libraries and make a copy of the data we need. 259 List<LibraryState> libs = state.getLibraries(); 260 261 for (LibraryState lib : libs) { 262 ProjectState libState = lib.getProjectState(); 263 addItem(lib.getRelativePath(), libState != null ? libState.getProject() : null, -1); 264 } 265 266 mMustSave = false; 267 268 resetEnabled(); 269 } 270 271 /** 272 * Saves the state of the UI into the {@link ProjectProperties} object that was returned by 273 * {@link #setContent}. 274 * <p/>This does not update the {@link ProjectState} object that was provided, nor does it save 275 * the new properties on disk. Saving the properties on disk, via 276 * {@link ProjectPropertiesWorkingCopy#save()}, and updating the {@link ProjectState} instance, 277 * via {@link ProjectState#reloadProperties()} must be done by the caller. 278 * @return <code>true</code> if there was actually new data saved in the project state, false 279 * otherwise. 280 */ save()281 boolean save() { 282 boolean mustSave = mMustSave; 283 if (mMustSave) { 284 // remove all previous library dependencies. 285 Set<String> keys = mPropertiesWorkingCopy.keySet(); 286 for (String key : keys) { 287 if (key.startsWith(ProjectProperties.PROPERTY_LIB_REF)) { 288 mPropertiesWorkingCopy.removeProperty(key); 289 } 290 } 291 292 // now add the new libraries. 293 int index = 1; 294 for (ItemData data : mItemDataList) { 295 mPropertiesWorkingCopy.setProperty(ProjectProperties.PROPERTY_LIB_REF + index++, 296 data.relativePath); 297 } 298 } 299 300 mMustSave = false; 301 return mustSave; 302 } 303 304 /** 305 * Enables or disables the whole widget. 306 * @param enabled whether the widget must be enabled or not. 307 */ setEnabled(boolean enabled)308 void setEnabled(boolean enabled) { 309 if (enabled == false) { 310 mTable.setEnabled(false); 311 mAddButton.setEnabled(false); 312 mRemoveButton.setEnabled(false); 313 mUpButton.setEnabled(false); 314 mDownButton.setEnabled(false); 315 } else { 316 mTable.setEnabled(true); 317 mAddButton.setEnabled(true); 318 resetEnabled(); 319 } 320 } 321 resetEnabled()322 private void resetEnabled() { 323 int index = mTable.getSelectionIndex(); 324 mRemoveButton.setEnabled(index != -1); 325 mUpButton.setEnabled(index > 0); 326 mDownButton.setEnabled(index != -1 && index < mTable.getItemCount() - 1); 327 } 328 329 /** 330 * Adds a new item and stores a {@link Stuff} into {@link #mStuff}. 331 * 332 * @param relativePath the relative path of the library entry 333 * @param project the associated IProject 334 * @param index if different than -1, the index at which to insert the item. 335 */ addItem(String relativePath, IProject project, int index)336 private void addItem(String relativePath, IProject project, int index) { 337 ItemData data = new ItemData(); 338 data.relativePath = relativePath; 339 data.project = project; 340 TableItem item; 341 if (index == -1) { 342 mItemDataList.add(data); 343 item = new TableItem(mTable, SWT.NONE); 344 } else { 345 mItemDataList.add(index, data); 346 item = new TableItem(mTable, SWT.NONE, index); 347 } 348 item.setData(data); 349 item.setText(0, data.relativePath); 350 item.setImage(data.project != null ? mMatchIcon : mErrorIcon); 351 item.setText(1, data.project != null ? data.project.getName() : "?"); 352 } 353 354 /** 355 * Adds a listener to adjust the columns width when the parent is resized. 356 * <p/> 357 * If we need something more fancy, we might want to use this: 358 * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co 359 */ adjustColumnsWidth(final Table table, final TableColumn column0, final TableColumn column1)360 private void adjustColumnsWidth(final Table table, 361 final TableColumn column0, 362 final TableColumn column1) { 363 // Add a listener to resize the column to the full width of the table 364 table.addControlListener(new ControlAdapter() { 365 @Override 366 public void controlResized(ControlEvent e) { 367 Rectangle r = table.getClientArea(); 368 column0.setWidth(r.width * 50 / 100); // 50% 369 column1.setWidth(r.width * 50 / 100); // 50% 370 } 371 }); 372 } 373 } 374 375