/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.eclipse.adt.internal.properties; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper; import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.IProjectChooserFilter; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Self-contained UI to edit the library dependencies of a Project. */ final class LibraryProperties { private Composite mTop; private Table mTable; private Image mMatchIcon; private Image mErrorIcon; private Button mAddButton; private Button mRemoveButton; private Button mUpButton; private Button mDownButton; private ProjectChooserHelper mProjectChooser; /** * Original ProjectState being edited. This is read-only. * @see #mPropertiesWorkingCopy */ private ProjectState mState; /** * read-write copy of the properties being edited. */ private ProjectPropertiesWorkingCopy mPropertiesWorkingCopy; private final List<ItemData> mItemDataList = new ArrayList<ItemData>(); private boolean mMustSave = false; /** * Internal struct to store library info in the table item. */ private final static class ItemData { String relativePath; IProject project; } /** * {@link IProjectChooserFilter} implementation that dynamically ignores libraries * that are already dependencies. */ IProjectChooserFilter mFilter = new IProjectChooserFilter() { public boolean accept(IProject project) { // first check if it's a library ProjectState state = Sdk.getProjectState(project); if (state != null) { if (state.isLibrary() == false || project == mState.getProject()) { return false; } // then check if the library is not already part of the dependencies. for (ItemData data : mItemDataList) { if (data.project == project) { return false; } } return true; } return false; } public boolean useCache() { return false; } }; LibraryProperties(Composite parent) { mMatchIcon = AdtPlugin.getImageDescriptor("/icons/match.png").createImage(); //$NON-NLS-1$ mErrorIcon = AdtPlugin.getImageDescriptor("/icons/error.png").createImage(); //$NON-NLS-1$ // Layout has 2 column mTop = new Composite(parent, SWT.NONE); mTop.setLayout(new GridLayout(2, false)); mTop.setLayoutData(new GridData(GridData.FILL_BOTH)); mTop.setFont(parent.getFont()); mTop.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { mMatchIcon.dispose(); mErrorIcon.dispose(); } }); mTable = new Table(mTop, SWT.BORDER | SWT.FULL_SELECTION | SWT.SINGLE); mTable.setLayoutData(new GridData(GridData.FILL_BOTH)); mTable.setHeaderVisible(true); mTable.setLinesVisible(false); mTable.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { resetEnabled(); } }); final TableColumn column0 = new TableColumn(mTable, SWT.NONE); column0.setText("Reference"); final TableColumn column1 = new TableColumn(mTable, SWT.NONE); column1.setText("Project"); Composite buttons = new Composite(mTop, SWT.NONE); buttons.setLayout(new GridLayout()); buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL)); mProjectChooser = new ProjectChooserHelper(parent.getShell(), mFilter); mAddButton = new Button(buttons, SWT.PUSH | SWT.FLAT); mAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mAddButton.setText("Add..."); mAddButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { IJavaProject javaProject = mProjectChooser.chooseJavaProject(null /*projectName*/, "Please select a library project"); if (javaProject != null) { IProject iProject = javaProject.getProject(); IPath relativePath = iProject.getLocation().makeRelativeTo( mState.getProject().getLocation()); addItem(relativePath.toString(), iProject, -1); resetEnabled(); mMustSave = true; } } }); mRemoveButton = new Button(buttons, SWT.PUSH | SWT.FLAT); mRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mRemoveButton.setText("Remove"); mRemoveButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // selection is ensured and in single mode. TableItem selection = mTable.getSelection()[0]; ItemData data = (ItemData) selection.getData(); mItemDataList.remove(data); mTable.remove(mTable.getSelectionIndex()); resetEnabled(); mMustSave = true; } }); Label l = new Label(buttons, SWT.SEPARATOR | SWT.HORIZONTAL); l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mUpButton = new Button(buttons, SWT.PUSH | SWT.FLAT); mUpButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mUpButton.setText("Up"); mUpButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { int index = mTable.getSelectionIndex(); ItemData data = mItemDataList.remove(index); mTable.remove(index); // add at a lower index. addItem(data.relativePath, data.project, index - 1); // reset the selection mTable.select(index - 1); resetEnabled(); mMustSave = true; } }); mDownButton = new Button(buttons, SWT.PUSH | SWT.FLAT); mDownButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mDownButton.setText("Down"); mDownButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { int index = mTable.getSelectionIndex(); ItemData data = mItemDataList.remove(index); mTable.remove(index); // add at a higher index. addItem(data.relativePath, data.project, index + 1); // reset the selection mTable.select(index + 1); resetEnabled(); mMustSave = true; } }); adjustColumnsWidth(mTable, column0, column1); } /** * Sets or reset the content. * @param state the {@link ProjectState} to display. This is read-only. * @param propertiesWorkingCopy the working copy of {@link ProjectProperties} to modify. */ void setContent(ProjectState state, ProjectPropertiesWorkingCopy propertiesWorkingCopy) { mState = state; mPropertiesWorkingCopy = propertiesWorkingCopy; // reset content mTable.removeAll(); mItemDataList.clear(); // get the libraries and make a copy of the data we need. List<LibraryState> libs = state.getLibraries(); for (LibraryState lib : libs) { ProjectState libState = lib.getProjectState(); addItem(lib.getRelativePath(), libState != null ? libState.getProject() : null, -1); } mMustSave = false; resetEnabled(); } /** * Saves the state of the UI into the {@link ProjectProperties} object that was returned by * {@link #setContent}. * <p/>This does not update the {@link ProjectState} object that was provided, nor does it save * the new properties on disk. Saving the properties on disk, via * {@link ProjectPropertiesWorkingCopy#save()}, and updating the {@link ProjectState} instance, * via {@link ProjectState#reloadProperties()} must be done by the caller. * @return <code>true</code> if there was actually new data saved in the project state, false * otherwise. */ boolean save() { boolean mustSave = mMustSave; if (mMustSave) { // remove all previous library dependencies. Set<String> keys = mPropertiesWorkingCopy.keySet(); for (String key : keys) { if (key.startsWith(ProjectProperties.PROPERTY_LIB_REF)) { mPropertiesWorkingCopy.removeProperty(key); } } // now add the new libraries. int index = 1; for (ItemData data : mItemDataList) { mPropertiesWorkingCopy.setProperty(ProjectProperties.PROPERTY_LIB_REF + index++, data.relativePath); } } mMustSave = false; return mustSave; } /** * Enables or disables the whole widget. * @param enabled whether the widget must be enabled or not. */ void setEnabled(boolean enabled) { if (enabled == false) { mTable.setEnabled(false); mAddButton.setEnabled(false); mRemoveButton.setEnabled(false); mUpButton.setEnabled(false); mDownButton.setEnabled(false); } else { mTable.setEnabled(true); mAddButton.setEnabled(true); resetEnabled(); } } private void resetEnabled() { int index = mTable.getSelectionIndex(); mRemoveButton.setEnabled(index != -1); mUpButton.setEnabled(index > 0); mDownButton.setEnabled(index != -1 && index < mTable.getItemCount() - 1); } /** * Adds a new item and stores a {@link Stuff} into {@link #mStuff}. * * @param relativePath the relative path of the library entry * @param project the associated IProject * @param index if different than -1, the index at which to insert the item. */ private void addItem(String relativePath, IProject project, int index) { ItemData data = new ItemData(); data.relativePath = relativePath; data.project = project; TableItem item; if (index == -1) { mItemDataList.add(data); item = new TableItem(mTable, SWT.NONE); } else { mItemDataList.add(index, data); item = new TableItem(mTable, SWT.NONE, index); } item.setData(data); item.setText(0, data.relativePath); item.setImage( data.project != null ? mMatchIcon : mErrorIcon); item.setText(1, data.project != null ? data.project.getName() : "?"); } /** * Adds a listener to adjust the columns width when the parent is resized. * <p/> * If we need something more fancy, we might want to use this: * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co */ private void adjustColumnsWidth(final Table table, final TableColumn column0, final TableColumn column1) { // Add a listener to resize the column to the full width of the table table.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle r = table.getClientArea(); column0.setWidth(r.width * 50 / 100); // 50% column1.setWidth(r.width * 50 / 100); // 50% } }); } }