Home | History | Annotate | Download | only in properties
      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 = iProject.getLocation().makeRelativeTo(
    171                             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}.
    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 ProjectPropertiesWorkingCopy#save()}, and updating the {@link ProjectState} instance,
    274      * via {@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