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