In progress: [ Issue 40 ] Add support for import resolution across multiple folders
https://code.google.com/p/protobuf-dt/issues/detail?id=40

Adding support for using both workspace and file system paths for resolution of imports.
diff --git a/com.google.eclipse.protobuf.ui/icons/folder.gif b/com.google.eclipse.protobuf.ui/icons/folder.gif
new file mode 100644
index 0000000..03ee1dc
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/icons/folder.gif
Binary files differ
diff --git a/com.google.eclipse.protobuf.ui/icons/workspace.gif b/com.google.eclipse.protobuf.ui/icons/workspace.gif
new file mode 100644
index 0000000..b5a0012
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/icons/workspace.gif
Binary files differ
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java
index b7501fe..44be08f 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java
@@ -23,6 +23,7 @@
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
 
 import com.google.eclipse.protobuf.ui.preferences.PreferenceAndPropertyPage;
+import com.google.eclipse.protobuf.ui.swt.EventListeners;
 import com.google.eclipse.protobuf.ui.util.*;
 import com.google.inject.Inject;
 
@@ -58,7 +59,7 @@
   private Label lblOutputFolderRelative;
 
   @Inject private DirectoryNameValidator directoryNameValidator;
-  @Inject private SwtEventListeners eventListeners;
+  @Inject private EventListeners eventListeners;
 
   @Inject public CompilerPreferencePage(IPreferenceStoreAccess preferenceStoreAccess) {
     super(preferenceStoreAccess);
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
index 7815410..3d76b77 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
@@ -10,17 +10,18 @@
 
 import static com.google.eclipse.protobuf.ui.preferences.paths.Messages.*;
 import static java.util.Arrays.asList;
-import static java.util.Collections.unmodifiableList;
 
-import java.util.Collection;
+import java.util.*;
+import java.util.List;
 
+import org.eclipse.jface.viewers.*;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.*;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.*;
 
-import com.google.eclipse.protobuf.ui.util.SwtEventListeners;
+import com.google.eclipse.protobuf.ui.swt.EventListeners;
 
 /**
  * Editor where users can add/remove the directories to be used for URI resolution.
@@ -29,26 +30,39 @@
  */
 public class DirectoryNamesEditor extends Composite {
 
-  private final SwtEventListeners eventListeners;
+  private final EventListeners eventListeners;
 
-  private List lstDirectoryNames;
+  private final Table tblDirectoryNames;
+  private final TableViewer tblDirectoryNamesViewer;
   private final Button btnAdd;
   private final Button btnRemove;
   private final Button btnUp;
   private final Button btnDown;
 
+  private final LinkedList<String> directoryNames = new LinkedList<String>();
+  
   private SelectionListener onChangeListener;
 
-  public DirectoryNamesEditor(Composite parent, SwtEventListeners eventListeners) {
+  public DirectoryNamesEditor(Composite parent, EventListeners eventListeners) {
     super(parent, SWT.NONE);
 
     // generated by WindowBuilder
     this.eventListeners = eventListeners;
-    setLayout(new GridLayout(3, false));
+    setLayout(new GridLayout(2, false));
+    
+    tblDirectoryNames = new Table(this, SWT.BORDER | SWT.FULL_SELECTION);
+    tblDirectoryNames.setLinesVisible(true);
+    tblDirectoryNames.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
 
-    lstDirectoryNames = new List(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI);
-    lstDirectoryNames.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
-
+    tblDirectoryNamesViewer = new TableViewer(tblDirectoryNames);
+    tblDirectoryNamesViewer.setContentProvider(new IStructuredContentProvider() {
+      public Object[] getElements(Object inputElement) {
+        return (Object[]) inputElement;
+      }
+      public void dispose() {}
+      public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
+    });
+    
     Composite composite = new Composite(this, SWT.NONE);
     composite.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
     composite.setLayout(new GridLayout(1, false));
@@ -61,6 +75,8 @@
     btnRemove.setEnabled(false);
     btnRemove.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
     btnRemove.setText(remove);
+    
+    new Label(composite, SWT.NONE);
 
     btnUp = new Button(composite, SWT.NONE);
     btnUp.setEnabled(false);
@@ -76,30 +92,28 @@
   }
 
   private void addEventListeners() {
-    lstDirectoryNames.addDisposeListener(new DisposeListener() {
-      public void widgetDisposed(DisposeEvent e) {
-        lstDirectoryNames = null;
-      }
-    });
-    lstDirectoryNames.addSelectionListener(new SelectionAdapter() {
+    tblDirectoryNames.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
-        enableButtonsDependingOnListSelection();
+        enableButtonsDependingOnTableSelection();
       }
     });
     btnAdd.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
         IncludeDialog dialog = new IncludeDialog(getShell(), includeDirectoryTitle);
         if (dialog.open()) {
-          lstDirectoryNames.add(dialog.getEnteredPath());
+          directoryNames.add(dialog.getEnteredPath());
+          updateTable();
+          enableButtonsDependingOnTableSelection();
         }
       }
     });
     btnRemove.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
-        int index = lstDirectoryNames.getSelectionIndex();
+        int index = tblDirectoryNames.getSelectionIndex();
         if (index < 0) return;
-        lstDirectoryNames.remove(index);
-        enableButtonsDependingOnListSelection();
+        directoryNames.remove(index);
+        updateTable();
+        enableButtonsDependingOnTableSelection();
       }
     });
     btnUp.addSelectionListener(new SelectionAdapter() {
@@ -115,23 +129,24 @@
   }
 
   private void swap(boolean goUp) {
-    int index = lstDirectoryNames.getSelectionIndex();
+    int index = tblDirectoryNames.getSelectionIndex();
     if (index < 0) return;
     int target = goUp ? index - 1 : index + 1;
-    String[] selection = lstDirectoryNames.getSelection();
-    lstDirectoryNames.remove(index);
-    lstDirectoryNames.add(selection[0], target);
-    lstDirectoryNames.setSelection(target);
-    enableButtonsDependingOnListSelection();
+    TableItem[] selection = tblDirectoryNames.getSelection();
+    directoryNames.remove(index);
+    directoryNames.add(target, selection[0].getText());
+    updateTable();
+    tblDirectoryNames.setSelection(target);
+    enableButtonsDependingOnTableSelection();
   }
 
   /** {@inheritDoc} */
   @Override public void setEnabled(boolean enabled) {
     super.setEnabled(enabled);
-    lstDirectoryNames.setEnabled(enabled);
+    tblDirectoryNames.setEnabled(enabled);
     btnAdd.setEnabled(enabled);
     if (enabled) {
-      enableButtonsDependingOnListSelection();
+      enableButtonsDependingOnTableSelection();
     } else {
       btnRemove.setEnabled(false);
       btnUp.setEnabled(false);
@@ -139,9 +154,9 @@
     }
   }
 
-  private void enableButtonsDependingOnListSelection() {
-    int selectionIndex = lstDirectoryNames.getSelectionIndex();
-    int size = lstDirectoryNames.getItemCount();
+  private void enableButtonsDependingOnTableSelection() {
+    int selectionIndex = tblDirectoryNames.getSelectionIndex();
+    int size = tblDirectoryNames.getItemCount();
     boolean hasSelection = selectionIndex >= 0;
     btnRemove.setEnabled(hasSelection);
     boolean hasElements = size > 1;
@@ -149,12 +164,21 @@
     btnDown.setEnabled(hasElements && hasSelection && selectionIndex < size - 1);
   }
 
-  public java.util.List<String> directoryNames() {
-    return unmodifiableList(asList(lstDirectoryNames.getItems()));
+  public List<String> directoryNames() {
+    // return unmodifiableList(asList(tblDirectoryNames.getItems()));
+    return null;
   }
 
-  public void addDirectoryNames(Collection<String> directoryNames) {
-    for (String name : directoryNames) lstDirectoryNames.add(name.trim());
+  public void addDirectoryNames(Collection<String> names) {
+    directoryNames.clear();
+    directoryNames.addAll(names);
+    updateTable();
+  }
+  
+  private void updateTable() {
+    tblDirectoryNamesViewer.setInput(directoryNames.toArray());
+    if (tblDirectoryNames.getItemCount() > 0 && tblDirectoryNames.getSelectionCount() == 0)
+      tblDirectoryNames.setSelection(0);
   }
 
   public void onAddOrRemove(SelectionListener listener) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
index 8c879f8..f43483d 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
@@ -9,7 +9,7 @@
 package com.google.eclipse.protobuf.ui.preferences.paths;
 
 import static com.google.eclipse.protobuf.ui.preferences.paths.Messages.*;
-import static com.google.eclipse.protobuf.ui.swt.BrowseWorkspaceDialogLauncher.showSelectWorkspaceDirectoryDialog;
+import static com.google.eclipse.protobuf.ui.swt.SelectDirectoryDialogLauncher.*;
 import static org.eclipse.xtext.util.Strings.isEmpty;
 
 import org.eclipse.swt.SWT;
@@ -78,6 +78,7 @@
     
     txtPath = new Text(shell, SWT.BORDER);
     txtPath.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+    txtPath.setEditable(false);
     
     btnIsWorkspacePath = new Button(shell, SWT.CHECK);
     btnIsWorkspacePath.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 1, 1));
@@ -116,13 +117,22 @@
   private void addEventListeners() {
     btnWorkspace.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
-        String path = showSelectWorkspaceDirectoryDialog(shell, txtPath.getText(), null);
+        String path = showWorkspaceDirectoryDialog(shell, txtPath.getText(), null);
         if (path != null) {
           txtPath.setText(path.trim());
           btnIsWorkspacePath.setSelection(true);
         }
       }
     });
+    btnFileSystem.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        String path = showFileSystemFolderDialog(shell, txtPath.getText());
+        if (path != null) {
+          txtPath.setText(path.trim());
+          btnIsWorkspacePath.setSelection(false);
+        }
+      }
+    });
     btnOk.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
         enteredPath = txtPath.getText().trim();
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties
index fd8966b..9706e93 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties
@@ -1,10 +1,10 @@
-add=&Add
+add=&Add...
 browseFileSystem=File system...
 browseWorkspace=Workspace...
 cancel=Cancel
 directoryNameInputMessage=Enter directory name:
 directoryNameInputTitle=Path Resolution
-down=&Down
+down=Move Down
 errorEmptyDirectoryName=The name of the directory should not be empty
 errorNoDirectoryNames=Enter the names of the directories
 filesInMultipleDirectories=Look for imported files in directories:
@@ -15,4 +15,4 @@
 isWorkspacePathCheck=Is a workspace path
 ok=OK
 remove=&Remove
-up=&Up
+up=Move Up
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
index d1cd8c5..06b5246 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
@@ -15,13 +15,15 @@
 
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.*;
-import org.eclipse.swt.layout.*;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.*;
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
 
 import com.google.eclipse.protobuf.ui.preferences.PreferenceAndPropertyPage;
-import com.google.eclipse.protobuf.ui.util.*;
+import com.google.eclipse.protobuf.ui.swt.EventListeners;
 import com.google.inject.Inject;
 
 /**
@@ -39,8 +41,7 @@
   private Button btnMultipleFolders;
   private DirectoryNamesEditor directoryNamesEditor;
 
-  @Inject private DirectoryNameValidator directoryNameValidator;
-  @Inject private SwtEventListeners eventListeners;
+  @Inject private EventListeners eventListeners;
 
   @Inject public PathsPreferencePage(IPreferenceStoreAccess preferenceStoreAccess) {
     super(preferenceStoreAccess);
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/SwtEventListeners.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/EventListeners.java
similarity index 95%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/SwtEventListeners.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/EventListeners.java
index 4515cac..aec8794 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/SwtEventListeners.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/EventListeners.java
@@ -6,7 +6,7 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.util;
+package com.google.eclipse.protobuf.ui.swt;
 
 import java.util.Collection;
 
@@ -21,7 +21,7 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 @Singleton
-public class SwtEventListeners {
+public class EventListeners {
 
   /**
    * Adds the given <code>{@link SelectionListener}</code> to the given <code>{@link Button}</code>s.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.java
index 699b0cc..19cfa69 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.java
@@ -15,6 +15,7 @@
  */
 public class Messages extends NLS {
 
+  public static String browseFileSystemFolderPrompt;
   public static String browseWorkspaceFolderPrompt;
   public static String browseWorkspaceFolderTitle;
   public static String errorElementIsNotDirectory;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.properties
index b65ca74..5e80af3 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.properties
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.properties
@@ -1,3 +1,4 @@
+browseFileSystemFolderPrompt=Select a folder from file system:
 browseWorkspaceFolderPrompt=Select a folder from workspace:
 browseWorkspaceFolderTitle=Folder selection
 errorElementIsNotDirectory=The selected element is not a directory.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/BrowseWorkspaceDialogLauncher.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java
similarity index 76%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/BrowseWorkspaceDialogLauncher.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java
index 53c492a..046c2d9 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/BrowseWorkspaceDialogLauncher.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/SelectDirectoryDialogLauncher.java
@@ -16,6 +16,8 @@
 
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.DirectoryDialog;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
 import org.eclipse.ui.dialogs.ISelectionStatusValidator;
@@ -24,16 +26,16 @@
 import org.eclipse.ui.views.navigator.ResourceComparator;
 
 /**
- * Launches a dialog where users can browse a workspace.
+ * Launches dialog where users can select a directory (either in a workspace or the file system.)
  * 
  * @author alruiz@google.com (Alex Ruiz)
  */
-public class BrowseWorkspaceDialogLauncher {
+public class SelectDirectoryDialogLauncher {
 
   private static final String PLUGIN_ID = "com.google.eclipse.protobuf.ui";
 
-  public static String showSelectWorkspaceDirectoryDialog(Shell shell, String text, IProject project) {
-    String currentPathText = text.replaceAll("\"", "");
+  public static String showWorkspaceDirectoryDialog(Shell shell, String initialPath, IProject project) {
+    String currentPathText = initialPath.replaceAll("\"", "");
     IPath path = new Path(currentPathText);
     ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(shell, new WorkbenchLabelProvider(),
         new WorkbenchContentProvider());
@@ -60,10 +62,17 @@
     StringBuilder b = new StringBuilder();
     return b.append("${").append("workspace_loc:").append(resource.getFullPath()).append("}").toString();
   }
+  
+  public static String showFileSystemFolderDialog(Shell shell, String filterPath) {
+    DirectoryDialog dialog = new DirectoryDialog(shell, SWT.OPEN | SWT.APPLICATION_MODAL);
+    if (filterPath != null && filterPath.trim().length() != 0) dialog.setFilterPath(filterPath);
+    dialog.setMessage(browseFileSystemFolderPrompt);
+    return dialog.open();
+  }
 
   private static IWorkspaceRoot workspaceRoot() {
     return ResourcesPlugin.getWorkspace().getRoot();
   }
 
-  private BrowseWorkspaceDialogLauncher() {}
+  private SelectDirectoryDialogLauncher() {}
 }