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/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 a829057..7815410 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
@@ -11,18 +11,16 @@
 import static com.google.eclipse.protobuf.ui.preferences.paths.Messages.*;
 import static java.util.Arrays.asList;
 import static java.util.Collections.unmodifiableList;
-import static org.eclipse.jface.window.Window.OK;
-import static org.eclipse.xtext.util.Strings.isEmpty;
 
 import java.util.Collection;
 
-import org.eclipse.jface.dialogs.*;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.*;
-import org.eclipse.swt.layout.*;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.*;
 
-import com.google.eclipse.protobuf.ui.util.*;
+import com.google.eclipse.protobuf.ui.util.SwtEventListeners;
 
 /**
  * Editor where users can add/remove the directories to be used for URI resolution.
@@ -31,7 +29,6 @@
  */
 public class DirectoryNamesEditor extends Composite {
 
-  private final DirectoryNameValidator directoryNameValidator;
   private final SwtEventListeners eventListeners;
 
   private List lstDirectoryNames;
@@ -42,12 +39,10 @@
 
   private SelectionListener onChangeListener;
 
-  public DirectoryNamesEditor(Composite parent, DirectoryNameValidator directoryNameValidator,
-      SwtEventListeners eventListeners) {
+  public DirectoryNamesEditor(Composite parent, SwtEventListeners eventListeners) {
     super(parent, SWT.NONE);
 
     // generated by WindowBuilder
-    this.directoryNameValidator = directoryNameValidator;
     this.eventListeners = eventListeners;
     setLayout(new GridLayout(3, false));
 
@@ -93,16 +88,9 @@
     });
     btnAdd.addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
-        IInputValidator validator = new IInputValidator() {
-          public String isValid(String newText) {
-            String text = (newText == null) ? null : newText.trim();
-            if (isEmpty(text)) return errorEmptyDirectoryName;
-            return directoryNameValidator.validateDirectoryName(text);
-          }
-        };
-        InputDialog input = new InputDialog(getShell(), directoryNameInputTitle, directoryNameInputMessage, null, validator);
-        if (input.open() == OK) {
-          lstDirectoryNames.add(input.getValue().trim());
+        IncludeDialog dialog = new IncludeDialog(getShell(), includeDirectoryTitle);
+        if (dialog.open()) {
+          lstDirectoryNames.add(dialog.getEnteredPath());
         }
       }
     });
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
new file mode 100644
index 0000000..8c879f8
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/IncludeDialog.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+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 org.eclipse.xtext.util.Strings.isEmpty;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Dialog where users can enter/select a path (in the workspace or file system) to be included in resolution of imports.
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class IncludeDialog extends Dialog {
+
+  private final Shell parent;
+
+  private Shell shell;
+  private boolean result;
+  
+  private String enteredPath;
+  private boolean isWorkspacePath;
+
+  private Text txtPath;
+  private Button btnWorkspace;
+  private Button btnIsWorkspacePath;
+  private Button btnFileSystem;
+  private Button btnCancel;
+  private Button btnOk;
+
+  /**
+   * Creates a new </code>{@link IncludeDialog}</code>.
+   * @param parent a shell which will be the parent of the new instance.
+   * @param title the title of this dialog.
+   */
+  public IncludeDialog(Shell parent, String title) {
+    super(parent, SWT.NONE);
+    this.parent = parent;
+    getStyle();
+    setText(title);
+  }
+
+  /**
+   * Opens this dialog.
+   * @return {@code true} if the user made a selection and pressed "OK" or {@code false} if the user pressed "Cancel."
+   */
+  public boolean open() {
+    result = false;
+    shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE);
+    shell.setText(getText());
+    createAndCenterContent();
+    shell.open();
+    Display display = parent.getDisplay();
+    while (!shell.isDisposed()) 
+      if (!display.readAndDispatch()) display.sleep();
+    return result;
+  }
+
+  private void createAndCenterContent() {
+    shell.setLayout(new GridLayout(2, false));
+    
+    Label label = new Label(shell, SWT.NONE);
+    label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+    label.setText(includeDirectoryPrompt);
+    
+    txtPath = new Text(shell, SWT.BORDER);
+    txtPath.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+    
+    btnIsWorkspacePath = new Button(shell, SWT.CHECK);
+    btnIsWorkspacePath.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 1, 1));
+    btnIsWorkspacePath.setText(isWorkspacePathCheck);
+    
+    Composite composite = new Composite(shell, SWT.NONE);
+    composite.setLayout(new GridLayout(2, true));
+    new Label(composite, SWT.NONE);
+    
+    btnWorkspace = new Button(composite, SWT.NONE);
+    btnWorkspace.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnWorkspace.setText(browseWorkspace);
+    new Label(composite, SWT.NONE);
+    
+    btnFileSystem = new Button(composite, SWT.NONE);
+    btnFileSystem.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnFileSystem.setText(browseFileSystem);
+    
+    btnOk = new Button(composite, SWT.NONE);
+    btnOk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnOk.setEnabled(false);
+    btnOk.setText(ok);
+    
+    btnCancel = new Button(composite, SWT.NONE);
+    btnCancel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnCancel.setText(cancel);
+    
+    addEventListeners();
+    
+    shell.setDefaultButton(btnOk);
+    shell.pack();
+    
+    centerWindow();
+  }
+
+  private void addEventListeners() {
+    btnWorkspace.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        String path = showSelectWorkspaceDirectoryDialog(shell, txtPath.getText(), null);
+        if (path != null) {
+          txtPath.setText(path.trim());
+          btnIsWorkspacePath.setSelection(true);
+        }
+      }
+    });
+    btnOk.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        enteredPath = txtPath.getText().trim();
+        isWorkspacePath = btnIsWorkspacePath.getSelection();
+        result = true;
+        shell.dispose();
+      }
+    });
+    btnCancel.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        shell.dispose();
+      }
+    });
+    txtPath.addModifyListener(new ModifyListener() {
+      public void modifyText(ModifyEvent e) {
+        boolean hasText = !isEmpty(txtPath.getText().trim());
+        btnOk.setEnabled(hasText);
+      }
+    });
+  }
+
+  private void centerWindow() {
+    Rectangle parentRect = parent.getBounds();
+    Rectangle shellRect = shell.getBounds();
+    int x = parentRect.x + (parentRect.width - shellRect.width) / 2;
+    int y = parentRect.y + (parentRect.height - shellRect.height) / 2;
+    shell.setBounds(x, y, shellRect.width, shellRect.height);
+  }
+
+  /**
+   * Returns the path entered/selected by the user.
+   * @return the path entered/selected by the user.
+   */
+  public String getEnteredPath() {
+    return enteredPath;
+  }
+
+  /**
+   * Indicates whether the path returned by <code>{@link #getEnteredPath()}</code> is a workspace path.
+   * @return {@code true} if the entered path is a workspace path; {@code false} otherwise.
+   */
+  public boolean isWorkspacePath() {
+    return isWorkspacePath;
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java
index ef56359..a51464b 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java
@@ -16,6 +16,9 @@
 public class Messages extends NLS {
 
   public static String add;
+  public static String browseFileSystem;
+  public static String browseWorkspace;
+  public static String cancel;
   public static String directoryNameInputMessage;
   public static String directoryNameInputTitle;
   public static String down;
@@ -24,6 +27,10 @@
   public static String filesInMultipleDirectories;
   public static String filesInOneDirectoryOnly;
   public static String importedFilesPathResolution;
+  public static String includeDirectoryPrompt;
+  public static String includeDirectoryTitle;
+  public static String isWorkspacePathCheck;
+  public static String ok;
   public static String remove;
   public static String up;
 
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 0322aa3..fd8966b 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,4 +1,7 @@
 add=&Add
+browseFileSystem=File system...
+browseWorkspace=Workspace...
+cancel=Cancel
 directoryNameInputMessage=Enter directory name:
 directoryNameInputTitle=Path Resolution
 down=&Down
@@ -7,5 +10,9 @@
 filesInMultipleDirectories=Look for imported files in directories:
 filesInOneDirectoryOnly=One directory for all .proto files
 importedFilesPathResolution=Path resolution of imported files
+includeDirectoryPrompt=Directory:
+includeDirectoryTitle=Add directory path
+isWorkspacePathCheck=Is a workspace path
+ok=OK
 remove=&Remove
 up=&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 d1c3807..d1cd8c5 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
@@ -64,7 +64,7 @@
     btnMultipleFolders.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
     btnMultipleFolders.setText(filesInMultipleDirectories);
 
-    directoryNamesEditor = new DirectoryNamesEditor(grpResolutionOfImported, directoryNameValidator, eventListeners);
+    directoryNamesEditor = new DirectoryNamesEditor(grpResolutionOfImported, eventListeners);
     directoryNamesEditor.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
     new Label(contents, SWT.NONE);
 
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/BrowseWorkspaceDialogLauncher.java
new file mode 100644
index 0000000..53c492a
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/BrowseWorkspaceDialogLauncher.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.swt;
+
+import static com.google.eclipse.protobuf.ui.swt.Messages.*;
+import static org.eclipse.core.runtime.IStatus.ERROR;
+import static org.eclipse.core.runtime.Status.OK_STATUS;
+import static org.eclipse.jface.window.Window.OK;
+import static org.eclipse.ui.views.navigator.ResourceComparator.NAME;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
+import org.eclipse.ui.dialogs.ISelectionStatusValidator;
+import org.eclipse.ui.model.WorkbenchContentProvider;
+import org.eclipse.ui.model.WorkbenchLabelProvider;
+import org.eclipse.ui.views.navigator.ResourceComparator;
+
+/**
+ * Launches a dialog where users can browse a workspace.
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class BrowseWorkspaceDialogLauncher {
+
+  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("\"", "");
+    IPath path = new Path(currentPathText);
+    ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(shell, new WorkbenchLabelProvider(),
+        new WorkbenchContentProvider());
+    dialog.setInput(project == null ? workspaceRoot() : project);
+    dialog.setComparator(new ResourceComparator(NAME));
+    IResource container = null;
+    if (path.isAbsolute()) {
+      IContainer containers[] = workspaceRoot().findContainersForLocation(path);
+      if (containers != null && containers.length > 0) container = containers[0];
+    }
+    dialog.setInitialSelection(container);
+    dialog.setValidator(new ISelectionStatusValidator() {
+      public IStatus validate(Object[] selection) {
+        if (selection != null && selection.length > 0 && selection[0] instanceof IFile)
+          return new Status(ERROR, PLUGIN_ID, errorElementIsNotDirectory);
+        return OK_STATUS;
+      }
+    });
+    dialog.setTitle(browseWorkspaceFolderTitle);
+    dialog.setMessage(browseWorkspaceFolderPrompt);
+    if (dialog.open() != OK) return null;
+    IResource resource = (IResource) dialog.getFirstResult();
+    if (resource == null) return null;
+    StringBuilder b = new StringBuilder();
+    return b.append("${").append("workspace_loc:").append(resource.getFullPath()).append("}").toString();
+  }
+
+  private static IWorkspaceRoot workspaceRoot() {
+    return ResourcesPlugin.getWorkspace().getRoot();
+  }
+
+  private BrowseWorkspaceDialogLauncher() {}
+}
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
new file mode 100644
index 0000000..699b0cc
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.swt;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class Messages extends NLS {
+
+  public static String browseWorkspaceFolderPrompt;
+  public static String browseWorkspaceFolderTitle;
+  public static String errorElementIsNotDirectory;
+  
+  static {
+    Class<Messages> targetType = Messages.class;
+    NLS.initializeMessages(targetType.getName(), targetType);
+  }
+
+  private Messages() {}
+}
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
new file mode 100644
index 0000000..b65ca74
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/swt/Messages.properties
@@ -0,0 +1,3 @@
+browseWorkspaceFolderPrompt=Select a folder from workspace:
+browseWorkspaceFolderTitle=Folder selection
+errorElementIsNotDirectory=The selected element is not a directory.