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.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 
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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