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