Initial commit of Sigil contribution. (FELIX-1142)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@793581 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/sigil/org.cauldron.sigil.ui/.classpath b/sigil/org.cauldron.sigil.ui/.classpath
new file mode 100644
index 0000000..6f93ad7
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins">
+		<accessrules>
+			<accessrule kind="accessible" pattern="org/eclipse/pde/internal/ui/parts/*"/>
+		</accessrules>
+	</classpathentry>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sigil/org.cauldron.sigil.ui/.project b/sigil/org.cauldron.sigil.ui/.project
new file mode 100644
index 0000000..e736268
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.cauldron.sigil.ui</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/sigil/org.cauldron.sigil.ui/META-INF/MANIFEST.MF b/sigil/org.cauldron.sigil.ui/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..4b4103a
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/META-INF/MANIFEST.MF
@@ -0,0 +1,34 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Ui Plug-in
+Bundle-SymbolicName: org.cauldron.sigil.ui;singleton:=true
+Bundle-Version: 0.8.0.qualifier
+Bundle-Activator: org.cauldron.sigil.ui.SigilUI
+Bundle-Vendor: Paremus Ltd
+Require-Bundle: org.cauldron.sigil.core,
+ org.cauldron.bld.core,
+ org.cauldron.sigil.utils,
+ org.cauldron.sigil.search,
+ org.eclipse.core.runtime,
+ org.eclipse.debug.ui,
+ org.eclipse.jdt.core,
+ org.eclipse.jdt.ui,
+ org.eclipse.jface.text,
+ org.eclipse.pde.ui,
+ org.eclipse.ui,
+ org.eclipse.ui.editors,
+ org.eclipse.ui.forms,
+ org.eclipse.ui.ide,
+ org.eclipse.help,
+ org.eclipse.zest.core;bundle-version="[1,2)",
+ org.eclipse.zest.layouts;bundle-version="[1,2)",
+ org.eclipse.ltk.core.refactoring
+Eclipse-LazyStart: true
+Export-Package: org.cauldron.sigil.actions,
+ org.cauldron.sigil.handlers,
+ org.cauldron.sigil.ui,
+ org.cauldron.sigil.ui.util,
+ org.cauldron.sigil.ui.wizard.project,
+ org.cauldron.sigil.ui.wizard.repository
+Bundle-Localization: plugin
+
diff --git a/sigil/org.cauldron.sigil.ui/build.properties b/sigil/org.cauldron.sigil.ui/build.properties
new file mode 100644
index 0000000..df5bef4
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/build.properties
@@ -0,0 +1,12 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               etc/,\
+               plugin.xml,\
+               icons/,\
+               resources/,\
+               toc.xml,\
+               html/,\
+               plugin.properties,\
+               schema/
diff --git a/sigil/org.cauldron.sigil.ui/plugin.properties b/sigil/org.cauldron.sigil.ui/plugin.properties
new file mode 100644
index 0000000..3e6d20d
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/plugin.properties
@@ -0,0 +1,5 @@
+repositoriesPrefPage=Repositories
+newtonRepoPrefs=Newton Repository
+librariesPrefsPage=OSGi Libraries
+commandConvertProject=Convert Project To Sigil Project
+commandRefreshClasspath=Refresh bundle classpath
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/plugin.xml b/sigil/org.cauldron.sigil.ui/plugin.xml
new file mode 100644
index 0000000..d8b96a5
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/plugin.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+  
+  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.
+-->
+<?eclipse version="3.2"?>
+<plugin>
+   <extension-point id="org.cauldron.sigil.ui.repositorywizard" name="Repository Wizard" schema="schema/org.cauldron.sigil.ui.repositorywizard.exsd"/>
+      <extension
+         point="org.eclipse.ui.startup">
+      <startup class="org.cauldron.sigil.startup.SigilStartup"/>
+   </extension>
+   <extension
+         point="org.eclipse.ui.newWizards">
+      <category
+            id="org.cauldron.sigil.newWizardCategory"
+            name="Sigil"/>
+      <wizard
+            category="org.cauldron.sigil.newWizardCategory"
+            class="org.cauldron.sigil.ui.wizard.project.SigilProjectWizard"
+            finalPerspective="org.cauldron.sigil.ui.perspective"
+            icon="etc/images/newton.png"
+            id="org.cauldron.sigil.editors.newProjectWizard"
+            name="Sigil Project"
+            project="true"/>
+   </extension>
+   <extension
+         point="org.eclipse.ui.preferencePages">
+      <page
+            class="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            id="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            name="Sigil"/>
+      <!--page
+            category="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            class="org.cauldron.sigil.ui.preferences.LibraryPreferencePage"
+            id="org.cauldron.sigil.ui.libraryPreferences"
+            name="%librariesPrefsPage" >
+      </page-->
+      <page
+            category="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            class="org.cauldron.sigil.ui.preferences.repository.RepositoriesPreferencePage"
+            id="org.cauldron.sigil.ui.preferences.repositoriesPreferencePage"
+            name="%repositoriesPrefPage">
+      </page>
+      <page
+            category="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            class="org.cauldron.sigil.ui.preferences.ExcludedResourcesPrefsPage"
+            id="org.cauldron.sigil.ui.preferences.excludedResources"
+            name="Excluded Resources">
+      </page>
+      <page
+            category="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            class="org.cauldron.sigil.ui.preferences.VersionsPreferencePage"
+            id="org.cauldron.sigil.ui.preferences.VersionsPreferencePage"
+            name="Version Handling">
+      </page>
+      <page
+            category="org.cauldron.sigil.ui.preferences.SigilPreferencePage"
+            class="org.cauldron.sigil.ui.preferences.installs.OSGiInstallsPreferencePage"
+            id="org.cauldron.sigil.ui.preferences.newtonInstalls"
+            name="Newton Installs">
+      </page>
+   </extension>
+   <extension
+         point="org.eclipse.ui.editors">
+      <editor
+            class="org.cauldron.sigil.ui.editors.project.SigilProjectEditorPart"
+            default="true"
+            filenames="sigil.properties"
+            icon="etc/images/newton.png"
+            id="org.cauldron.sigil.editors.SigilProjectEditor"
+            name="Sigil Project Editor"/>
+   </extension>
+   <extension
+         point="org.eclipse.jdt.core.classpathContainerInitializer">
+      <classpathContainerInitializer
+            class="org.cauldron.sigil.classpath.SigilClasspathContainerInitializer"
+            id="org.cauldron.sigil.core.classpathContainer"/>
+   </extension>
+   <extension
+         point="org.eclipse.jdt.ui.classpathContainerPage">
+      <classpathContainerPage
+            class="org.cauldron.sigil.classpath.SigilLibraryPage"        id="org.cauldron.sigil.core.classpathContainer"
+            name="Sigil Library"/>
+   </extension>
+   <extension
+         point="org.eclipse.ui.perspectives">
+      <perspective
+            class="org.cauldron.sigil.ui.perspective.SigilPerspectiveFactory"
+            icon="etc/images/newton.png"
+            id="org.cauldron.sigil.ui.perspective"
+            name="Sigil">
+      </perspective>
+   </extension>
+   <extension
+         point="org.eclipse.jdt.ui.quickFixProcessors">
+      <quickFixProcessor
+            class="org.cauldron.sigil.ui.quickfix.ImportQuickFixProcessor"
+            id="org.cauldron.sigil.ui.quickFixProcessor1">
+      </quickFixProcessor>
+   </extension>
+   <extension
+         point="org.eclipse.ui.views">
+      <view
+            allowMultiple="false"
+            category="org.cauldron.sigil.ui.views"
+            class="org.cauldron.sigil.ui.views.resolution.BundleResolverView"
+            icon="icons/jar_web_obj.png"
+            id="org.cauldron.sigil.ui.bundleDependencyView"
+            name="Bundle Dependency View"
+            restorable="true">
+      </view>
+      <view
+            allowMultiple="false"
+            category="org.cauldron.sigil.ui.views"
+            class="org.cauldron.sigil.ui.views.RepositoryViewPart"
+            icon="icons/jars_obj.png"
+            id="org.cauldron.sigil.ui.repositoryBrowser"
+            name="Repository Browser"
+            restorable="true">
+      </view>
+      <category
+            id="org.cauldron.sigil.ui.views"
+            name="Sigil">
+      </category>
+   </extension>
+   <extension
+         point="org.cauldron.sigil.ui.repositorywizard">
+      <wizard
+            class="org.cauldron.sigil.ui.internal.repository.FileSystemRepositoryWizard"
+            repository="org.cauldron.sigil.core.file">
+      </wizard>
+   </extension>
+   <extension
+         point="org.eclipse.ui.commands">
+      <command
+            id="org.cauldron.sigil.ui.commands.renameComposite"
+            name="Rename Composite">
+      </command>
+   </extension>
+   <extension
+         point="org.eclipse.ltk.core.refactoring.refactoringContributions">
+      <contribution
+            class="org.cauldron.sigil.ui.refactor.RenameCompositeRefactoring"
+            id="org.cauldron.sigil.ui.rename.composite">
+      </contribution>
+   </extension>
+   <extension
+         point="org.eclipse.ui.propertyPages">
+      <page
+            class="org.cauldron.sigil.ui.preferences.project.ProjectPropertyPage"
+            id="org.cauldron.sigil.ui.projectpage"
+            name="Repositories">
+         <filter
+               name="projectNature"
+               value="org.cauldron.sigil.core.newtonnature"/>
+         <enabledWhen>
+            <adapt
+                  type="org.eclipse.core.resources.IProject">
+            </adapt>
+         </enabledWhen>
+      </page>
+   </extension>
+   <extension
+         point="org.eclipse.ui.menus">
+      <menuContribution
+            locationURI="popup:org.eclipse.ui.popup.any">
+         <menu
+               icon="etc/images/newton.png"
+               label="Sigil">
+            <command
+                  commandId="org.cauldron.sigil.ui.convertproject"
+                  label="Convert Project"
+                  style="push">
+               <visibleWhen
+                     checkEnabled="true">
+               </visibleWhen>
+            </command>
+         </menu>
+         <separator
+               name="org.cauldron.sigil.ui.separator"
+               visible="true">
+         </separator>
+         <command
+               commandId="org.cauldron.sigil.ui.refreshclasspath"
+               icon="icons/refreshBundle.png"
+               label="Refresh bundle classpath"
+               style="push">
+               <visibleWhen
+                     checkEnabled="true">
+               </visibleWhen>
+            </command>
+      </menuContribution>
+   </extension>
+   <extension
+         point="org.eclipse.ui.commands">
+      <command
+            id="org.cauldron.sigil.ui.convertproject"
+            name="%commandConvertProject">
+      </command>
+      <command
+            id="org.cauldron.sigil.ui.refreshclasspath"
+            name="%commandRefreshClasspath">
+      </command>
+   </extension>
+   <extension
+         point="org.eclipse.ui.commandImages">
+      <image
+            commandId="org.cauldron.sigil.ui.convertproject"
+            icon="etc/images/newton.png">
+      </image>
+      <image
+            commandId="org.cauldron.sigil.ui.refreshclasspath"
+            icon="etc/images/newton.png">
+      </image>
+   </extension>
+   
+   <extension
+         point="org.eclipse.ui.handlers">
+      <handler
+            class="org.cauldron.sigil.handlers.project.ConvertProjectHandler"
+            commandId="org.cauldron.sigil.ui.convertproject">
+         <activeWhen>
+            <with
+                  variable="selection">
+               <iterate
+                     ifEmpty="false"
+                     operator="and">
+					<instanceof
+	                           value="org.eclipse.core.resources.IProject">
+	                </instanceof>
+		            <test
+						property="org.cauldron.sigil.isSigilProject"
+						value="false">
+					</test>
+               </iterate>
+            </with>
+         </activeWhen>
+      </handler>      
+      <handler
+            class="org.cauldron.sigil.handlers.project.RefreshSigilClasspathHandler"
+            commandId="org.cauldron.sigil.ui.refreshclasspath">
+         <activeWhen>
+            <with
+                  variable="selection">
+               <iterate
+                     ifEmpty="false"
+                     operator="and">
+					<instanceof
+	                           value="org.eclipse.core.resources.IProject">
+	                </instanceof>
+		            <test
+						property="org.cauldron.sigil.isSigilProject"
+						value="true">
+					</test>
+               </iterate>
+            </with>
+         </activeWhen>
+      </handler>      
+   </extension>   
+</plugin>
diff --git a/sigil/org.cauldron.sigil.ui/resources/org/cauldron/sigil/ui/SigilUI.properties b/sigil/org.cauldron.sigil.ui/resources/org/cauldron/sigil/ui/SigilUI.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/resources/org/cauldron/sigil/ui/SigilUI.properties
diff --git a/sigil/org.cauldron.sigil.ui/schema/org.cauldron.sigil.ui.repositorywizard.exsd b/sigil/org.cauldron.sigil.ui/schema/org.cauldron.sigil.ui.repositorywizard.exsd
new file mode 100644
index 0000000..e0ba4a4
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/schema/org.cauldron.sigil.ui.repositorywizard.exsd
@@ -0,0 +1,127 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+  
+  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.
+-->
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.cauldron.sigil.ui" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appinfo>
+         <meta.schema plugin="org.cauldron.sigil.ui" id="org.cauldron.sigil.ui.repositorywizard" name="Repository Wizard"/>
+      </appinfo>
+      <documentation>
+         [Enter description of this extension point.]
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appinfo>
+            <meta.element />
+         </appinfo>
+      </annotation>
+      <complexType>
+         <sequence minOccurs="0" maxOccurs="unbounded">
+            <element ref="wizard"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="wizard">
+      <complexType>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="java" basedOn="org.cauldron.sigil.ui.wizard.repository.RepositoryWizard:"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+         <attribute name="repository" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="since"/>
+      </appinfo>
+      <documentation>
+         [Enter the first release in which this extension point appears.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="examples"/>
+      </appinfo>
+      <documentation>
+         [Enter extension point usage example here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="apiinfo"/>
+      </appinfo>
+      <documentation>
+         [Enter API information here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="implementation"/>
+      </appinfo>
+      <documentation>
+         [Enter information about supplied implementation of this extension point.]
+      </documentation>
+   </annotation>
+
+
+</schema>
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/DisplayAction.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/DisplayAction.java
new file mode 100644
index 0000000..852f972
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/DisplayAction.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.actions;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+public abstract class DisplayAction extends Action {
+
+	public DisplayAction() {
+		super();
+	}
+
+	public DisplayAction(String text) {
+		super(text);
+	}
+
+	public DisplayAction(String text, ImageDescriptor image) {
+		super(text, image);
+	}
+
+	public DisplayAction(String text, int style) {
+		super(text, style);
+	}
+
+	protected Display findDisplay() {
+		Display d = Display.getCurrent();
+		
+		if ( d == null ) {
+			d = Display.getDefault();
+		}
+		
+		return d;
+	}
+
+	protected void runInUI(final Shell shell, final WorkspaceModifyOperation op) {
+	}
+
+	protected void info(final Shell shell, final String msg) {
+		shell.getDisplay().asyncExec( new Runnable() {
+			public void run() {
+				MessageDialog.openInformation(shell, "Information", msg );
+			}
+		} );
+	}
+
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/PruneProjectDependenciesAction.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/PruneProjectDependenciesAction.java
new file mode 100644
index 0000000..86886bd
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/PruneProjectDependenciesAction.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.actions;
+
+import java.util.Collection;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.util.JavaHelper;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.util.ResourceReviewDialog;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+import org.eclipse.ui.progress.IProgressService;
+
+public class PruneProjectDependenciesAction extends DisplayAction {
+	
+	private ISigilProjectModel project;
+	
+	public PruneProjectDependenciesAction(ISigilProjectModel project) {
+		this.project = project;
+	}
+
+	@Override
+	public void run() {
+ 		final Shell shell = findDisplay().getActiveShell();
+ 		
+		Job job = new Job("Resolving imports") {
+
+			@Override
+			protected IStatus run(IProgressMonitor monitor) {
+				Collection<IModelElement> unused = JavaHelper.findUnusedReferences(project, monitor);
+				
+				if ( unused.isEmpty() ) {
+					info( shell, "No unused references found" );
+				}
+				else {
+					final ResourceReviewDialog<IModelElement> dialog = new ResourceReviewDialog<IModelElement>(shell, "Review Unused Imports", unused);
+					
+					shell.getDisplay().asyncExec( new Runnable() {
+						public void run() {
+							if ( dialog.open() == Window.OK ) {
+								WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+									@Override
+									protected void execute(IProgressMonitor monitor) throws CoreException {
+										for ( IModelElement e : dialog.getResources() ) {
+											if ( !project.getBundle().getBundleInfo().removeChild(e) ) {
+												SigilCore.error( "Failed to remove " + e );
+											}
+										}
+										
+										project.save(monitor);
+									}			
+								};
+								
+								SigilUI.runWorkspaceOperation(op, shell);
+							}
+						}
+					});
+				}
+				
+				return Status.OK_STATUS;
+			}
+		};
+		
+		job.schedule();
+		
+		IProgressService p = PlatformUI.getWorkbench().getProgressService();
+		p.showInDialog(shell, job);				
+	}	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/RefreshRepositoryAction.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/RefreshRepositoryAction.java
new file mode 100644
index 0000000..39d3abd
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/RefreshRepositoryAction.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.actions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+public class RefreshRepositoryAction extends DisplayAction {
+	private final IRepositoryModel[] model;
+
+	public RefreshRepositoryAction(IRepositoryModel... model) {
+		super( "Refresh repository");
+		this.model = model;
+	}
+	
+	@Override
+	public void run() {
+		WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+
+			@Override
+			protected void execute(IProgressMonitor monitor)
+					throws CoreException, InvocationTargetException,
+					InterruptedException {
+				boolean changed = false;
+				
+				for ( IBundleRepository b : SigilCore.getGlobalRepositoryManager().getRepositories() ) {
+					for ( IRepositoryModel m : model ) {
+						if ( b.getId().equals( m.getId() ) ) {
+							b.refresh();
+							changed = true;
+						}
+					}
+				}
+				
+				if ( changed ) {
+					List<ISigilProjectModel> projects = SigilCore.getRoot().getProjects();
+					SubMonitor sub = SubMonitor.convert(monitor, projects.size() * 10);
+					for ( ISigilProjectModel p : projects ) {
+						p.resetClasspath(sub.newChild(10));
+					}
+				}
+			}
+		};
+		
+		SigilUI.runWorkspaceOperation(op, null);
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/ResolveProjectDependenciesAction.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/ResolveProjectDependenciesAction.java
new file mode 100644
index 0000000..1f1ec1e
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/actions/ResolveProjectDependenciesAction.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.actions;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.util.JavaHelper;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.util.ResourceReviewDialog;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+import org.eclipse.ui.progress.IProgressService;
+
+public class ResolveProjectDependenciesAction extends DisplayAction {
+
+	private ISigilProjectModel project;
+	private boolean review;
+
+	public ResolveProjectDependenciesAction(ISigilProjectModel project, boolean review) {
+		this.project = project;
+		this.review = review;
+	}
+	
+	public void run() {
+		final Shell shell = findDisplay().getActiveShell();
+		
+		Job job = new Job("Resolving dependencies" ) {
+			@Override
+			protected IStatus run(IProgressMonitor monitor) {
+				monitor.beginTask("", IProgressMonitor.UNKNOWN);
+				
+				List<IPackageImport> imports = JavaHelper.findRequiredImports(project, monitor);
+				
+				if ( imports.isEmpty() ) {
+					info( shell, "No new dependencies found" );
+				}
+				else {
+					Collections.sort(imports, new Comparator<IPackageImport>() {
+						public int compare(IPackageImport o1, IPackageImport o2) {
+							int i = o1.getPackageName().compareTo(o2.getPackageName());
+
+							// shouldn't get more than one import for same package
+							// but may as well sort if do...
+							if ( i == 0 ) {
+								i = o1.getVersions().getFloor().compareTo(o2.getVersions().getFloor() );
+							}
+							
+							return i;
+						}
+					});
+					
+					final ResourceReviewDialog<IPackageImport> dialog = new ResourceReviewDialog<IPackageImport>(shell, "Review New Dependencies", imports);					
+					shell.getDisplay().asyncExec( new Runnable() {
+						public void run() {
+							if ( !review || dialog.open() == Window.OK ) {
+								WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+									@Override
+									protected void execute(IProgressMonitor monitor) throws CoreException {
+										for ( IPackageImport pi : dialog.getResources() ) {
+											project.getBundle().getBundleInfo().addImport(pi);
+										}
+										
+										project.save(monitor);
+									}			
+								};
+								
+								SigilUI.runWorkspaceOperation(op, shell);
+							}
+						}
+					} );
+				}
+				
+				return Status.OK_STATUS;
+			}
+		};
+		
+		job.schedule();
+		
+		IProgressService p = PlatformUI.getWorkbench().getProgressService();
+		p.showInDialog(shell, job);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilClassPathContainer.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilClassPathContainer.java
new file mode 100644
index 0000000..69d8894
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilClassPathContainer.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.classpath;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.job.ThreadProgressMonitor;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IClasspathContainer;
+import org.eclipse.jdt.core.IClasspathEntry;
+
+/**
+ * @author dave
+ *
+ */
+public class SigilClassPathContainer implements IClasspathContainer {
+
+    private IClasspathEntry[] entries;
+    private ISigilProjectModel sigil;
+
+    public SigilClassPathContainer(ISigilProjectModel sigil) {
+    	this.sigil = sigil;
+	}
+
+	/* (non-Javadoc)
+     * @see org.eclipse.jdt.core.IClasspathContainer#getClasspathEntries()
+     */
+    public IClasspathEntry[] getClasspathEntries() {
+        if ( entries == null ) {
+        	buildClassPathEntries();
+        }
+        
+        return entries;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jdt.core.IClasspathContainer#getDescription()
+     */
+    public String getDescription() {
+        return "Bundle Context Classpath";
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jdt.core.IClasspathContainer#getKind()
+     */
+    public int getKind() {
+        return K_SYSTEM;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jdt.core.IClasspathContainer#getPath()
+     */
+    public IPath getPath() {
+        return new Path( SigilCore.CLASSPATH_CONTAINER_PATH );
+    }
+
+    /**
+     * @return
+     * @throws CoreException 
+     * @throws CoreException 
+     */
+    private void buildClassPathEntries() {
+		try {
+			IProgressMonitor monitor = ThreadProgressMonitor.getProgressMonitor();
+			entries = sigil.findExternalClasspath(monitor).toArray( new IClasspathEntry[0] );
+		} catch (CoreException e) {
+    		SigilCore.error( "Failed to build classpath entries", e);
+		}
+		finally {
+			if ( entries == null ) {
+	    		entries = new IClasspathEntry[] {};
+			}
+		}
+    }
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilClasspathContainerInitializer.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilClasspathContainerInitializer.java
new file mode 100644
index 0000000..f0229ee
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilClasspathContainerInitializer.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.classpath;

+

+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.job.*;
+import org.eclipse.core.runtime.CoreException;

+import org.eclipse.core.runtime.IPath;

+import org.eclipse.core.runtime.IProgressMonitor;

+import org.eclipse.core.runtime.jobs.Job;

+import org.eclipse.jdt.core.ClasspathContainerInitializer;

+import org.eclipse.jdt.core.IClasspathContainer;

+import org.eclipse.jdt.core.IJavaProject;

+import org.eclipse.jdt.core.JavaCore;

+

+public class SigilClasspathContainerInitializer extends ClasspathContainerInitializer {

+

+	public SigilClasspathContainerInitializer() {

+		// TODO Auto-generated constructor stub

+	}

+

+	@Override
+	public boolean canUpdateClasspathContainer(IPath containerPath,
+			IJavaProject project) {
+		return true;
+	}
+
+	@Override
+	public void requestClasspathContainerUpdate(IPath containerPath, IJavaProject project, IClasspathContainer containerSuggestion)
+			throws CoreException {
+		ISigilProjectModel sigil = SigilCore.create(project.getProject());
+		
+        IClasspathContainer sigilContainer = new SigilClassPathContainer(sigil);
+		
+		IJavaProject[] affectedProjects = new IJavaProject[] { project };
+        
+        IClasspathContainer[] respectiveContainers = new IClasspathContainer[] { sigilContainer };
+        
+        IProgressMonitor monitor = ThreadProgressMonitor.getProgressMonitor();
+        
+        if ( monitor == null ) { 
+        	monitor = Job.getJobManager().createProgressGroup();
+        }
+
+        JavaCore.setClasspathContainer(containerPath, affectedProjects, respectiveContainers , monitor);
+	}
+
+	@Override

+	public void initialize(IPath containerPath, IJavaProject project)

+			throws CoreException {
+		ISigilProjectModel sigil = SigilCore.create(project.getProject());
+		

+        IClasspathContainer sigilContainer = new SigilClassPathContainer(sigil);

+		

+		IJavaProject[] affectedProjects = new IJavaProject[] { project };

+        

+        IClasspathContainer[] respectiveContainers = new IClasspathContainer[] { sigilContainer };

+        

+        IProgressMonitor monitor = Job.getJobManager().createProgressGroup();

+

+        JavaCore.setClasspathContainer(containerPath, affectedProjects, respectiveContainers , monitor);

+	}

+

+}

diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilLibraryPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilLibraryPage.java
new file mode 100644
index 0000000..cd73c8f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/classpath/SigilLibraryPage.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.classpath;
+
+import org.cauldron.sigil.SigilCore;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.ui.wizards.IClasspathContainerPage;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+public class SigilLibraryPage extends WizardPage implements IClasspathContainerPage {
+
+    private IClasspathEntry classpathEntry;
+
+    public SigilLibraryPage() {
+        super( "Sigil" );
+    }
+    
+    public void createControl(Composite parent) {
+        setTitle("Sigil Library");
+
+        Label label = new Label(parent, SWT.NONE);
+        label.setText("Press Finish to add the Sigil Library");
+        label.setFont(parent.getFont());
+
+        setControl(label);
+    }
+
+    public boolean finish() {
+        classpathEntry = JavaCore.newContainerEntry(new Path(SigilCore.CLASSPATH_CONTAINER_PATH));
+        return true;
+    }
+
+    public boolean isPageComplete() {
+        return true;
+    }
+
+    public IClasspathEntry getSelection() {
+        return classpathEntry;
+    }
+
+    public void setSelection(IClasspathEntry containerEntry) {
+        this.classpathEntry = containerEntry;
+    }
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/AbstractResourceCommandHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/AbstractResourceCommandHandler.java
new file mode 100644
index 0000000..94184a9
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/AbstractResourceCommandHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers;
+
+import org.eclipse.core.commands.AbstractHandler;
+
+public abstract class AbstractResourceCommandHandler extends AbstractHandler {
+
+	protected abstract IResourceCommandHandler getResourceCommandHandler();
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/EditorResourceCommandHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/EditorResourceCommandHandler.java
new file mode 100644
index 0000000..8bbcc6f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/EditorResourceCommandHandler.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+public abstract class EditorResourceCommandHandler extends AbstractResourceCommandHandler {
+	
+	public Object execute(ExecutionEvent event) throws ExecutionException {
+		final Shell shell = HandlerUtil.getActiveShell(event);
+		final IEditorPart editorPart = HandlerUtil.getActiveEditor(event);
+		if ( editorPart != null ) {
+			IEditorInput editorInput = editorPart.getEditorInput();
+			
+			if(!(editorInput instanceof IFileEditorInput)) {
+				throw new ExecutionException("Editor input must be a file");
+			}
+			IFileEditorInput fileInput = (IFileEditorInput) editorInput;
+			
+			try {
+				// Save the editor content (if dirty)
+				IRunnableWithProgress saveOperation = new IRunnableWithProgress() {
+					public void run(IProgressMonitor monitor) throws InvocationTargetException,
+					InterruptedException {
+						if(editorPart.isDirty()) {
+							if(MessageDialog.openQuestion(shell, "Save File", "The file contents must be saved before the command can be executed. Do you wish to save now?")) {
+								editorPart.doSave(monitor);
+							} else {
+								throw new InterruptedException();
+							}
+						}
+					}
+				};
+				new ProgressMonitorDialog(shell).run(false, true, saveOperation);
+	
+				// Execute on the file
+				IFile file = fileInput.getFile();
+				getResourceCommandHandler().execute(new IResource[] { file }, event);
+			} catch (InvocationTargetException e) {
+				throw new ExecutionException("Error saving file", e.getTargetException());
+			} catch (InterruptedException e) {
+				// Exit the command silently
+			}
+		}			
+		return null;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/IResourceCommandHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/IResourceCommandHandler.java
new file mode 100644
index 0000000..a1eb206
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/IResourceCommandHandler.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IResource;
+
+public interface IResourceCommandHandler {
+	
+	Object execute(IResource[] resources, ExecutionEvent event) throws ExecutionException;
+	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/SelectionResourceCommandHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/SelectionResourceCommandHandler.java
new file mode 100644
index 0000000..b96cc73
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/SelectionResourceCommandHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+public abstract class SelectionResourceCommandHandler extends AbstractResourceCommandHandler {
+	
+	public final Object execute(ExecutionEvent event) throws ExecutionException {
+		ISelection selection = HandlerUtil.getCurrentSelection(event);
+		
+		Object[] objectArray = ((IStructuredSelection) selection).toArray();
+		IResource[] resourceArray = new IResource[objectArray.length];
+		System.arraycopy(objectArray, 0, resourceArray, 0, objectArray.length);
+		
+		return getResourceCommandHandler().execute(resourceArray, event);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/ConvertProjectCommandHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/ConvertProjectCommandHandler.java
new file mode 100644
index 0000000..8e93c4e
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/ConvertProjectCommandHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers.project;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.handlers.IResourceCommandHandler;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+public class ConvertProjectCommandHandler implements IResourceCommandHandler {
+
+	public Object execute(IResource[] resources, ExecutionEvent event)
+			throws ExecutionException {
+		for ( IResource r : resources ) {
+			final IProject project = (IProject) r;
+			if ( project != null ) {
+				WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+					@Override
+					protected void execute(IProgressMonitor monitor)
+							throws CoreException {
+						SigilCore.makeSigilProject(project, monitor);
+						IJavaProject java = JavaCore.create(project);
+						ISigilProjectModel sigil = SigilCore.create(project);
+						IClasspathEntry[] entries = java.getRawClasspath();
+						for (int i = 0; i < entries.length; i++) {
+							IClasspathEntry entry = entries[i];
+							if(entry.getEntryKind()==IClasspathEntry.CPE_SOURCE) {
+								String encodedClasspath = sigil.getJavaModel().encodeClasspathEntry( entry );
+								sigil.getBundle().addClasspathEntry(encodedClasspath);
+							}
+						}
+						sigil.save(monitor);
+					}				
+				};
+				SigilUI.runWorkspaceOperation(op, null);
+			}
+		}
+		
+		return null;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/ConvertProjectHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/ConvertProjectHandler.java
new file mode 100644
index 0000000..bf012f8
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/ConvertProjectHandler.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers.project;
+
+import org.cauldron.sigil.handlers.IResourceCommandHandler;
+import org.cauldron.sigil.handlers.SelectionResourceCommandHandler;
+
+public class ConvertProjectHandler extends SelectionResourceCommandHandler {
+
+	@Override
+	protected IResourceCommandHandler getResourceCommandHandler() {
+		return new ConvertProjectCommandHandler();
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/RefreshSigilClasspathCommandHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/RefreshSigilClasspathCommandHandler.java
new file mode 100644
index 0000000..0df3770
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/RefreshSigilClasspathCommandHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers.project;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.handlers.IResourceCommandHandler;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+public class RefreshSigilClasspathCommandHandler implements
+		IResourceCommandHandler {
+
+	public Object execute(IResource[] resources, ExecutionEvent event)
+			throws ExecutionException {
+		try {
+			for ( IResource res : resources ) {
+				IProject p = (IProject) res;
+				final ISigilProjectModel model = SigilCore.create(p);
+				
+				WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+					@Override
+					protected void execute(IProgressMonitor monitor)
+							throws CoreException, InvocationTargetException,
+							InterruptedException {
+						model.resetClasspath(monitor);
+					}
+				};
+				
+				SigilUI.runWorkspaceOperation(op, null);
+			}
+		} catch (CoreException e) {
+			SigilCore.error( "Failed to create sigil project for refresh action", e );
+		}
+		return null;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/RefreshSigilClasspathHandler.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/RefreshSigilClasspathHandler.java
new file mode 100644
index 0000000..6dc20bb
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/handlers/project/RefreshSigilClasspathHandler.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.handlers.project;
+
+import org.cauldron.sigil.handlers.IResourceCommandHandler;
+import org.cauldron.sigil.handlers.SelectionResourceCommandHandler;
+
+public class RefreshSigilClasspathHandler extends SelectionResourceCommandHandler {
+
+	@Override
+	protected IResourceCommandHandler getResourceCommandHandler() {
+		return new RefreshSigilClasspathCommandHandler();
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/startup/SigilStartup.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/startup/SigilStartup.java
new file mode 100644
index 0000000..3f437bd
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/startup/SigilStartup.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.startup;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.job.ResolveProjectsJob;
+import org.cauldron.sigil.repository.IRepositoryChangeListener;
+import org.cauldron.sigil.repository.RepositoryChangeEvent;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.ui.IStartup;
+
+public class SigilStartup implements IStartup {
+
+	public void earlyStartup() {
+		// Create a task to run the resolver
+		final Runnable resolver = new Runnable() {
+			public void run() {
+				IWorkspace workspace = ResourcesPlugin.getWorkspace();
+				ResolveProjectsJob job = new ResolveProjectsJob(workspace);
+				job.setSystem(true);
+				job.schedule();
+			}
+		};
+		
+		// Register a repository change listener to re-run the resolver when repository changes
+		SigilCore.getGlobalRepositoryManager().addRepositoryChangeListener(new IRepositoryChangeListener() {
+			public void repositoryChanged(RepositoryChangeEvent event) {
+				resolver.run();
+			}
+		});
+
+		// Run the resolver now
+		resolver.run();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/SigilUI.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/SigilUI.java
new file mode 100644
index 0000000..a3c70d5
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/SigilUI.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui;
+
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import org.cauldron.sigil.SigilCore;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class SigilUI extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "org.cauldron.sigil.ui";
+
+	public static final String REPOSITORY_WIZARD_EXTENSION_POINT_ID = "org.cauldron.sigil.ui.repositorywizard";
+
+	public static final String PREF_NOPROMPT_INSTALL_COMPOSITE_WITH_ERRORS = "nopromptInstallCompositeError";
+	public static final String PREF_INSTALL_COMPOSITE_WITH_ERRORS_ANSWER = "answerInstallCompositeError";
+
+	public static final String ID_REPOSITORY_VIEW = "org.cauldron.sigil.ui.repositoryBrowser";
+	public static final String ID_DEPENDENCY_VIEW = "org.cauldron.sigil.ui.bundleDependencyView";
+	public static final String ID_INSTANCES_VIEW = "org.cauldron.sigil.runtime.newtonInstancesView";
+
+	// The shared instance
+	private static SigilUI plugin;
+	
+	/**
+	 * The constructor
+	 */
+	public SigilUI() {
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		SigilCore.getDefault();
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static SigilUI getDefault() {
+		return plugin;
+	}
+
+	public static ResourceBundle getResourceBundle() {
+		return ResourceBundle.getBundle( "resources." + SigilUI.class.getName(), Locale.getDefault(), SigilUI.class.getClassLoader() );
+	}
+
+	public static void runWorkspaceOperation(IRunnableWithProgress op, Shell shell) {
+		if ( shell == null ) {
+			shell = getActiveWorkbenchShell();
+		}
+		try {
+			new ProgressMonitorDialog(shell).run(true, true, op);
+		} catch (InvocationTargetException e) {
+			SigilCore.error( "Workspace operation failed", e);
+		} catch (InterruptedException e1) {
+			SigilCore.log( "Workspace operation interrupted");
+		}
+	}
+	
+	public static void runWorkspaceOperationSync(IRunnableWithProgress op, Shell shell) throws Throwable {
+		if ( shell == null ) {
+			shell = getActiveWorkbenchShell();
+		}
+		try {
+			new ProgressMonitorDialog(shell).run(false, true, op);
+		} catch (InvocationTargetException e) {
+			throw e.getCause();
+		} catch (InterruptedException e1) {
+			SigilCore.log( "Workspace operation interrupted");
+		}
+	}
+	public static IWorkbenchWindow getActiveWorkbenchWindow() {
+		return getDefault().getWorkbench().getActiveWorkbenchWindow();
+	}
+	
+	public static Shell getActiveWorkbenchShell() {
+		final Shell[] shell = new Shell[1];
+		runInUISync( new Runnable() {
+			public void run() {
+				shell[0] = getActiveDisplay().getActiveShell();
+			}
+		});
+		return shell[0];
+	}
+
+	public static Display getActiveDisplay() {
+		Display d = Display.getCurrent();
+		
+		if ( d == null ) {
+			d = Display.getDefault();
+		}
+
+		return d;
+	}
+
+	public static void runInUI(Runnable runnable) {
+		getActiveDisplay().asyncExec(runnable);
+	}
+
+	public static void runInUISync(Runnable runnable) {
+		getActiveDisplay().syncExec(runnable);
+	}
+	
+	public static Image cacheImage(String path, ClassLoader classLoader) {
+		ImageRegistry registry = SigilUI.getDefault().getImageRegistry();
+		
+		Image image = registry.get( path );
+		
+		if ( image == null ) {
+			image = loadImage( path, classLoader );
+			// XXX-FIXME-XXX add null image
+			if ( image != null ) {
+				registry.put( path, image);
+			}
+		}
+		
+		return image; 
+	}
+
+	private static Image loadImage(String resource, ClassLoader loader) {
+		InputStream in = loader.getResourceAsStream( resource );
+		if ( in != null ) {
+			ImageData data = new ImageData( in );
+			return new Image( SigilUI.getActiveDisplay(), data );
+		}
+		else {
+			return null;
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/completion/CompletionProposal.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/completion/CompletionProposal.java
new file mode 100644
index 0000000..b8e7d2e
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/completion/CompletionProposal.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.completion;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+
+class CompletionProposal implements ICompletionProposal {
+
+	private int fReplacementOffset;
+	private int fReplacementLength;
+	private int fCursorPosition;
+	private String fReplacementString;
+	private String fAdditionalProposalInfo;
+	private IContextInformation fContextInformation;
+	private String fDisplayString;
+	private Image fImage;
+	
+	/**
+	 * Creates a new completion proposal based on the provided information. The replacement string is
+	 * considered being the display string too. All remaining fields are set to <code>null</code>.
+	 *
+	 * @param replacementString the actual string to be inserted into the document
+	 * @param replacementOffset the offset of the text to be replaced
+	 * @param replacementLength the length of the text to be replaced
+	 * @param cursorPosition the position of the cursor following the insert relative to replacementOffset
+	 */
+	public CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition) {
+		this(replacementString, replacementOffset, replacementLength, cursorPosition, null, null, null, null);
+	}
+
+	/**
+	 * Creates a new completion proposal. All fields are initialized based on the provided information.
+	 *
+	 * @param replacementString the actual string to be inserted into the document
+	 * @param replacementOffset the offset of the text to be replaced
+	 * @param replacementLength the length of the text to be replaced
+	 * @param cursorPosition the position of the cursor following the insert relative to replacementOffset
+	 * @param image the image to display for this proposal
+	 * @param displayString the string to be displayed for the proposal
+	 * @param contextInformation the context information associated with this proposal
+	 * @param additionalProposalInfo the additional information associated with this proposal
+	 */
+	public CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo) {
+		fReplacementString= replacementString;
+		fReplacementOffset= replacementOffset;
+		fReplacementLength= replacementLength;
+		fCursorPosition= cursorPosition;
+		fImage= image;
+		fDisplayString= displayString;
+		fContextInformation= contextInformation;
+		fAdditionalProposalInfo= additionalProposalInfo;
+	}
+	
+	
+	/* (non-Javadoc)
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposal#apply(org.eclipse.jface.text.IDocument)
+	 */
+	public void apply( IDocument document ) {
+		try {
+			document.replace(fReplacementOffset, fReplacementLength, fReplacementString);
+		} catch (BadLocationException x) {
+			// ignore
+		}
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposal#getAdditionalProposalInfo()
+	 */
+	public String getAdditionalProposalInfo() {
+		return fAdditionalProposalInfo;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposal#getContextInformation()
+	 */
+	public IContextInformation getContextInformation() {
+		return fContextInformation;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposal#getDisplayString()
+	 */
+	public String getDisplayString() {
+		if (fDisplayString != null)
+			return fDisplayString;
+		return fReplacementString;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposal#getImage()
+	 */
+	public Image getImage() {
+		return fImage;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.jface.text.contentassist.ICompletionProposal#getSelection(org.eclipse.jface.text.IDocument)
+	 */
+	public Point getSelection( IDocument document ) {
+		return new Point(fReplacementOffset + fCursorPosition, 0);
+	}
+	
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/AbstractResourceSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/AbstractResourceSection.java
new file mode 100644
index 0000000..2ba92ca
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/AbstractResourceSection.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.io.File;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.ui.forms.IManagedForm;
+
+public abstract class AbstractResourceSection extends SigilSection implements IResourceChangeListener, ICheckStateListener  {
+
+	protected static final Object[] EMPTY = new Object[]{};
+	protected Tree tree;
+	protected CheckboxTreeViewer viewer;
+	private IWorkspace workspace;
+
+	public AbstractResourceSection(SigilPage page, Composite parent,
+			ISigilProjectModel project) throws CoreException {
+		super(page, parent, project);
+	}
+
+	@Override
+	public void initialize(IManagedForm form) {
+		super.initialize(form);
+		viewer.setAllChecked(false);
+		viewer.setGrayedElements( EMPTY );
+		refreshSelections();
+		viewer.refresh();		
+	}
+	
+	@Override
+	public void refresh() {
+		refreshSelections();
+		super.refresh();
+	}
+
+	public void checkStateChanged(CheckStateChangedEvent event) {
+		handleStateChanged( (IResource) event.getElement(), event.getChecked(), true, true );
+	}
+
+	public void resourceChanged(IResourceChangeEvent event) {
+		if ( !getSection().getDisplay().isDisposed() ) {
+			getSection().getDisplay().asyncExec( new Runnable() {
+				public void run() {
+					if ( getSection().isVisible() ) {
+						viewer.refresh();
+					}
+				}			
+			});
+		}
+	}
+	
+	protected void startWorkspaceListener(IWorkspace workspace) {
+		this.workspace = workspace;
+		workspace.addResourceChangeListener(this);
+	}
+	
+	@Override
+	public void dispose() {
+		workspace.removeResourceChangeListener(this);
+	}
+
+	protected abstract void refreshSelections();
+	
+	protected abstract void syncResourceModel(IResource element, boolean checked);
+
+	protected IResource findResource(IPath path) {
+		IProject project2 = getProjectModel().getProject();
+		File f = project2.getLocation().append(path).toFile();
+		
+		if ( f.exists() ) {
+			try {
+				if ( f.isFile() ) {
+					return project2.getFile(path);
+				}
+				else {
+					return project2.getFolder(path);
+				}
+			}
+			catch (IllegalArgumentException e) {
+				SigilCore.error( "Unknown path " + path );
+				return null;
+			}
+		}
+		else {
+			SigilCore.error( "Unknown file " + f );
+			return null;
+		}
+	}	
+	
+
+	protected void handleStateChanged(IResource element, boolean checked, boolean recurse,
+			boolean sync) {
+				if ( element instanceof IContainer ) {
+					setParentsGrayChecked(element, checked);
+					if ( recurse ) {
+						recursiveCheck( (IContainer) element, checked, sync );
+					}
+				}
+				else {
+					setParentsGrayChecked(element, checked);
+				}
+				
+				if ( sync ) {
+					syncResourceModel( element, checked );
+				}
+			}
+
+	private void recursiveCheck(IContainer element, final boolean checked, final boolean sync) {
+		try {
+			element.accept( new IResourceVisitor() {
+				public boolean visit(IResource resource) throws CoreException {
+					viewer.setChecked(resource, checked);
+					if ( sync ) {
+						syncResourceModel(resource, checked);
+					}
+					return true;
+				}
+			} );
+		}
+		catch ( CoreException e ) {
+			DebugPlugin.log( e.getStatus() );
+		}
+	}
+
+	private void setParentsGrayChecked(IResource r, boolean checked) {
+		while ( r.getParent() != null ) {
+			r = r.getParent();
+			if ( (viewer.getGrayed(r) && viewer.getChecked(r)) == checked) {
+				break;
+			}
+			if ( viewer.getChecked(r) == checked ) {
+				viewer.setGrayed( r, !checked );
+			}
+			else {
+				viewer.setGrayChecked(r, checked);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/BundleDependencySection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/BundleDependencySection.java
new file mode 100644
index 0000000..0750e0f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/BundleDependencySection.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+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.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+public abstract class BundleDependencySection extends SigilSection {
+
+	private Set<? extends IModelElement> unresolvedElements;
+	
+	protected ProjectTableViewer viewer;
+	private Button add;
+	private Button edit;
+	private Button remove;
+
+	public BundleDependencySection(SigilPage page, Composite parent, ISigilProjectModel project, Set<IModelElement> unresolvedElements) throws CoreException {
+		super(page, parent, project);
+		viewer.setUnresolvedElements(unresolvedElements);
+	}
+	
+	public BundleDependencySection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		this(page, parent, project, null);
+	}
+	
+	protected abstract String getTitle();
+	
+	protected abstract Label createLabel(Composite parent, FormToolkit toolkit);
+	
+	protected abstract IContentProvider getContentProvider();
+
+	protected abstract void handleAdd();
+	
+	protected abstract void handleEdit();
+	
+	protected abstract void handleRemoved();
+	
+	@Override
+	public void refresh() {
+		super.refresh();
+		viewer.refresh();
+	}
+	
+	protected ISelection getSelection() {
+		return viewer.getSelection();
+	}
+
+	@Override
+	protected void createSection(Section section, FormToolkit toolkit) {
+		setTitle( getTitle() );
+		
+		Composite body = createGridBody(2, false, toolkit);
+		
+        Label label = createLabel(body, toolkit);
+        
+        label.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, true, true, 2, 1 ) );
+        
+		Table bundleTable = toolkit.createTable(body, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.BORDER);
+		GridData data = new GridData( GridData.FILL_BOTH );
+		data.heightHint = 600;
+        bundleTable.setLayoutData( data );
+        bundleTable.setLinesVisible(false);        
+        createButtons(body, toolkit);        
+        createViewer( bundleTable );
+	}
+	
+	protected void createButtons(Composite body, FormToolkit toolkit) {
+	    Composite buttons = toolkit.createComposite(body);
+	    TableWrapLayout layout = new TableWrapLayout();
+	    layout.numColumns = 1;
+	    layout.topMargin = 0;
+	    layout.leftMargin = 0;
+	    layout.rightMargin = 0;
+	    layout.bottomMargin = 0;
+	    buttons.setLayout( layout );
+	    GridData data = new GridData();
+	    data.verticalAlignment = SWT.TOP;
+	    buttons.setLayoutData(data);
+	    
+	    add = toolkit.createButton(buttons, "Add", SWT.NULL);
+	    add.setLayoutData( new TableWrapData( TableWrapData.FILL ) );
+	    add.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleAdd();
+			}        	
+	    } );
+	    
+	    edit = toolkit.createButton(buttons, "Edit", SWT.NULL);
+	    edit.setLayoutData( new TableWrapData( TableWrapData.FILL ) );
+	    edit.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleEdit();
+			}        	
+	    } );
+	    
+	    remove = toolkit.createButton(buttons, "Remove", SWT.NULL);
+	    remove.setLayoutData( new TableWrapData( TableWrapData.FILL ) );
+	    remove.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleRemoved();
+			}
+	    } );
+	    
+	    setSelected(false);
+	}
+
+	protected void createViewer(Table bundleTable) {
+	    viewer = new ProjectTableViewer(bundleTable);
+	    viewer.setContentProvider(getContentProvider());  
+	    viewer.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				setSelected(!event.getSelection().isEmpty());
+			}
+	    });
+	}
+
+	private void setSelected(boolean selected) {
+		edit.setEnabled(selected);
+		remove.setEnabled(selected);
+	}
+
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ClasspathSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ClasspathSection.java
new file mode 100644
index 0000000..311acca
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ClasspathSection.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.util.JavaHelper;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.cauldron.sigil.ui.util.BackgroundLoadingSelectionDialog;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.util.IFilter;
+import org.cauldron.sigil.ui.util.ModelLabelProvider;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+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.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+public class ClasspathSection extends SigilSection {
+
+	private ProjectTableViewer viewer;
+	private final Comparator<IClasspathEntry> CLASSPATH_COMPARATOR = new Comparator<IClasspathEntry>() {
+		public int compare(IClasspathEntry o1, IClasspathEntry o2) {
+			return compareClasspaths(o1, o2);
+		}
+	};
+
+	public ClasspathSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}
+	
+	@Override
+	protected void createSection(Section section, FormToolkit toolkit) {
+		setTitle( "Classpath");
+		
+		Composite body = createGridBody(2, false, toolkit);
+		
+        Label label = toolkit.createLabel( body, "Specify the internal classpath of this bundle." );
+        label.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, true, true, 2, 1 ) );
+		
+		Table bundleTable = toolkit.createTable(body, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.BORDER);
+		GridData tableLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
+		tableLayoutData.heightHint = 150;
+        bundleTable.setLayoutData(tableLayoutData);
+        
+        createButtons(body, toolkit);
+        createViewer( bundleTable );
+	}
+
+	private void createButtons(Composite body, FormToolkit toolkit) {
+        Composite buttons = toolkit.createComposite(body);
+        TableWrapLayout layout = new TableWrapLayout();
+        layout.numColumns = 1;
+        layout.topMargin = 0;
+        layout.leftMargin = 0;
+        layout.rightMargin = 0;
+        layout.bottomMargin = 0;
+        buttons.setLayout( layout );
+        
+        Button add = toolkit.createButton(buttons, "Add", SWT.NULL);
+        add.setLayoutData( new TableWrapData( TableWrapData.FILL ) );
+        add.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleAdd();
+			}        	
+        } );
+        
+        Button remove = toolkit.createButton(buttons, "Remove", SWT.NULL);
+        remove.setLayoutData( new TableWrapData( TableWrapData.FILL ) );
+        remove.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleRemoved();
+			}
+        } );
+	}
+
+	private void createViewer(Table bundleTable) {
+        viewer = new ProjectTableViewer(bundleTable);
+        viewer.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				ArrayList<IClasspathEntry> cp = new ArrayList<IClasspathEntry>();
+				for (IClasspathEntry cpe : JavaHelper.findClasspathEntries(getBundle())) {
+					cp.add(cpe);
+				}
+				
+				Collections.sort(cp, new Comparator<IClasspathEntry>() {
+					public int compare(IClasspathEntry o1, IClasspathEntry o2) {
+						return o1.toString().compareTo(o2.toString());
+					}
+				});
+				return cp.toArray();
+			}
+        });    
+        viewer.setComparator(new ViewerComparator() {
+			@Override
+			public int category(Object element) {
+				return index((IClasspathEntry) element);
+			}
+        });
+	}
+	
+	protected ISigilBundle getBundle() {
+		return getProjectModel().getBundle();
+	}
+
+	private void handleAdd() {
+		try {
+			BackgroundLoadingSelectionDialog<IClasspathEntry> dialog = new BackgroundLoadingSelectionDialog<IClasspathEntry>(getSection().getShell(), "Classpath Entry:", true);
+			
+			dialog.setDescriptor(new IElementDescriptor<IClasspathEntry>() {
+				public String getName(IClasspathEntry element) {
+					return element.getPath().toString();
+				}
+			
+				public String getLabel(IClasspathEntry element) {
+					return getName(element);
+				}
+			});
+			
+			dialog.setLabelProvider(new ModelLabelProvider());
+			
+			dialog.setFilter(new IFilter<IClasspathEntry>() {
+				public boolean select(IClasspathEntry cp) {
+					switch ( cp.getEntryKind() ) {
+					case IClasspathEntry.CPE_LIBRARY:
+					case IClasspathEntry.CPE_VARIABLE:
+					case IClasspathEntry.CPE_SOURCE:
+						return !getBundle().getClasspathEntrys().contains(encode(cp));
+					default:
+						return false;
+					}
+				}
+			});
+			
+			dialog.setComparator(CLASSPATH_COMPARATOR);
+			
+			IClasspathEntry[] classpath = getProjectModel().getJavaModel().getRawClasspath();
+			dialog.addElements(Arrays.asList(classpath));
+			if ( dialog.open() == Window.OK ) {
+				List<IClasspathEntry> selectedElements = dialog.getSelectedElements();
+				
+				Object[] added = selectedElements.toArray();
+				for (IClasspathEntry entry : selectedElements) {
+					getBundle().addClasspathEntry(encode(entry));
+				}
+				viewer.add(added);
+				viewer.refresh();
+				markDirty();
+			}
+		} catch (JavaModelException e) {
+			ErrorDialog.openError(getSection().getShell(), "Error", null, e.getStatus());
+		}
+	}
+
+	private int compareClasspaths(IClasspathEntry o1, IClasspathEntry o2) {
+		if ( o1.getEntryKind() == o2.getEntryKind() ) {
+			ModelLabelProvider mlp = viewer.getLabelProvider();
+			return mlp.getText(o1).compareTo(mlp.getText(o2));
+		}
+		else {
+			int i1 = index(o1);
+			int i2 = index(o2);
+			
+			if ( i1 < i2 ) {
+				return -1;
+			}
+			else {
+				return 1;
+			}
+		}
+	}
+
+	private static int index(IClasspathEntry o1) {
+		switch ( o1.getEntryKind() ) {
+		case IClasspathEntry.CPE_SOURCE: return 0;
+		case IClasspathEntry.CPE_PROJECT: return 1;
+		case IClasspathEntry.CPE_LIBRARY: return 2;
+		case IClasspathEntry.CPE_VARIABLE: return 3;
+		case IClasspathEntry.CPE_CONTAINER: return 4;
+		default: throw new IllegalStateException( "Unknown classpath entry type " + o1);
+		}
+	}
+
+	private String encode(IClasspathEntry cp) {
+		return getProjectModel().getJavaModel().encodeClasspathEntry(cp).trim();
+	}
+
+	@SuppressWarnings("unchecked")
+	private void handleRemoved() {
+		IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IClasspathEntry> i = selection.iterator(); i.hasNext(); ) {			
+				getBundle().removeClasspathEntry(getProjectModel().getJavaModel().encodeClasspathEntry(i.next()).trim());
+			}		
+			viewer.remove(selection.toArray());
+			markDirty();
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContainerTreeProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContainerTreeProvider.java
new file mode 100644
index 0000000..5d091fb
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContainerTreeProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.ui.util.DefaultTreeContentProvider;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+
+public class ContainerTreeProvider extends DefaultTreeContentProvider {
+
+	private static final Object[] EMPTY = new Object[] {};
+	
+	public Object[] getChildren(Object parentElement) {
+		if ( parentElement instanceof IContainer ) {
+			IContainer f = (IContainer) parentElement;
+			try {
+				return f.members();
+			} catch (CoreException e) {
+				DebugPlugin.log( e.getStatus() );
+			}
+		}
+		return EMPTY;	
+	}
+
+	public Object getParent(Object element) {
+		IResource r = (IResource) element;
+		return r.getParent();
+	}
+
+	public boolean hasChildren(Object element) {
+		if ( element instanceof IContainer ) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+
+	public Object[] getElements(Object inputElement) {
+		IContainer container = (IContainer) inputElement;
+		return getChildren(container);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContentSummarySection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContentSummarySection.java
new file mode 100644
index 0000000..7d1fbcb
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContentSummarySection.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Hyperlink;
+import org.eclipse.ui.forms.widgets.Section;
+
+public class ContentSummarySection extends SigilSection {
+
+	public ContentSummarySection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}
+
+	@Override
+	protected void createSection(Section section, FormToolkit toolkit) {
+		setTitle( "Project Content");
+		
+		Composite body = createTableWrapBody(2, toolkit);
+		Hyperlink link = toolkit.createHyperlink( body, "Contents:", SWT.NONE );
+		link.setHref( ContentsForm.PAGE_ID );
+		link.addHyperlinkListener(this);
+		toolkit.createLabel( body, "Manage the content that this bundle provides." );
+		
+		link = toolkit.createHyperlink( body, "Dependencies:", SWT.NONE );
+		link.setHref( DependenciesForm.PAGE_ID );
+		link.addHyperlinkListener(this);
+		toolkit.createLabel( body, "Manage the dependencies that this bundle needs to run." );
+		
+		link = toolkit.createHyperlink( body, "Exports:", SWT.NONE );
+		link.setHref( ExportsForm.PAGE_ID );
+		link.addHyperlinkListener(this);
+		toolkit.createLabel( body, "Manage the resources that this bundle exports." );
+	}
+
+	@Override
+	public void linkActivated(HyperlinkEvent e) {
+		getPage().getEditor().setActivePage( (String) e.getHref() );
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContentsForm.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContentsForm.java
new file mode 100644
index 0000000..62d58a8
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ContentsForm.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormEditor;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+public class ContentsForm extends SigilPage  {
+
+	public static final String PAGE_ID = "contents";
+	
+	private ISigilProjectModel project;
+	
+	public ContentsForm(FormEditor editor, ISigilProjectModel project) {
+		super(editor, PAGE_ID, "Contents");
+		this.project = project;
+	}
+	
+    @Override
+    protected void createFormContent(IManagedForm managedForm) {
+        FormToolkit toolkit = managedForm.getToolkit();
+        
+        ScrolledForm form = managedForm.getForm();
+        form.setText( "Contents" );
+        
+        Composite body = form.getBody();        
+        TableWrapLayout layout = new TableWrapLayout();
+        layout.bottomMargin = 10;
+        layout.topMargin = 5;
+        layout.leftMargin = 10;
+        layout.rightMargin = 10;
+        layout.numColumns = 2;
+        layout.horizontalSpacing = 10;
+        body.setLayout(layout);
+        body.setLayoutData(new TableWrapData(TableWrapData.FILL));
+        
+        Composite top = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        top.setLayout(layout);
+        TableWrapData data = new TableWrapData(TableWrapData.FILL_GRAB);
+        data.colspan = 2;
+        top.setLayoutData(data);
+        
+        Composite left = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        left.setLayout(layout);
+        left.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
+        
+        Composite right = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        right.setLayout(layout);
+        right.setLayoutData( new TableWrapData( TableWrapData.FILL_GRAB) );
+                
+        try {
+	        ClasspathSection classpath = new ClasspathSection( this, top, project );
+	        managedForm.addPart( classpath );
+	        
+	        ResourceBuildSection runtimeBuild = new ResourceBuildSection( this, left, project );
+	        managedForm.addPart( runtimeBuild );
+	        
+	        DownloadSection download = new DownloadSection( this, right, project );
+	        managedForm.addPart( download );	        
+        }
+        catch (CoreException e) {
+        	SigilCore.error( "Failed to create contents form", e);
+        }
+    }
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DependenciesForm.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DependenciesForm.java
new file mode 100644
index 0000000..1c28ba5
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DependenciesForm.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormEditor;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+public class DependenciesForm extends SigilPage  {
+
+	public static final String PAGE_ID = "dependencies";
+	
+	private final ISigilProjectModel project;
+	private final Set<IModelElement> unresolvedElements;
+
+	private ImportPackagesSection importPackages;
+	private RequiresBundleSection requiresSection;
+
+	
+	public DependenciesForm(FormEditor editor, ISigilProjectModel project, Set<IModelElement> unresolved) {
+		super(editor, PAGE_ID, "Dependencies");
+		this.project = project;
+		this.unresolvedElements = unresolved;
+	}
+	
+    @Override
+	public boolean canLeaveThePage() {
+    	return !isDirty();
+	}
+
+	@Override
+    protected void createFormContent(IManagedForm managedForm) {
+        FormToolkit toolkit = managedForm.getToolkit();
+        
+        ScrolledForm form = managedForm.getForm();
+        form.setText( "Dependencies" );
+        
+        Composite body = form.getBody();        
+        TableWrapLayout layout = new TableWrapLayout();
+        layout.bottomMargin = 10;
+        layout.topMargin = 5;
+        layout.leftMargin = 10;
+        layout.rightMargin = 10;
+        layout.numColumns = 2;
+        layout.horizontalSpacing = 10;
+        body.setLayout(layout);
+        body.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
+        
+        Composite left = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        left.setLayout(layout);
+        TableWrapData layoutData = new TableWrapData(TableWrapData.FILL_GRAB);
+        layoutData.rowspan = 2;
+        left.setLayoutData(layoutData);
+        
+        Composite right = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        right.setLayout(layout);
+        right.setLayoutData(new TableWrapData( TableWrapData.FILL_GRAB) );
+        
+        Composite bottom = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        bottom.setLayout(layout);
+        bottom.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
+                
+        try {
+	        importPackages = new ImportPackagesSection( this, left, project, unresolvedElements );
+	        managedForm.addPart( importPackages );
+	        
+	        requiresSection = new RequiresBundleSection(this, right, project, unresolvedElements );
+	        managedForm.addPart(requiresSection);
+	        
+	        DependencyManagementSection depMgmt = new DependencyManagementSection(this, bottom, project);
+	        managedForm.addPart(depMgmt);
+        }
+        catch (CoreException e) {
+        	SigilCore.error( "Failed to create dependencies form", e);
+        }
+    }
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DependencyManagementSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DependencyManagementSection.java
new file mode 100644
index 0000000..aadf990
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DependencyManagementSection.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.common.VersionRangeBoundingRule;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.cauldron.sigil.ui.util.AccumulatorAdapter;
+import org.cauldron.sigil.ui.util.ExportedPackageFinder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IImportDeclaration;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.AbstractFormPart;
+import org.eclipse.ui.forms.IFormPart;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Hyperlink;
+import org.eclipse.ui.forms.widgets.Section;
+
+public class DependencyManagementSection extends SigilSection {
+
+	private Hyperlink hypConvertRBtoIP;
+
+	public DependencyManagementSection(SigilPage page, Composite parent,
+			ISigilProjectModel project) throws CoreException {
+		super(page, parent, project);
+	}
+
+	protected void createSection(Section section, FormToolkit toolkit) throws CoreException {
+		setTitle("Dependency Management");
+		
+		Composite body = createGridBody(1, false, toolkit);
+		
+		hypConvertRBtoIP = toolkit.createHyperlink(body, "Convert Required Bundles to Imported Packages", SWT.NONE);
+		hypConvertRBtoIP.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		
+		hypConvertRBtoIP.addHyperlinkListener(new HyperlinkAdapter() {
+			public void linkActivated(HyperlinkEvent e) {
+				run();
+			}
+		});
+	}
+	
+	protected void run() {
+		final Map<String, IPackageExport> exports = new HashMap<String, IPackageExport>();
+		final Set<String> imports = new HashSet<String>();
+		
+		// Find all exports
+		final ExportedPackageFinder exportFinder = new ExportedPackageFinder(getProjectModel(), new AccumulatorAdapter<IPackageExport>() {
+			public void addElements(Collection<? extends IPackageExport> elements) {
+				for (IPackageExport export : elements) {
+					exports.put(export.getPackageName(), export);
+				}
+			}
+		});
+		Job findExportsJob = new Job("Find exports") {
+			protected IStatus run(IProgressMonitor monitor) {
+				return exportFinder.run(monitor);
+			}
+		};
+		findExportsJob.setUser(true);
+		findExportsJob.schedule();
+		
+		// Find imports from Java source
+		Job findImportsJob = new Job("Find imports") {
+			protected IStatus run(IProgressMonitor monitor) {
+				IJavaProject javaProject = getProjectModel().getJavaModel();
+				try {
+					IPackageFragment[] packages = javaProject.getPackageFragments();
+					for (IPackageFragment pkg : packages) {
+						ICompilationUnit[] compilationUnits = pkg.getCompilationUnits();
+						for (ICompilationUnit compilationUnit : compilationUnits) {
+							IImportDeclaration[] importDecls = compilationUnit.getImports();
+							for (IImportDeclaration importDecl : importDecls) {
+								imports.add(getPackageName(importDecl));
+							}
+						}
+					}
+					return Status.OK_STATUS;
+				} catch (JavaModelException e) {
+					return new Status(IStatus.ERROR, SigilUI.PLUGIN_ID, 0, "Error finding imports", e);
+				}
+			}
+		};
+		findImportsJob.setUser(true);
+		findImportsJob.schedule();
+		
+		// Wait for both jobs to complete
+		try {
+			findImportsJob.join();
+			findExportsJob.join();
+		} catch (InterruptedException e) {
+			// Aborted, just do nothing
+			return;
+		}
+		
+		// Get the version rules
+		IPreferenceStore prefStore = SigilCore.getDefault().getPreferenceStore();
+		VersionRangeBoundingRule lowerBoundRule = VersionRangeBoundingRule.valueOf(prefStore.getString(SigilCore.DEFAULT_VERSION_LOWER_BOUND));
+		VersionRangeBoundingRule upperBoundRule = VersionRangeBoundingRule.valueOf(prefStore.getString(SigilCore.DEFAULT_VERSION_UPPER_BOUND));
+		
+		// Get the existing imports for the bundle
+		IBundleModelElement bundleInfo = getProjectModel().getBundle().getBundleInfo();
+		Set<IPackageImport> existingImports = bundleInfo.getImports();
+		Map<String, IPackageImport> existingImportsMap = new HashMap<String, IPackageImport>();
+		for (IPackageImport existingImport : existingImports) {
+			existingImportsMap.put(existingImport.getPackageName(), existingImport);
+		}
+		
+		// Add imports to the bundle
+		ModelElementFactory elementFactory = ModelElementFactory.getInstance();
+		int count = 0;
+		for (String pkgImport : imports) {
+			IPackageExport export = exports.get(pkgImport);
+			if(export != null && !existingImportsMap.containsKey(pkgImport)) {
+				VersionRange versionRange = VersionRange.newInstance(export.getVersion(), lowerBoundRule, upperBoundRule);
+				IPackageImport newImport = elementFactory.newModelElement(IPackageImport.class);
+				newImport.setPackageName(pkgImport);
+				newImport.setVersions(versionRange);
+				newImport.setOptional(false);
+				
+				bundleInfo.addImport(newImport);
+				count++;
+			}
+		}
+		
+		// Remove required bundles
+		Set<IRequiredBundle> requiredBundles = bundleInfo.getRequiredBundles();
+		int requiredBundlesSize = requiredBundles.size();
+		for (IRequiredBundle requiredBundle : requiredBundles) {
+			bundleInfo.removeRequiredBundle(requiredBundle);
+		}
+
+		// Update the editor
+		if(count + requiredBundlesSize > 0) {
+			IFormPart[] parts = getPage().getManagedForm().getParts();
+			for (IFormPart formPart : parts) {
+				formPart.refresh();
+				((AbstractFormPart) formPart).markDirty();
+			}
+		}
+		
+		MessageDialog.openInformation(getManagedForm().getForm().getShell(), "Dependency Management", "Removed " + requiredBundlesSize + " required bundle(s) and added " + count + " imported package(s).");
+	}
+	
+	private static String getPackageName(IImportDeclaration decl) {
+		String name = decl.getElementName();
+		int lastDot = name.lastIndexOf('.');
+		return name.substring(0, lastDot);
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DownloadSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DownloadSection.java
new file mode 100644
index 0000000..ad2e705
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/DownloadSection.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+
+
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.eclipse.IDownloadJar;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.util.ModelLabelProvider;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+
+/**
+ * @author dave
+ *
+ */
+public class DownloadSection extends AbstractResourceSection {
+
+	/**
+	 * @param page
+	 * @param parent
+	 * @param project
+	 * @throws CoreException 
+	 */
+	
+	private IDownloadJar dl;
+	
+	public DownloadSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}
+
+	/* (non-Javadoc)
+	 * @see org.cauldron.sigil.ui.editors.project.SigilSection#createSection(org.eclipse.ui.forms.widgets.Section, org.eclipse.ui.forms.widgets.FormToolkit)
+	 */
+	@Override
+	protected void createSection(Section section, FormToolkit toolkit) throws CoreException {
+		setTitle( "Codebase" );
+		
+		Composite body = createTableWrapBody(1, toolkit);
+
+        toolkit.createLabel( body, "Specify which resources are included as part of this bundles remote codebase." );
+		
+		tree = toolkit.createTree( body, SWT.CHECK | SWT.BORDER );
+		
+		TableWrapData data = new TableWrapData( TableWrapData.FILL_GRAB);
+		data.heightHint = 200;
+		tree.setLayoutData( data );
+		
+		viewer = new CheckboxTreeViewer( tree );
+		IFolder base = ResourcesPlugin.getWorkspace().getRoot().getFolder(getProjectModel().getJavaModel().getOutputLocation());
+		viewer.setContentProvider( new ContainerTreeProvider() );
+		viewer.setLabelProvider( new ModelLabelProvider() );
+		viewer.addCheckStateListener( this );
+		viewer.setInput( base );
+		
+		dl = getProjectModel().getBundle().getDownloadJar();
+		
+		startWorkspaceListener(base.getWorkspace());
+	}
+
+	@Override
+	public void refresh() {
+		dl = getProjectModel().getBundle().getDownloadJar();
+		super.refresh();
+	}
+
+	@Override
+	public void commit(boolean onSave) {
+		getProjectModel().getBundle().setDownloadJar(dl);			
+		super.commit(onSave);
+	}
+
+	@Override
+	protected void refreshSelections() {
+		// zero the state
+		if ( dl != null ) {
+			for ( IPath path : dl.getEntrys() ) {
+				IResource r = findResource( path );
+				if ( r != null ) {
+					viewer.expandToLevel(r, 0);
+					viewer.setChecked( r, true );
+					viewer.setGrayed( r, false );
+					handleStateChanged(r, true, false, false);
+				}
+				else {
+					SigilCore.error( "Unknown path " + path );
+				}
+			}
+		}
+	}	
+	
+	protected void syncResourceModel(IResource element, boolean checked) {
+		try {
+			if ( dl == null ) {
+				dl = ModelElementFactory.getInstance().newModelElement(IDownloadJar.class);
+				getProjectModel().getBundle().setDownloadJar(dl);
+			}
+			
+			if ( checked ) {
+				dl.addEntry( element.getProjectRelativePath() );
+			}
+			else {
+				dl.removeEntry( element.getProjectRelativePath() );
+			}
+			
+			markDirty();
+		}
+		catch (ModelElementFactoryException e) {
+			SigilCore.error( "Failed to create model element", e );
+		}
+	}	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExcludedResourcesFilter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExcludedResourcesFilter.java
new file mode 100644
index 0000000..4f91445
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExcludedResourcesFilter.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.preferences.PrefsUtils;
+import org.cauldron.sigil.utils.GlobCompiler;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+
+public class ExcludedResourcesFilter extends ViewerFilter {
+
+	private final Set<Pattern> exclusionSet = new HashSet<Pattern>();
+	
+	public ExcludedResourcesFilter() {
+		loadExclusions();
+	}
+
+	public final synchronized void loadExclusions() {
+		exclusionSet.clear();
+		IPreferenceStore store = SigilCore.getDefault().getPreferenceStore();
+		String[] exclusions = PrefsUtils.stringToArray(store.getString(SigilCore.DEFAULT_EXCLUDED_RESOURCES));
+		for (String exclusion : exclusions) {
+			exclusionSet.add(GlobCompiler.compile(exclusion));
+		}
+	}
+
+	@Override
+	public synchronized boolean select(Viewer viewer, Object parentElement, Object element) {
+		IResource file = (IResource) element;
+		String path = file.getName();
+		for ( Pattern p :exclusionSet ) {
+			if ( p.matcher(path).matches() ) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExportPackagesSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExportPackagesSection.java
new file mode 100644
index 0000000..406a5e5
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExportPackagesSection.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.common.VersionRangeBoundingRule;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.preferences.OptionalPrompt;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.util.ResourcesDialogHelper;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.osgi.framework.Version;
+
+public class ExportPackagesSection extends BundleDependencySection {
+
+	public ExportPackagesSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}
+		
+	@Override
+	protected String getTitle() {
+		return "Export Packages";
+	}
+
+	@Override
+	protected Label createLabel(Composite parent, FormToolkit toolkit) {
+		return toolkit.createLabel( parent, "Specify which packages this bundle shares with other bundles." );
+	}
+
+	@Override
+	protected IContentProvider getContentProvider() {
+        return new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return getBundle().getBundleInfo().getExports().toArray();
+			}
+	    };
+	}
+
+	@Override
+	protected void handleAdd() {
+		NewPackageExportDialog dialog = ResourcesDialogHelper.createNewExportDialog(getSection().getShell(), "Add Exported Package", null, getProjectModel(), true);
+
+		if ( dialog.open() == Window.OK ) {
+			try {
+				// Add selected exports
+				boolean exportsAdded = false;
+				
+				List<IPackageFragment> newPkgFragments = dialog.getSelectedElements();
+				for (IPackageFragment pkgFragment : newPkgFragments) {
+					IPackageExport pkgExport = ModelElementFactory.getInstance().newModelElement(IPackageExport.class);
+					pkgExport.setPackageName(pkgFragment.getElementName());
+					pkgExport.setVersion(dialog.getVersion());
+					getBundle().getBundleInfo().addExport(pkgExport);
+					
+					exportsAdded = true;
+				}
+
+				// Add corresponding imports (maybe)
+				boolean importsAdded = false;
+				
+				IPreferenceStore store = SigilCore.getDefault().getPreferenceStore();
+				boolean shouldAddImports = OptionalPrompt.optionallyPrompt(store, SigilCore.PREFERENCES_ADD_IMPORT_FOR_EXPORT, "Add Exports", "Should corresponding imports be added?", getSection().getShell());
+				if(shouldAddImports) {
+					for (IPackageFragment pkgFragment : newPkgFragments) {
+						IPackageImport pkgImport = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+						pkgImport.setPackageName(pkgFragment.getElementName());
+						VersionRangeBoundingRule lowerBound = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_LOWER_BOUND));
+						VersionRangeBoundingRule upperBound = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_UPPER_BOUND));
+						Version version = dialog.getVersion();
+						if(version == null) {
+							version = getBundle().getVersion();
+						}
+						VersionRange versionRange = VersionRange.newInstance(version, lowerBound, upperBound);
+						pkgImport.setVersions(versionRange);
+						
+						getBundle().getBundleInfo().addImport(pkgImport);
+						
+						importsAdded = true;
+					}
+				}
+				
+				if(importsAdded) {
+					((SigilProjectEditorPart) getPage().getEditor()).refreshAllPages();
+					markDirty();
+				} else if(exportsAdded) {
+					refresh();
+					markDirty();
+				}
+			}
+			catch (ModelElementFactoryException e) {
+				SigilCore.error( "Failed to buiild model element for package export", e);
+			}
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void handleEdit() {
+		IStructuredSelection selection = (IStructuredSelection) getSelection();
+		
+		boolean changed = false;
+		
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IPackageExport> i = selection.iterator(); i.hasNext(); ) {	
+				IPackageExport packageExport = i.next();
+				NewPackageExportDialog dialog = ResourcesDialogHelper.createNewExportDialog(getSection().getShell(), "Edit Imported Package", packageExport, getProjectModel(), false);
+				if ( dialog.open() == Window.OK ) {
+					changed = true;
+					IPackageFragment pkgFragment = dialog.getSelectedElement();
+					packageExport.setPackageName(pkgFragment.getElementName());
+					packageExport.setVersion(dialog.getVersion());
+				}
+			}
+		}
+		
+		if ( changed ) {
+			refresh();
+			markDirty();
+		}
+	}
+
+	@SuppressWarnings("unchecked") 
+	@Override
+	protected void handleRemoved() {
+		IStructuredSelection selection = (IStructuredSelection) getSelection();
+
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IPackageExport> i = selection.iterator(); i.hasNext(); ) {			
+				getBundle().getBundleInfo().removeExport( i.next() );
+			}		
+			
+			refresh();
+			markDirty();
+		}
+	}
+		
+	private ISigilBundle getBundle() {
+		return getProjectModel().getBundle();
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExportsForm.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExportsForm.java
new file mode 100644
index 0000000..7a6fe3c
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ExportsForm.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormEditor;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+public class ExportsForm extends SigilPage  {
+
+	public static final String PAGE_ID = "exports";
+	
+	private ISigilProjectModel project;
+	
+	public ExportsForm(FormEditor editor, ISigilProjectModel project) {
+		super(editor, PAGE_ID, "Exports");
+		this.project = project;
+	}
+	
+    @Override
+    protected void createFormContent(IManagedForm managedForm) {
+        ScrolledForm form = managedForm.getForm();
+        form.setText( "Exports" );
+        
+        Composite body = form.getBody();        
+        TableWrapLayout layout = new TableWrapLayout();
+        layout.bottomMargin = 10;
+        layout.topMargin = 5;
+        layout.leftMargin = 10;
+        layout.rightMargin = 10;
+        layout.numColumns = 1;
+        layout.horizontalSpacing = 10;
+        layout.verticalSpacing = 20;
+        body.setLayout(layout);
+        body.setLayoutData(new TableWrapData(TableWrapData.FILL));
+                       
+        try {
+	        ExportPackagesSection exportPackages = new ExportPackagesSection( this, body, project );
+	        managedForm.addPart( exportPackages );
+        }
+        catch (CoreException e) {
+        	SigilCore.error( "Failed to create contents form", e);
+        }
+    }
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/GeneralInfoSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/GeneralInfoSection.java
new file mode 100644
index 0000000..4200712
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/GeneralInfoSection.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.IFormValueConverter;
+import org.cauldron.sigil.ui.form.SigilFormEntry;
+import org.cauldron.sigil.ui.form.SigilFormEntryAdapter;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.cauldron.sigil.ui.util.BackgroundLoadingSelectionDialog;
+import org.cauldron.sigil.ui.util.ResourcesDialogHelper;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.Version;
+
+/**
+ * @author dave
+ *
+ */
+public class GeneralInfoSection extends SigilSection {
+
+	private String name;
+	private String symbolicName;
+    private Version version;
+    private String description;
+    private String provider;
+    private String activator;
+    private IRequiredBundle fragmentHost;
+    
+    private SigilFormEntry nameEntry;
+	private SigilFormEntry symbolicNameEntry;
+    private SigilFormEntry versionEntry;
+    private SigilFormEntry descriptionEntry;
+    private SigilFormEntry providerEntry;
+    private SigilFormEntry activatorEntry;
+    private SigilFormEntry fragmentHostEntry;
+    
+    /**
+     * @param parent
+     * @param toolkit
+     * @param style
+     * @throws CoreException 
+     */
+    public GeneralInfoSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}    
+
+    protected void createSection(Section section,FormToolkit toolkit ) {
+        setTitle("General Information");
+                
+		Composite body = createGridBody(3, false, toolkit);
+        
+        Label label = toolkit.createLabel( body, "This section describes general information about this project." );
+        label.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, true, false, 3, 1) );
+       
+        symbolicNameEntry = new SigilFormEntry(body, toolkit, "Symbolic Name");
+        symbolicNameEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				symbolicName = nullIfEmpty((String) form.getValue());
+				checkDirty();
+			}
+        });
+        
+        nameEntry = new SigilFormEntry(body, toolkit, "Name");
+        nameEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				name = nullIfEmpty((String) form.getValue());
+				checkDirty();
+			}
+        });
+        
+        descriptionEntry = new SigilFormEntry(body, toolkit, "Description");
+        descriptionEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				description = nullIfEmpty((String) form.getValue());
+				checkDirty();
+			}
+        });
+        
+        IFormValueConverter converter = new IFormValueConverter() {
+			public String getLabel(Object value) {
+				Version v = (Version) value;
+				return v.toString();
+			}
+
+			public Object getValue(String label) {
+				return Version.parseVersion(label);
+			}        	
+        };
+        
+        versionEntry = new SigilFormEntry(body, toolkit, "Version", null, converter);
+        versionEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				version = (Version) form.getValue();
+				checkDirty();
+			}
+        });
+        
+        providerEntry = new SigilFormEntry(body, toolkit, "Provider");
+        providerEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				provider = nullIfEmpty((String) form.getValue());
+				checkDirty();
+			}
+        });
+
+		activatorEntry = new SigilFormEntry(body, toolkit, "Bundle Activator", "Browse...", null);
+		activatorEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				activator = (String) form.getValue();
+				checkDirty();
+			}
+
+			@Override
+			public void browseButtonSelected(SigilFormEntry form) {
+				BackgroundLoadingSelectionDialog<String> dialog = ResourcesDialogHelper.createClassSelectDialog(getShell(), "Add Bundle Activator", getProjectModel(), activator, BundleActivator.class.getName());
+				
+				if (dialog.open() == Window.OK) {
+					form.setValue(dialog.getSelectedElement());
+				}				
+			}			
+		});
+		
+		converter = new IFormValueConverter() {
+			public String getLabel(Object value) {
+				IRequiredBundle b = (IRequiredBundle) value;
+				return b == null ? null : b.getSymbolicName() + " " + b.getVersions();
+			}
+
+			public Object getValue(String label) {
+				return null;
+			}			
+		};
+		
+		fragmentHostEntry = new SigilFormEntry(body, toolkit, "Fragment Host", "Browse...", converter );
+		fragmentHostEntry.setFormEntryListener( new SigilFormEntryAdapter() {
+			@Override
+			public void textValueChanged(SigilFormEntry form) {
+				fragmentHost = (IRequiredBundle) form.getValue();
+				checkDirty();
+			}
+
+			@Override
+			public void browseButtonSelected(SigilFormEntry form) {
+				NewResourceSelectionDialog<IBundleModelElement> dialog = ResourcesDialogHelper.createRequiredBundleDialog( getSection().getShell(), "Add Required Bundle", getProjectModel(), null, getBundle().getBundleInfo().getRequiredBundles() );
+				
+				if (dialog.open() == Window.OK) {
+					IRequiredBundle required = ModelElementFactory.getInstance().newModelElement( IRequiredBundle.class );
+					required.setSymbolicName(dialog.getSelectedName());
+					required.setVersions(dialog.getSelectedVersions());				
+					form.setValue(required);
+				}				
+			}			
+		});
+		fragmentHostEntry.setFreeText(false);
+    }
+    
+	private static String nullIfEmpty(String value) {
+		if ( value.trim().length() == 0 ) {
+			return null;
+		}
+		return value;
+	}
+	
+	private Shell getShell() {
+		return getSection().getShell();
+	}
+
+	@Override
+	public void commit(boolean onSave) {
+		getBundle().getBundleInfo().setSymbolicName( symbolicName );
+		getBundle().getBundleInfo().setName( name );
+		getBundle().getBundleInfo().setVersion( version );
+		getBundle().getBundleInfo().setDescription( description );
+		getBundle().getBundleInfo().setVendor( provider );
+		getBundle().getBundleInfo().setFragmentHost(fragmentHost);
+		getBundle().getBundleInfo().setActivator(activator);
+		
+		super.commit(onSave);
+	}
+
+	@Override
+	public void refresh() {
+        symbolicName = getProjectModel().getBundle().getBundleInfo().getSymbolicName();
+        name = getProjectModel().getBundle().getBundleInfo().getName();
+        description = getProjectModel().getBundle().getBundleInfo().getDescription();
+        version = getProjectModel().getBundle().getBundleInfo().getVersion();
+        provider = getProjectModel().getBundle().getBundleInfo().getVendor();
+        fragmentHost = getProjectModel().getBundle().getBundleInfo().getFragmentHost();
+        activator = getProjectModel().getBundle().getBundleInfo().getActivator();
+        
+        nameEntry.setValue(name);
+    	symbolicNameEntry.setValue(symbolicName);
+    	versionEntry.setValue(version);
+        descriptionEntry.setValue(description);
+        providerEntry.setValue(provider);
+        fragmentHostEntry.setValue(fragmentHost);
+        activatorEntry.setValue(activator);
+        
+		super.refresh();
+	}
+
+	private void checkDirty() {
+		boolean dirty = different(symbolicName, getProjectModel().getBundle().getBundleInfo().getSymbolicName() ) ||
+						different(name, getProjectModel().getBundle().getBundleInfo().getName() ) ||
+						different(version, getProjectModel().getBundle().getBundleInfo().getVersion() ) ||
+						different(description, getProjectModel().getBundle().getBundleInfo().getDescription()) ||
+						different(provider, getProjectModel().getBundle().getBundleInfo().getVendor()) ||
+						different(fragmentHost, getProjectModel().getBundle().getBundleInfo().getFragmentHost()) ||
+						different(activator, getProjectModel().getBundle().getBundleInfo().getActivator());
+				
+		if ( dirty ) markDirty();
+	}
+	
+	private boolean different(Object val1, Object val2) {
+		return val1 == null ? val2 != null : !val1.equals( val2 );
+	}
+
+	private ISigilBundle getBundle() {
+		return getProjectModel().getBundle();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/IDependencyChecker.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/IDependencyChecker.java
new file mode 100644
index 0000000..556c5e9
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/IDependencyChecker.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+
+public interface IDependencyChecker {
+	boolean isSatisfied(IPackageImport packageImport);
+	boolean isSatisfied(IRequiredBundle requiredBundle);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/IElementDescriptor.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/IElementDescriptor.java
new file mode 100644
index 0000000..4aa2953
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/IElementDescriptor.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+public interface IElementDescriptor<E> {	
+	/**
+	 * Return the short identifying name of the element.
+	 */
+	String getName(E element);
+
+	/**
+	 * Return a label for the element, including the name but possibly supplying
+	 * additional information.
+	 * 
+	 * @param element
+	 * @return
+	 */
+	String getLabel(E element);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ImportPackagesSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ImportPackagesSection.java
new file mode 100644
index 0000000..9403075
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ImportPackagesSection.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IPackageModelElement;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.util.ResourcesDialogHelper;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+
+public class ImportPackagesSection extends BundleDependencySection {
+	
+	public ImportPackagesSection(SigilPage page, Composite parent, ISigilProjectModel project, Set<IModelElement> unresolvedPackages) throws CoreException {
+		super( page, parent, project, unresolvedPackages );
+	}
+
+	@Override
+	protected String getTitle() {
+		return "Import Packages";
+	}
+	
+	@Override
+	protected Label createLabel(Composite parent, FormToolkit toolkit) {
+		return toolkit.createLabel( parent, "Specify which packages this bundle imports from other bundles." );
+	}
+
+
+	@Override
+	protected IContentProvider getContentProvider() {
+		return new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				ArrayList<IPackageImport> imports = new ArrayList<IPackageImport>(getBundle().getBundleInfo().getImports());
+				Collections.sort(imports, new Comparator<IPackageImport>() {
+					public int compare(IPackageImport o1, IPackageImport o2) {
+						return o1.getPackageName().compareTo( o2.getPackageName() );
+					}
+				});
+				return imports.toArray();
+			}
+        };
+	}
+
+	protected ISigilBundle getBundle() {
+		return getProjectModel().getBundle();
+	}
+
+	@Override
+	protected void handleAdd() {
+		NewResourceSelectionDialog<? extends IPackageModelElement> dialog = 
+			ResourcesDialogHelper.createImportDialog(
+					getSection().getShell(), 
+					"Add Imported Package", 
+					getProjectModel(), 
+					null, 
+					getBundle().getBundleInfo().getImports() );
+		
+		if ( dialog.open() == Window.OK ) {
+			IPackageImport pi = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+			pi.setPackageName(dialog.getSelectedName());
+			pi.setVersions(dialog.getSelectedVersions());
+			pi.setOptional(dialog.isOptional());
+			
+			getBundle().getBundleInfo().addImport(pi);
+			refresh();
+			markDirty();
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void handleRemoved() {
+		IStructuredSelection selection = (IStructuredSelection) getSelection();
+
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IPackageImport> i = selection.iterator(); i.hasNext(); ) {			
+				getBundle().getBundleInfo().removeImport( i.next() );
+			}		
+			
+			refresh();
+			markDirty();
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void handleEdit() {
+		IStructuredSelection selection = (IStructuredSelection) getSelection();
+		
+		boolean changed = false;
+		
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IPackageImport> i = selection.iterator(); i.hasNext(); ) {	
+				IPackageImport packageImport = i.next();
+				NewResourceSelectionDialog<? extends IPackageModelElement> dialog = 
+					ResourcesDialogHelper.createImportDialog( 
+							getSection().getShell(), 
+							"Edit Imported Package", 
+							getProjectModel(), 
+							packageImport, 
+							getBundle().getBundleInfo().getImports() );
+				if ( dialog.open() == Window.OK ) {
+					changed = true;
+					IPackageImport newImport = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+					newImport.setPackageName(dialog.getSelectedName());
+					newImport.setVersions(dialog.getSelectedVersions());
+					newImport.setOptional(dialog.isOptional());
+					
+					getBundle().getBundleInfo().removeImport( packageImport );
+					getBundle().getBundleInfo().addImport(newImport);
+				}
+			}					
+		}
+		
+		if ( changed ) {
+			refresh();
+			markDirty();
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/NewPackageExportDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/NewPackageExportDialog.java
new file mode 100644
index 0000000..02289d6
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/NewPackageExportDialog.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.Comparator;
+
+import org.cauldron.sigil.ui.util.BackgroundLoadingSelectionDialog;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.swt.SWT;
+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.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.framework.Version;
+
+public class NewPackageExportDialog extends BackgroundLoadingSelectionDialog<IPackageFragment> {
+	
+	private static final IElementDescriptor<IPackageFragment> PKG_FRAGMENT_STRINGIFIER = new IElementDescriptor<IPackageFragment>() {
+		public String getLabel(IPackageFragment element) {
+			return getName(element);
+		}
+		public String getName(IPackageFragment element) {
+			return element.getElementName();
+		}
+	};
+	
+	private static final Comparator<IPackageFragment> PKG_FRAGMENT_COMPARATOR = new Comparator<IPackageFragment>() {
+		public int compare(IPackageFragment o1, IPackageFragment o2) {
+			return o1.getElementName().compareTo(o2.getElementName());
+		}
+	};
+	
+	private Version version = null;
+	private String error = null;
+	private Version projectVersion = new Version(0,0,0);
+	
+	private Button btnInheritBundleVersion;
+	private Button btnExplicitVersion;
+	private Text txtVersion;
+
+	
+	public NewPackageExportDialog(Shell parentShell, boolean multiSelect) {
+		super(parentShell, "Package:", multiSelect);
+		setDescriptor(PKG_FRAGMENT_STRINGIFIER);
+		setComparator(PKG_FRAGMENT_COMPARATOR);
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		// Create controls
+		Composite container = (Composite) super.createDialogArea(parent);
+		Composite composite = new Composite(container, SWT.NONE);
+		
+		Group grpVersion = new Group(composite, SWT.NONE);
+		grpVersion.setText("Version");
+		
+		btnInheritBundleVersion = new Button(grpVersion, SWT.RADIO);
+		btnInheritBundleVersion.setText("Inherit bundle version");
+		new Label(grpVersion, SWT.NONE); // Spacer
+		btnExplicitVersion = new Button(grpVersion, SWT.RADIO);
+		btnExplicitVersion.setText("Fixed version:");
+		txtVersion = new Text(grpVersion, SWT.BORDER);
+		
+		// Initialize
+		if(version == null) {
+			btnInheritBundleVersion.setSelection(true);
+			txtVersion.setEnabled(false);
+			txtVersion.setText(projectVersion.toString());
+		} else {
+			btnExplicitVersion.setSelection(true);
+			txtVersion.setEnabled(true);
+			txtVersion.setText(version.toString());
+		}
+		updateButtons();
+		
+		// Listeners
+		Listener radioAndTextListener = new Listener() {
+			public void handleEvent(Event event) {
+				error = null;
+				if(btnInheritBundleVersion.getSelection()) {
+					version = null;
+					txtVersion.setEnabled(false);
+				} else {
+					txtVersion.setEnabled(true);
+					try {
+						version = new Version(txtVersion.getText());
+					} catch (IllegalArgumentException e) {
+						error = "Invalid version";
+					}
+				}
+				setErrorMessage(error);
+				updateButtons();
+			}
+		};
+		txtVersion.addListener(SWT.Modify, radioAndTextListener);
+		btnInheritBundleVersion.addListener(SWT.Selection, radioAndTextListener);
+		btnExplicitVersion.addListener(SWT.Selection, radioAndTextListener);
+		
+		
+		// Layout
+		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		composite.setLayout(new GridLayout(1, false));
+		grpVersion.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		grpVersion.setLayout(new GridLayout(2, false));
+		txtVersion.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		
+		return container;
+	}
+	
+	@Override
+	protected boolean canComplete() {
+		return super.canComplete() && error == null;
+	}
+	
+	public void setProjectVersion(Version projectVersion) {
+		this.projectVersion = projectVersion;
+	}
+
+	public void setVersion(Version version) {
+		this.version = version;
+	}
+	
+	public Version getVersion() {
+		return version;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/NewResourceSelectionDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/NewResourceSelectionDialog.java
new file mode 100644
index 0000000..59812cc
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/NewResourceSelectionDialog.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.common.VersionRangeBoundingRule;
+import org.cauldron.sigil.model.osgi.IVersionedModelElement;
+import org.cauldron.sigil.ui.util.BackgroundLoadingSelectionDialog;
+import org.cauldron.sigil.ui.util.IValidationListener;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.FillLayout;
+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.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.osgi.framework.Version;
+
+public class NewResourceSelectionDialog<E extends IVersionedModelElement> extends BackgroundLoadingSelectionDialog<E> {
+
+	private VersionRangeComponent pnlVersionRange;
+	private boolean optionalEnabled = true;
+	private Button btnOptional;
+
+	private VersionRange selectedVersions = null;
+	private boolean optional = false;
+
+	public NewResourceSelectionDialog(Shell parentShell, String selectionLabel, boolean multi) {
+		super(parentShell, selectionLabel, multi);
+	}
+	
+	public void setOptionalEnabled(boolean enabled) {
+		optionalEnabled = enabled;
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		// Create controls
+		Composite container = (Composite) super.createDialogArea(parent);
+		Composite composite = new Composite(container, SWT.NONE);
+		
+		if ( optionalEnabled ) {
+			new Label(composite, SWT.NONE); //Spacer
+			btnOptional = new Button(composite, SWT.CHECK);
+			btnOptional.setText("Optional");
+		}
+		
+		Label lblVersionRange = new Label(composite, SWT.NONE);
+		lblVersionRange.setText("Version Range:");
+		Group group = new Group(composite, SWT.BORDER);
+
+		pnlVersionRange = new VersionRangeComponent(group, SWT.NONE);
+		
+		// Initialize
+		if (selectedVersions != null) {
+			pnlVersionRange.setVersions(selectedVersions);
+		}
+		
+		if ( optionalEnabled ) {		
+			btnOptional.setSelection(optional);
+			updateButtons();
+		}
+		
+		// Hookup Listeners
+		pnlVersionRange.addVersionChangeListener(new VersionsChangeListener() {
+			public void versionsChanged(VersionRange range) {
+				selectedVersions = range;
+				updateButtons();
+			}
+		});
+		pnlVersionRange.addValidationListener(new IValidationListener() {
+			public void validationMessage(String message, int level) {
+				setMessage(message, level);
+				updateButtons();
+			}
+		});
+		
+		if ( optionalEnabled ) {		
+			btnOptional.addSelectionListener(new SelectionAdapter() {
+				public void widgetSelected(SelectionEvent e) {
+					optional = btnOptional.getSelection();
+				}
+			});
+		}
+		
+		// Layout
+		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		GridLayout layout = new GridLayout(2, false);
+		layout.verticalSpacing = 10;
+		layout.horizontalSpacing = 10;
+		composite.setLayout(layout);
+
+		lblVersionRange.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
+		group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		group.setLayout(new FillLayout());
+
+		return container;
+	}
+
+	@Override
+	protected void elementSelected(E selection) {
+		if(selection != null) {
+			IPreferenceStore store = SigilCore.getDefault().getPreferenceStore();
+			VersionRangeBoundingRule lowerBoundRule = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_LOWER_BOUND));
+			VersionRangeBoundingRule upperBoundRule = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_UPPER_BOUND));
+			
+			Version version = selection.getVersion();
+			selectedVersions = VersionRange.newInstance(version, lowerBoundRule, upperBoundRule);
+			pnlVersionRange.setVersions(selectedVersions);
+		}
+	}
+
+	@Override
+	protected synchronized boolean canComplete() {
+		return super.canComplete() && selectedVersions != null;
+	}
+
+	public VersionRange getSelectedVersions() {
+		return selectedVersions;
+	}
+
+	public void setVersions(VersionRange versions) {
+		selectedVersions = versions;
+		if(pnlVersionRange != null && !pnlVersionRange.isDisposed()) {
+			pnlVersionRange.setVersions(versions);
+			updateButtons();
+		}
+	}
+	
+	public boolean isOptional() {
+		return optional;
+	}
+	
+	public void setOptional(boolean optional) {
+		this.optional = optional;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/OverviewForm.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/OverviewForm.java
new file mode 100644
index 0000000..a948427
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/OverviewForm.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.ScrolledForm;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+/**
+ * @author dave
+ *
+ */
+public class OverviewForm extends SigilPage {
+    public static final String PAGE_ID = "overview";
+    private ISigilProjectModel sigil;
+    
+    public OverviewForm(SigilProjectEditorPart editor, ISigilProjectModel sigil) {
+        super(editor, PAGE_ID, "Overview");
+        this.sigil = sigil;
+    }
+
+    @Override
+    protected void createFormContent(IManagedForm managedForm) {
+        FormToolkit toolkit = managedForm.getToolkit();
+        
+        ScrolledForm form = managedForm.getForm();
+        form.setText( "Overview" );
+        
+        Composite body = form.getBody();        
+        TableWrapLayout layout = new TableWrapLayout();
+        layout.bottomMargin = 10;
+        layout.topMargin = 5;
+        layout.leftMargin = 10;
+        layout.rightMargin = 10;
+        layout.numColumns = 2;
+        layout.horizontalSpacing = 10;
+        body.setLayout(layout);
+        body.setLayoutData(new TableWrapData(TableWrapData.FILL));
+        
+        Composite left = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        left.setLayout(layout);
+        left.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB));
+        
+        Composite right = toolkit.createComposite(body);
+        layout = new TableWrapLayout();
+        layout.verticalSpacing = 20;
+        right.setLayout(layout);
+        right.setLayoutData( new TableWrapData( TableWrapData.FILL_GRAB) );
+        
+        try {
+	        GeneralInfoSection general = new GeneralInfoSection(this, left, sigil);
+	        managedForm.addPart( general );
+	        
+	        ContentSummarySection content = new ContentSummarySection( this, right, sigil);
+	        managedForm.addPart( content );
+	        
+	        // XXX-FELIX
+	        // commented out due to removal of runtime newton integration
+	        // potential to bring back in medium term...
+	        //TestingSection testing = new TestingSection(this, right, newton);
+	        //managedForm.addPart(testing);
+	        
+	        ToolsSection tools = new ToolsSection(this, right, sigil);
+	        managedForm.addPart(tools);
+        }
+        catch (CoreException e) {
+        	SigilCore.error( "Failed to create overview form", e );
+        }
+    }
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/PackageExportDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/PackageExportDialog.java
new file mode 100644
index 0000000..80c2328
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/PackageExportDialog.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.framework.Version;
+
+public class PackageExportDialog extends ResourceSelectDialog  {
+
+	private Text versionText;
+	private Version version;
+	
+	public PackageExportDialog(Shell parentShell, String title, IContentProvider content, ViewerFilter filter, Object scope ) {
+		super(parentShell, content, filter, scope, title, "Package Name:", true);
+	}
+	
+	@Override
+	protected void createCustom(Composite body) {
+		Label l = new Label( body, SWT.NONE );
+		l.setText( "Version:" );
+		versionText = new Text(body, SWT.BORDER);
+		versionText.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyReleased(KeyEvent e) {
+				try {
+					version = Version.parseVersion(versionText.getText());
+					setErrorMessage(null);
+				}
+				catch (IllegalArgumentException ex) {
+					setErrorMessage("Invalid version");
+				}
+			}			
+		});
+		if ( version != null ) {
+			versionText.setText( version.toString() );
+		}
+	}
+
+	public Version getVersion() {
+		return version;
+	}
+
+	public void setVersion(Version version) {
+		this.version = version;
+	}	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ProjectLabelProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ProjectLabelProvider.java
new file mode 100644
index 0000000..ca745f5
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ProjectLabelProvider.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.io.InputStream;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.ui.util.DefaultLabelProvider;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.resource.ImageRegistry;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Widget;
+
+public class ProjectLabelProvider extends DefaultLabelProvider implements IBaseLabelProvider {
+
+	private final Widget parent;
+	private final ImageRegistry registry;
+	private final IDependencyChecker checker;
+	
+	public ProjectLabelProvider(Widget parent, ImageRegistry registry ) {
+		this(parent, registry, null);
+	}
+	
+	public ProjectLabelProvider(Widget parent, ImageRegistry registry, IDependencyChecker checker) {
+		this.parent = parent;
+		this.registry = registry;
+		this.checker = checker;
+	}
+
+	public Image getImage(Object element) {
+		Image result = null;
+		if ( element instanceof ISigilBundle || element instanceof IRequiredBundle || element instanceof IBundleModelElement) {
+			result = findBundle();
+		}
+		else if (element instanceof IPackageImport) {
+			if(checker != null) {
+				result = checker.isSatisfied((IPackageImport) element) ? findPackage() : findPackageError();
+			} else {
+				result = findPackage();
+			}
+		}
+		else if (element instanceof IPackageExport) {
+			result = findPackage();
+		}
+		else if ( element instanceof IPackageFragmentRoot ) {
+			IPackageFragmentRoot root = (IPackageFragmentRoot) element;
+			try {
+				if ( root.getKind() == IPackageFragmentRoot.K_SOURCE ) {
+					result = findPackage();
+				}
+				else {
+					result = findBundle();
+				}
+			} catch (JavaModelException e) {
+				SigilCore.error( "Failed to inspect package fragment root", e );
+			}
+		}
+		else if ( element instanceof IClasspathEntry ) {
+			result = findPackage();
+		}
+	
+		return result;
+	}
+
+	public String getText(Object element) {
+		if ( element instanceof ISigilBundle ) {
+			ISigilBundle bundle = (ISigilBundle) element;
+			return bundle.getBundleInfo().getSymbolicName(); 
+		}
+
+		if ( element instanceof IRequiredBundle ) {
+			IRequiredBundle req = (IRequiredBundle) element;
+			return req.getSymbolicName() + " " + req.getVersions();
+		}
+		
+		if ( element instanceof IPackageImport ) {
+			IPackageImport req = (IPackageImport) element;
+			return req.getPackageName() + " " + req.getVersions();
+		}
+		
+		if ( element instanceof IPackageExport ) {
+			IPackageExport pe = (IPackageExport) element;
+			return pe.getPackageName() + " " + pe.getVersion();
+		}
+		
+		if ( element instanceof IResource ) {
+			IResource resource = (IResource) element;
+			return resource.getName();
+		}
+		
+		if ( element instanceof IPackageFragment )  {
+			IPackageFragment f = (IPackageFragment) element;
+			return f.getElementName();
+		}
+		
+		if ( element instanceof IPackageFragmentRoot ) {
+			IPackageFragmentRoot f = (IPackageFragmentRoot) element;
+			try {
+				return f.getUnderlyingResource().getName();
+			} catch (JavaModelException e) {
+				return "unknown";
+			}
+		}
+		
+		if ( element instanceof IClasspathEntry ) {
+			IClasspathEntry cp = (IClasspathEntry) element;
+			return cp.getPath().toString();
+		}
+		
+		return element.toString();
+	}
+
+	private Image findPackage() {
+		Image image = registry.get( "package" );
+		
+		if ( image == null ) {
+			image = loadImage( "icons/package_obj.png" ); 
+			registry.put( "package", image);
+		}
+		
+		return image; 
+	}
+	
+	private Image findPackageError() {
+		Image image = registry.get("package_error");
+		if(image == null) {
+			image = loadImage("icons/package_obj_error.gif");
+			registry.put("package_error", image);
+		}
+		return image;
+	}
+
+	private Image findBundle() {
+		Image image = registry.get( "bundle" );
+		
+		if ( image == null ) {
+			image = loadImage( "icons/jar_obj.png" ); 
+			registry.put( "bundle", image);
+		}
+		
+		return image; 
+	}
+		
+	private Image loadImage(String resource) {
+		InputStream in = ProjectLabelProvider.class.getClassLoader().getResourceAsStream( resource );
+		ImageData data = new ImageData( in );
+		return new Image( parent.getDisplay(), data );
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ProjectTableViewer.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ProjectTableViewer.java
new file mode 100644
index 0000000..362de5b
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ProjectTableViewer.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.Set;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.ui.util.ModelLabelProvider;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.widgets.Table;
+
+public class ProjectTableViewer extends TableViewer {
+
+	private ModelLabelProvider labelProvider;
+
+	public ProjectTableViewer(Table table) {
+		super(table);
+		labelProvider = new ModelLabelProvider();
+		setLabelProvider(labelProvider);
+	}
+
+	@Override
+	public void setContentProvider(IContentProvider provider) {
+		super.setContentProvider(provider);
+		setInput(getTable());
+	}
+
+	public void setUnresolvedElements(Set<? extends IModelElement> elements) {
+		labelProvider.setUnresolvedElements(elements);
+	}
+
+	@Override
+	public ModelLabelProvider getLabelProvider() {
+		return labelProvider;
+	}
+	
+	
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/PropertiesForm.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/PropertiesForm.java
new file mode 100644
index 0000000..1e069fd
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/PropertiesForm.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.propertiesfileeditor.IPropertiesFilePartitions;
+import org.eclipse.jdt.internal.ui.propertiesfileeditor.PropertiesFileSourceViewerConfiguration;
+import org.eclipse.jdt.ui.text.JavaTextTools;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+@SuppressWarnings("restriction")
+public class PropertiesForm extends SigilSourcePage {
+
+    public static final String PAGE_ID = "properties";
+    
+	public PropertiesForm(SigilProjectEditorPart editor, ISigilProjectModel project) {
+		super(PAGE_ID);
+		JavaTextTools textTools= JavaPlugin.getDefault().getJavaTextTools();
+		IPreferenceStore store= JavaPlugin.getDefault().getCombinedPreferenceStore();
+		setPreferenceStore(store);
+		setSourceViewerConfiguration(new PropertiesFileSourceViewerConfiguration(textTools.getColorManager(), store, this, IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING));
+        /*IFileEditorInput fileInput = (IFileEditorInput) editor.getEditorInput();
+		this.setDocumentProvider(fileInput);*/
+	}
+
+	@Override
+	public boolean isSaveAsAllowed() {
+		return false;
+	}	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/RequiresBundleSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/RequiresBundleSection.java
new file mode 100644
index 0000000..5adf84d
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/RequiresBundleSection.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+
+import java.util.Iterator;
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.util.ResourcesDialogHelper;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+
+public class RequiresBundleSection extends BundleDependencySection {
+	
+	public RequiresBundleSection(SigilPage page, Composite parent, ISigilProjectModel project, Set<IModelElement> unresolvedElements) throws CoreException {
+		super( page, parent, project, unresolvedElements );
+	}
+	
+	@Override
+	protected String getTitle() {
+		return "Requires Bundles";
+	}
+	
+	@Override
+	protected Label createLabel(Composite parent, FormToolkit toolkit) {
+		return toolkit.createLabel( parent, "Specify which bundles this bundle depends on." );
+	}
+	
+	@Override
+	protected IContentProvider getContentProvider() {
+		return new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return getBundle().getBundleInfo().getRequiredBundles().toArray();
+			}
+		};
+	}
+
+	protected ISigilBundle getBundle() {
+		return getProjectModel().getBundle();
+	}
+
+	@Override
+	protected void handleAdd() {
+		try {
+			NewResourceSelectionDialog<IBundleModelElement> dialog = ResourcesDialogHelper.createRequiredBundleDialog( getSection().getShell(), "Add Required Bundle", getProjectModel(), null, getBundle().getBundleInfo().getRequiredBundles() );
+			
+			if (dialog.open() == Window.OK) {
+				IRequiredBundle required = ModelElementFactory.getInstance().newModelElement( IRequiredBundle.class );
+				required.setSymbolicName(dialog.getSelectedName());
+				required.setVersions(dialog.getSelectedVersions());
+				required.setOptional(dialog.isOptional());
+				
+				getBundle().getBundleInfo().addRequiredBundle(required);
+				refresh();
+				markDirty();
+			}
+		}
+		catch (ModelElementFactoryException e) {
+			SigilCore.error( "Failed to build required bundle", e );
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void handleEdit() {
+		IStructuredSelection selection = (IStructuredSelection) getSelection();
+		
+		boolean changed = false;
+		
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IRequiredBundle> i = selection.iterator(); i.hasNext(); ) {	
+				IRequiredBundle requiredBundle = i.next();
+				NewResourceSelectionDialog<IBundleModelElement> dialog = ResourcesDialogHelper.createRequiredBundleDialog(getSection().getShell(), "Edit Imported Package", getProjectModel(), requiredBundle, getBundle().getBundleInfo().getRequiredBundles() );
+				if ( dialog.open() == Window.OK ) {
+					changed = true;
+					requiredBundle.setSymbolicName(dialog.getSelectedName());
+					requiredBundle.setVersions(dialog.getSelectedVersions());
+					requiredBundle.setOptional(dialog.isOptional());
+				}
+			}					
+		}
+		
+		if ( changed ) {
+			refresh();
+			markDirty();
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void handleRemoved() {
+		IStructuredSelection selection = (IStructuredSelection) getSelection();
+
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IRequiredBundle> i = selection.iterator(); i.hasNext(); ) {			
+				getBundle().getBundleInfo().removeRequiredBundle( i.next() );
+			}		
+			
+			refresh();
+			markDirty();
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceBuildSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceBuildSection.java
new file mode 100644
index 0000000..efd61d6
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceBuildSection.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.util.ModelLabelProvider;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.viewers.CheckboxTreeViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+
+/**
+ * @author dave
+ *
+ */
+public class ResourceBuildSection extends AbstractResourceSection implements ICheckStateListener, IResourceChangeListener, IPropertyChangeListener {
+
+	private ExcludedResourcesFilter resourcesFilter;
+
+	/**
+	 * @param page
+	 * @param parent
+	 * @param project
+	 * @throws CoreException 
+	 */
+	public ResourceBuildSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}
+
+	/* (non-Javadoc)
+	 * @see org.cauldron.sigil.ui.editors.project.SigilSection#createSection(org.eclipse.ui.forms.widgets.Section, org.eclipse.ui.forms.widgets.FormToolkit)
+	 */
+	@Override
+	protected void createSection(Section section, FormToolkit toolkit) {
+		setTitle( "Resources" );
+		
+		Composite body = createTableWrapBody(1, toolkit);
+
+        toolkit.createLabel( body, "Specify which resources are included in the bundle." );
+		
+		tree = toolkit.createTree( body, SWT.CHECK | SWT.BORDER );
+		Link link = new Link(body, SWT.WRAP);
+		link.setText("Some resources may be filtered according to preferences. <a href=\"excludedResourcePrefs\">Click here</a> to edit the list of exclusions.");
+		
+		TableWrapData data = new TableWrapData( TableWrapData.FILL_GRAB);
+		data.heightHint = 200;
+		tree.setLayoutData( data );
+		
+		viewer = new CheckboxTreeViewer( tree );
+		IProject base = getProjectModel().getProject();
+		viewer.setContentProvider( new ContainerTreeProvider() );
+		viewer.setLabelProvider( new ModelLabelProvider() );
+		viewer.addCheckStateListener( this );
+		resourcesFilter = new ExcludedResourcesFilter();
+		viewer.addFilter(resourcesFilter);
+		viewer.setInput(base);
+		
+		link.addListener(SWT.Selection, new Listener() {
+			public void handleEvent(Event event) {
+				if("excludedResourcePrefs".equals(event.text)) {
+					PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(getPage().getEditorSite().getShell(), SigilCore.EXCLUDED_RESOURCES_PREFERENCES_ID, null, null);
+					dialog.open();
+				}
+			}
+		});
+		
+		SigilCore.getDefault().getPreferenceStore().addPropertyChangeListener(this);
+		
+		startWorkspaceListener(base.getWorkspace());
+	}
+
+	@Override
+	public void commit(boolean onSave) {
+		ISigilBundle bundle = getProjectModel().getBundle();
+		
+		bundle.clearSourcePaths();
+		
+		SigilUI.runInUISync( new Runnable() {
+			public void run() {
+				for ( Object o : viewer.getCheckedElements() ) {
+					if ( !viewer.getGrayed(o) ) {
+						IResource r = (IResource) o;
+						getProjectModel().getBundle().addSourcePath( r.getProjectRelativePath() );
+					}
+				}
+			}			
+		});
+		
+		super.commit(onSave);
+	}
+
+	@Override
+	protected void refreshSelections()  {
+		// zero the state
+		for ( IPath path : getProjectModel().getBundle().getSourcePaths() ) {
+			IResource r = findResource( path );
+			if ( r != null ) {
+				viewer.expandToLevel(r, 0);
+				viewer.setChecked( r, true );
+				viewer.setGrayed( r, false );
+				handleStateChanged(r, true, false, false);
+			}
+			else {
+				SigilCore.error( "Unknown path " + path );
+			}
+		}
+	}
+	
+	@Override
+	protected void syncResourceModel(IResource element, boolean checked) {
+		if ( checked ) {
+			getProjectModel().getBundle().addSourcePath( element.getProjectRelativePath() );
+		}
+		else {
+			getProjectModel().getBundle().removeSourcePath( element.getProjectRelativePath() );
+		}
+		
+		markDirty();
+	}
+	
+	@Override
+	public void dispose() {
+		super.dispose();
+		SigilCore.getDefault().getPreferenceStore().removePropertyChangeListener(this);
+	}
+
+	public void propertyChange(PropertyChangeEvent event) {
+		resourcesFilter.loadExclusions();
+		viewer.refresh();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceImportDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceImportDialog.java
new file mode 100644
index 0000000..e50fb21
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceImportDialog.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+
+public class ResourceImportDialog extends ResourceSelectDialog implements VersionsChangeListener {
+
+	private VersionRangeComponent versions;
+	private VersionRange range;
+	
+	public ResourceImportDialog(Shell parentShell, String title, String label, IContentProvider content, ViewerFilter filter, Object scope ) {
+		super(parentShell, content, filter, scope, title, label, true);
+	}
+
+	public VersionRange getVersions() {
+		return range;
+	}
+
+	@Override
+	protected void createCustom(Composite body) {
+		versions = new VersionRangeComponent(body, SWT.BORDER );
+		versions.addVersionChangeListener(this);
+		versions.setVersions(range);
+		
+		GridData data = new GridData( SWT.LEFT, SWT.TOP, true, true );
+		data.horizontalSpan = 2;
+		data.widthHint = 300;
+		data.heightHint = 200;
+		versions.setLayoutData(data);
+	}
+
+	@Override
+	protected void selectionChanged(SelectionChangedEvent event) {
+		if ( event.getSelection().isEmpty() ) {
+			versions.setEnabled(false);
+		}
+		else {
+			versions.setEnabled(true);
+		}
+	}
+
+	public void versionsChanged(VersionRange range) {
+		this.range = range; 
+		if ( range == null ) {
+			setErrorMessage( "Invalid version" );
+		}
+		else {
+			setErrorMessage( null );
+		}
+	}
+
+	public void setVersions(VersionRange range) {
+		this.range = range;
+	}
+
+	public IPackageImport getImport() {
+		IPackageImport packageImport = ModelElementFactory.getInstance().newModelElement( IPackageImport.class );
+		packageImport.setPackageName( (String) getSelected()[0] );
+		packageImport.setVersions( getVersions() );
+		return packageImport;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceSelectDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceSelectDialog.java
new file mode 100644
index 0000000..e967548
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ResourceSelectDialog.java
@@ -0,0 +1,372 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.cauldron.sigil.ui.util.ModelLabelProvider;
+import org.cauldron.sigil.ui.util.SingletonSelection;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.resource.StringConverter;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredViewer;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+
+public class ResourceSelectDialog extends Dialog {
+
+	private AtomicInteger keyCheck = new AtomicInteger();
+	private ScheduledExecutorService background = Executors.newSingleThreadScheduledExecutor();
+	
+	private class UpdateViewerRunnable implements Runnable {
+		private int check;
+		public UpdateViewerRunnable(int check) {
+			this.check = check;
+		}
+
+		public void run() {
+			if ( check == keyCheck.get() ) {
+				try {
+					viewer.refresh();
+				}
+				catch (SWTException e) {
+					// discard
+				}
+			}
+		}
+	}
+
+	private static final ISelection EMPTY_SELECTION = new ISelection() {
+		public boolean isEmpty() {
+			return true;
+		}
+	};
+	
+	private Job job;
+	
+	private boolean isCombo;
+	private String title;
+	private String selectionText;
+
+	private StructuredViewer viewer;
+	private Combo resourceNameCombo;
+	private Table resourceNameTable;
+	private Text errorMessageText;
+	private String errorMessage;
+	
+	private ViewerFilter filter;
+	private Object[] selected;
+	
+	private Object scope;	
+	private IContentProvider content;
+	private ILabelProvider labelProvider;
+
+	public ResourceSelectDialog(Shell parentShell, IContentProvider content, ViewerFilter filter, Object scope, String title, String selectionText, boolean isCombo) {
+		super(parentShell);
+		this.title = title;
+		this.selectionText = selectionText;
+		this.content = content;
+		this.filter = filter;
+		this.scope = scope;
+		this.isCombo = isCombo;
+	}
+	
+	public void setJob(Job job) {
+		this.job = job;
+	}
+	
+	public void refreshResources() {
+		try {
+			getShell().getDisplay().asyncExec( new Runnable() {
+				public void run() {
+					try {
+						viewer.refresh();
+					}
+					catch (SWTException e) {
+						// attempt to exec after dialog closed - discard
+					}
+				}
+			});
+		}
+		catch (NullPointerException e) {
+			// attempt to exec after dialog closed - discard
+		}
+		catch (SWTException e) {
+			// attempt to exec after dialog closed - discard
+		}
+	}
+
+	/*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+     */
+    protected void configureShell(Shell shell) {
+        super.configureShell(shell);
+        if (title != null) {
+			shell.setText(title);
+		}        
+    }
+
+	@Override
+	public void create() {
+		super.create();
+		if ( getItemCount() == 0 ) {
+			setErrorMessage( "No resources available" );
+			getButton(IDialogConstants.OK_ID).setEnabled(false);
+		}
+		else {
+			ISelection selection = selected == null ? EMPTY_SELECTION : new SingletonSelection( selected );
+			setSelected( new SelectionChangedEvent( viewer, selection ), true );
+		}
+		
+		if ( job != null ) {
+			job.schedule();
+		}
+	}
+
+	@Override
+	public boolean close() {
+		if ( job != null ) {
+			job.cancel();
+		}
+		background.shutdownNow();
+		return super.close();
+	}
+
+	private int getItemCount() {
+		if ( isCombo ) {
+			return resourceNameCombo.getItemCount();
+		}
+		else {
+			return resourceNameTable.getItemCount();
+		}
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		Composite body = (Composite) super.createDialogArea(parent);
+		
+		GridLayout layout = (GridLayout) body.getLayout();
+		layout.numColumns = 2;
+		GridData data;
+		
+		labelProvider = new ModelLabelProvider();
+		
+		Label l = new Label( body, SWT.LEFT );
+		l.setText( selectionText );
+		data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
+		if ( !isCombo ) {
+			data.horizontalSpan = 2;
+		}
+		l.setLayoutData( data );
+	
+		if ( isCombo ) {
+			createCombo( body );
+		}
+		else {
+			createTable( body );
+		}
+		
+		viewer.addFilter( filter );
+		viewer.setContentProvider(content);
+		viewer.setLabelProvider( getLabelProvider() );
+		viewer.setComparator( new ViewerComparator() );
+		viewer.setInput( scope );
+		
+		viewer.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				setSelected(event, false);
+			}			
+		});
+	    createCustom( body );
+	    
+		errorMessageText = new Text(body, SWT.READ_ONLY | SWT.WRAP);
+		data = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL);
+		data.horizontalSpan = 2;
+	    errorMessageText.setLayoutData(data);
+	    errorMessageText.setBackground(errorMessageText.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
+	    setErrorMessage(errorMessage);
+	    
+		return body;
+	}
+	
+    protected void createCustom(Composite body) {
+	}
+
+	private void createCombo(Composite body) {
+		resourceNameCombo = new Combo( body, SWT.SINGLE | SWT.BORDER );
+		GridData data = new GridData( GridData.HORIZONTAL_ALIGN_END);
+		data.widthHint = 200;
+		resourceNameCombo.setLayoutData( data );
+		
+		viewer = new ComboViewer(resourceNameCombo);
+	}
+
+	private void createTable(Composite body) {
+		final Text txtFilter = new Text(body, SWT.BORDER);
+		GridData data = new GridData( GridData.HORIZONTAL_ALIGN_END);
+		data.horizontalSpan = 2;
+		data.widthHint = 400;
+		txtFilter.setLayoutData(data);
+		
+		resourceNameTable = new Table( body, SWT.MULTI | SWT.BORDER );
+		data = new GridData( GridData.HORIZONTAL_ALIGN_END);
+		data.widthHint = 400;
+		data.heightHint = 400;
+		resourceNameTable.setLayoutData( data );
+		
+		viewer = new TableViewer(resourceNameTable);
+
+		txtFilter.addKeyListener( new KeyAdapter() {
+			@Override
+			public void keyReleased(KeyEvent e) {
+				switch ( e.keyCode ) {
+				case SWT.ARROW_UP:
+					scrollTable(-1);
+					break;
+				case SWT.ARROW_DOWN:
+					scrollTable(+1);
+					break;
+				default:
+					Runnable r = new UpdateViewerRunnable(keyCheck.incrementAndGet());
+					background.schedule(r, 100, TimeUnit.MILLISECONDS);
+					break;
+				}
+			}
+		});
+		
+		ViewerFilter filter = new ViewerFilter() {
+			@Override
+			public boolean select(Viewer viewer, Object parentElement,
+					Object element) {
+				return getLabelProvider().getText(element).startsWith( txtFilter.getText() );
+			}
+		};
+		
+		viewer.addFilter(filter);
+	}
+	
+	private void scrollTable(int delta) {
+		int i = resourceNameTable.getSelectionIndex();
+		
+		if ( i == -1 ) {
+			if ( delta < 0 ) { 
+				i = resourceNameTable.getItemCount() - 1;
+			}
+			else {
+				i = 0;
+			}
+		}
+		else {
+			i+=delta;
+		}
+		
+		if ( i > -1 && i < resourceNameTable.getItemCount() ) {
+			Item item = resourceNameTable.getItem(i);
+			resourceNameTable.select(i);
+			selected = new Object[] { item.getData() };
+			ISelection selection = new SingletonSelection( selected );
+			selectionChanged(new SelectionChangedEvent(viewer, selection));
+			viewer.reveal(selected);
+		}
+	}
+
+	private void setSelected(SelectionChangedEvent event, boolean reveal) {
+		if ( event.getSelection().isEmpty() ) {
+			selected = null;
+			setErrorMessage( "No resource selected" );
+		}
+		else {
+			selected = ((IStructuredSelection) event.getSelection()).toArray();
+			setErrorMessage( null );
+		}
+		
+		selectionChanged(event);
+		
+		if ( reveal && !event.getSelection().isEmpty() ) {
+			if ( resourceNameCombo != null ) {
+				IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+				resourceNameCombo.select(resourceNameCombo.indexOf((String) sel.getFirstElement()));
+			}
+			else {
+				viewer.setSelection(event.getSelection(), true);
+			}
+		}
+	}
+
+	protected ILabelProvider getLabelProvider() {
+		return labelProvider;
+	}
+
+	public Object[] getSelected() {
+    	return selected;
+    }
+	
+	public void setSelected(Object[] selected) {
+		this.selected = selected;
+	}
+	
+	protected void selectionChanged(SelectionChangedEvent event) {
+	}
+
+	public void setErrorMessage(String errorMessage) {
+		this.errorMessage = errorMessage;
+		if (errorMessageText != null && !errorMessageText.isDisposed()) {
+			errorMessageText.setText(errorMessage == null ? " \n " : errorMessage); 
+			boolean hasError = errorMessage != null && (StringConverter.removeWhiteSpaces(errorMessage)).length() > 0;
+			errorMessageText.setEnabled(hasError);
+			errorMessageText.setVisible(hasError);
+			errorMessageText.getParent().update();
+			Control ok = getButton(IDialogConstants.OK_ID);
+			if (ok != null) {
+				ok.setEnabled(!hasError);
+			}
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/SigilProjectEditorPart.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/SigilProjectEditorPart.java
new file mode 100644
index 0000000..c1fafaf
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/SigilProjectEditorPart.java
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.forms.IFormPart;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormEditor;
+import org.eclipse.ui.forms.editor.IFormPage;
+
+public class SigilProjectEditorPart extends FormEditor implements IResourceChangeListener {
+
+	private final Set<IModelElement> unresolvedElements = Collections.synchronizedSet(new HashSet<IModelElement>());
+	private ISigilProjectModel project;
+	private volatile boolean saving = false;
+	private int dependenciesPageIndex;
+	
+	// XXX-FIXME-XXX
+	private Image errorImage = null; //SigilUI.imageDescriptorFromPlugin(SigilUI.PLUGIN_ID, "icons/error.gif").createImage();
+	private PropertiesForm textPage;
+	
+    public IProject getProject() {
+        IFileEditorInput fileInput = (IFileEditorInput) getEditorInput();
+        return fileInput.getFile().getProject();
+    }
+    
+    /* (non-Javadoc)
+     * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+     */
+    @Override
+    public void doSave(IProgressMonitor monitor) {
+		monitor.beginTask("Saving", IProgressMonitor.UNKNOWN);
+		try {
+			saving = true;
+			new ProgressMonitorDialog(getSite().getShell()).run(true, true, new IRunnableWithProgress() {
+				public void run(final IProgressMonitor monitor) throws InvocationTargetException,
+						InterruptedException {
+					try {
+						if ( textPage.isDirty() ) {
+							SigilUI.runInUISync(new Runnable() {
+								public void run() {
+									textPage.doSave(monitor);
+								}								
+							});
+							project.setBundle(null);
+						}
+						else if ( isDirty() ) {
+							commitPages(true);
+							project.save(monitor);
+							SigilUI.runInUISync(new Runnable() {
+								public void run() {
+									editorDirtyStateChanged();
+								}								
+							});
+						}
+						
+						monitor.done();
+					} catch (CoreException e) {
+						throw new InvocationTargetException(e);
+					}
+				}
+			});
+		} catch (InvocationTargetException e) {
+			SigilCore.error("Failed to save " + project, e.getTargetException());
+		} catch (InterruptedException e) {
+			monitor.setCanceled(true);
+			return;
+		} finally {
+			saving = false;
+		}
+		monitor.done();		
+    }
+    
+	/* (non-Javadoc)
+     * @see org.eclipse.ui.forms.editor.FormEditor#addPages()
+     */
+    @Override
+    protected void addPages() {
+        try {
+            addPage(new OverviewForm(this, project));
+			addPage(new ContentsForm(this, project));
+			dependenciesPageIndex = addPage(new DependenciesForm(this, project, unresolvedElements));
+			addPage(new ExportsForm(this, project));
+			textPage = new PropertiesForm(this, project);
+			addPage(textPage, getEditorInput());
+			setPartName(project.getSymbolicName());
+			
+			refreshTabImages();
+        }
+        catch (PartInitException e) {
+        	SigilCore.error( "Failed to build " + this, e );
+		}
+    }
+    
+    protected void refreshTabImages() {
+	    if(unresolvedElements.isEmpty()) {
+	    	setPageImage(dependenciesPageIndex, null);
+	    } else {
+	    	setPageImage(dependenciesPageIndex, errorImage);
+	    }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.eclipse.ui.part.EditorPart#doSaveAs()
+     */
+    @Override
+    public void doSaveAs() {
+    	// save as not allowed
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
+     */
+    @Override
+    public boolean isSaveAsAllowed() {
+        return false;
+    }
+    
+    @Override
+    public void dispose() {
+    	ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
+    	errorImage.dispose();
+    	super.dispose();
+    }
+
+	public void resourceChanged(IResourceChangeEvent event) {
+		IResourceDelta delta = event.getDelta();
+		final IFile editorFile = ((IFileEditorInput) getEditorInput()).getFile();
+		try {
+			delta.accept(new IResourceDeltaVisitor() {
+				public boolean visit(IResourceDelta delta) throws CoreException {
+					int kind = delta.getKind();
+					IResource resource = delta.getResource();
+					if(resource instanceof IProject) {
+						if(!editorFile.getProject().equals(resource)) {
+							return false;
+						}
+						if(kind == IResourceDelta.CHANGED && (delta.getFlags() & IResourceDelta.MARKERS) > 0) {
+							loadUnresolvedDependencies();
+							refreshAllPages();
+						}
+						return true;
+					}
+					
+					if(resource instanceof IFile) {
+						IFile affectedFile = (IFile) resource;
+							if(affectedFile.equals(editorFile)) {
+							switch (kind) {
+							case IResourceDelta.REMOVED:
+								close(false);
+								break;
+							case IResourceDelta.CHANGED:
+								if(!saving) {
+									reload();
+								}
+								SigilUI.runInUISync( new Runnable() {
+									public void run() {
+										setPartName(project.getSymbolicName());								
+									} 									
+								} );
+								break;
+							}
+						}
+						// Recurse no more
+						return false;
+					}
+					
+					return true;
+				}
+			});
+		} catch (CoreException e) {
+			ErrorDialog.openError(getSite().getShell(), "Error", null, e.getStatus());
+		}
+	}
+	
+	protected void refreshAllPages() {
+		Runnable op = new Runnable() {
+			public void run() {
+				for(Iterator<?> iter = pages.iterator(); iter.hasNext(); ) {
+					IFormPage page = (IFormPage) iter.next();
+					if(page != null) {
+						IManagedForm managedForm = page.getManagedForm();
+						if(managedForm != null) {
+							managedForm.refresh();
+							IFormPart[] parts = managedForm.getParts();
+							for (IFormPart part : parts) {
+								part.refresh();
+							}
+						}
+					}
+				}
+				firePropertyChange(IEditorPart.PROP_DIRTY);
+				setPartName(project.getSymbolicName());
+				refreshTabImages();
+			}
+		};
+		getSite().getShell().getDisplay().syncExec(op);
+	}
+	
+	protected void reload() {
+		project.setBundle(null);
+		refreshAllPages();
+	}
+	
+	@Override
+	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+		super.init(site, input);
+		
+        try {
+			this.project = SigilCore.create(getProject());
+		} catch (CoreException e) {
+			throw new PartInitException("Error creating Sigil project", e);
+		}
+		
+    	ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
+		
+		if(input instanceof IFileEditorInput) {
+			try {
+				loadUnresolvedDependencies();
+			} catch (CoreException e) {
+				throw new PartInitException("Error retrieving dependency markers", e);
+			}
+		}
+	}
+
+	private void loadUnresolvedDependencies() throws CoreException {
+		ModelElementFactory factory = ModelElementFactory.getInstance();
+		IMarker[] markers = getProject().findMarkers(SigilCore.MARKER_UNRESOLVED_DEPENDENCY, true, IResource.DEPTH_ONE);
+		unresolvedElements.clear();
+		
+		for (IMarker marker : markers) {
+			String elementName = (String) marker.getAttribute("element");
+			String versionRangeStr = (String) marker.getAttribute("versionRange");
+			if(elementName != null && versionRangeStr != null) {
+				if(marker.getType().equals(SigilCore.MARKER_UNRESOLVED_IMPORT_PACKAGE)) {
+					IPackageImport pkgImport = factory.newModelElement(IPackageImport.class);
+					pkgImport.setPackageName(elementName);
+					pkgImport.setVersions(VersionRange.parseVersionRange(versionRangeStr));
+					unresolvedElements.add(pkgImport);
+				} else if(marker.getType().equals(SigilCore.MARKER_UNRESOLVED_REQUIRE_BUNDLE)) {
+					IRequiredBundle req = factory.newModelElement(IRequiredBundle.class);
+					req.setSymbolicName(elementName);
+					req.setVersions(VersionRange.parseVersionRange(versionRangeStr));
+					unresolvedElements.add(req);
+				}
+			}
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/SigilSourcePage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/SigilSourcePage.java
new file mode 100644
index 0000000..291c927
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/SigilSourcePage.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextInputListener;
+import org.eclipse.jface.text.ITextListener;
+import org.eclipse.jface.text.TextEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.editors.text.TextEditor;
+import org.eclipse.ui.forms.IManagedForm;
+import org.eclipse.ui.forms.editor.FormEditor;
+import org.eclipse.ui.forms.editor.IFormPage;
+import org.eclipse.ui.ide.IDE;
+
+public class SigilSourcePage extends TextEditor implements IFormPage {
+	private final String id;
+	private int index;
+	private SigilProjectEditorPart editor;
+	private boolean active;
+	private Control control;
+	
+	public SigilSourcePage(String id) {
+		this.id = id;
+	}
+
+	@Override
+	public void createPartControl(Composite parent) {
+		super.createPartControl(parent);
+		Control[] children = parent.getChildren();
+		control = children[children.length - 1];
+		getSourceViewer().addTextListener( new ITextListener() {
+			public void textChanged(TextEvent event) {
+				if ( editor != null ) {
+					editor.refreshAllPages();
+				}
+			}
+		});
+		//PlatformUI.getWorkbench().getHelpSystem().setHelp(fControl, IHelpContextIds.MANIFEST_SOURCE_PAGE);
+	}
+
+	public void initialize(FormEditor editor) {
+		this.editor = (SigilProjectEditorPart) editor;
+	}
+
+	public FormEditor getEditor() {
+		return editor;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public int getIndex() {
+		return index;
+	}
+	
+	public void setIndex(int index) {
+		this.index = index;
+	}	
+
+	public boolean isActive() {
+		return active;
+	}
+
+	public void setActive(boolean active) {
+		this.active = active;
+	}
+	
+	public Control getPartControl() {
+		return control;
+	}
+
+	public boolean selectReveal(Object object) {
+		if (object instanceof IMarker) {
+			IDE.gotoMarker(this, (IMarker) object);
+			return true;
+		}
+		return false;
+	}
+
+	// static impls
+	public boolean isEditor() {
+		return true;
+	}
+
+	public boolean canLeaveThePage() {
+		return true;
+	}
+
+	public IManagedForm getManagedForm() {
+		// this is not a form
+		return null;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/TestingSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/TestingSection.java
new file mode 100644
index 0000000..ca0eb01
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/TestingSection.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.ui.ILaunchShortcut;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Hyperlink;
+import org.eclipse.ui.forms.widgets.Section;
+
+public class TestingSection extends SigilSection {
+	
+	public TestingSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}    
+
+    protected void createSection(Section section,FormToolkit toolkit ) {
+        setTitle("Testing");
+        
+		Composite body = createTableWrapBody(1, toolkit);
+
+        toolkit.createLabel( body, "Test this project by launching a separate Newton application:" );
+        
+        Hyperlink launch = toolkit.createHyperlink( body, "Launch a newton container", SWT.NULL );
+        launch.setHref( "launchShortcut.run.org.cauldron.sigil.launching.shortcut" );
+        launch.addHyperlinkListener(this);
+        
+        Hyperlink debug = toolkit.createHyperlink( body, "Debug a newton container", SWT.NULL );
+        debug.setHref( "launchShortcut.debug.org.cauldron.sigil.launching.shortcut" );
+        debug.addHyperlinkListener(this);
+    }
+
+	public void linkActivated(HyperlinkEvent e) {
+		String href = (String) e.getHref();
+		if (href.startsWith("launchShortcut.")) { //$NON-NLS-1$
+			href = href.substring(15);
+			int index = href.indexOf('.');
+			if (index < 0)
+				return;  // error.  Format of href should be launchShortcut.<mode>.<launchShortcutId>
+			String mode = href.substring(0, index);
+			String id = href.substring(index + 1); 
+
+			//getEditor().doSave(null);
+			
+			IExtensionRegistry registry = Platform.getExtensionRegistry();
+			IConfigurationElement[] elements = registry.getConfigurationElementsFor("org.eclipse.debug.ui.launchShortcuts"); //$NON-NLS-1$
+			for (int i = 0; i < elements.length; i++) {
+				if (id.equals(elements[i].getAttribute("id"))) //$NON-NLS-1$
+					try {
+						ILaunchShortcut shortcut = (ILaunchShortcut)elements[i].createExecutableExtension("class"); //$NON-NLS-1$
+						// preLaunch();
+						shortcut.launch(new StructuredSelection(getLaunchObject()), mode);
+					} catch (CoreException e1) {
+						e1.printStackTrace();
+					}
+			}
+		}
+	}
+
+	private Object getLaunchObject() {
+		return getProjectModel().getProject();
+	}
+
+	public void linkEntered(HyperlinkEvent e) {
+	}
+
+	public void linkExited(HyperlinkEvent e) {
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ToolsSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ToolsSection.java
new file mode 100644
index 0000000..22d9824
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/ToolsSection.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.actions.PruneProjectDependenciesAction;
+import org.cauldron.sigil.actions.ResolveProjectDependenciesAction;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.form.SigilPage;
+import org.cauldron.sigil.ui.form.SigilSection;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Hyperlink;
+import org.eclipse.ui.forms.widgets.Section;
+
+public class ToolsSection extends SigilSection {
+	
+	public ToolsSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super( page, parent, project );
+	}    
+
+    protected void createSection(Section section,FormToolkit toolkit ) {
+        setTitle("Tools");
+        
+		Composite body = createTableWrapBody(1, toolkit);
+
+        toolkit.createLabel( body, "Tools to help manage this project:" );
+        
+        Hyperlink launch = toolkit.createHyperlink( body, "Resolve missing dependencies", SWT.NULL );
+        launch.setHref( "resolve" );
+        launch.addHyperlinkListener(this);
+        
+        Hyperlink debug = toolkit.createHyperlink( body, "Prune unused dependencies", SWT.NULL );
+        debug.setHref( "prune" );
+        debug.addHyperlinkListener(this);
+    }
+
+	public void linkActivated(HyperlinkEvent e) {
+		String href = (String) e.getHref();
+		if ( "resolve".equals( href ) ) {
+			handleResolve();
+		}
+		else if ( "prune".equals( href ) ) {
+			handlePrune();
+		}
+	}
+
+	private void handlePrune() {
+		new PruneProjectDependenciesAction(getProjectModel()).run();
+	}
+
+	private void handleResolve() {
+		final ISigilProjectModel project = getProjectModel();
+		new ResolveProjectDependenciesAction(project, true).run();
+	}
+
+	public void linkEntered(HyperlinkEvent e) {
+	}
+
+	public void linkExited(HyperlinkEvent e) {
+	}
+	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/VersionRangeComponent.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/VersionRangeComponent.java
new file mode 100644
index 0000000..7591b38
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/VersionRangeComponent.java
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.ui.util.IValidationListener;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+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.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.framework.Version;
+
+public class VersionRangeComponent extends Composite {
+	private VersionRange versions = VersionRange.ANY_VERSION;
+	
+	private Button specificButton;
+	
+	private Text specificText;
+	private Button rangeButton;
+	private Text minimumText;
+	private Text maximumText;
+	private Button minInclusiveButton;
+	private Button maxInclusiveButton;
+	
+	private Set<VersionsChangeListener> listeners = new HashSet<VersionsChangeListener>();
+	private Set<IValidationListener> validationListeners = new HashSet<IValidationListener>();
+	
+	public VersionRangeComponent(Composite parent, int style) {
+		super( parent, style );
+		createComponents(this);
+	}
+	
+	public void addVersionChangeListener(VersionsChangeListener listener) {
+		synchronized(listeners) {
+			listeners.add( listener );
+		}
+	}
+	
+	public void removeVersionChangeListener(VersionsChangeListener listener) {
+		synchronized(listeners) {
+			listeners.remove( listener );
+		}
+	}
+	
+	public void addValidationListener(IValidationListener listener) {
+		validationListeners.add(listener);
+	}
+	
+	public void removeValidationListener(IValidationListener listener) {
+		validationListeners.remove(listener);
+	}
+	
+	@Override
+	public void setEnabled(boolean enabled) {
+		super.setEnabled(enabled);
+		specificButton.setEnabled(enabled);
+		rangeButton.setEnabled(enabled);
+		if ( enabled ) {
+			specificButton.setSelection(versions.isPointVersion());
+			setSpecific();
+		}
+		else {
+			minimumText.setEnabled(enabled);
+			maximumText.setEnabled(enabled);
+			minInclusiveButton.setEnabled(enabled);
+			maxInclusiveButton.setEnabled(enabled);
+		}
+	}
+
+
+	public VersionRange getVersions() {
+		return versions;
+	}
+	
+	public void setVersions(VersionRange versions) {
+		this.versions = versions == null ? VersionRange.ANY_VERSION : versions;
+		updateFields();
+	}
+
+	private void updateFields() {
+		if ( versions.isPointVersion() ) {
+			specificButton.setSelection(true);
+			specificText.setText( versions.getCeiling() == VersionRange.INFINITE_VERSION ? "*" : versions.getFloor().toString() );
+		}
+		else {
+			rangeButton.setSelection( true );
+			minimumText.setText( versions.getFloor().toString() );
+			minInclusiveButton.setSelection( !versions.isOpenFloor() );
+			maximumText.setText( versions.getCeiling() == VersionRange.INFINITE_VERSION ? "*" : versions.getCeiling().toString() );
+			maxInclusiveButton.setSelection( !versions.isOpenCeiling() );
+		}
+		
+		setSpecific();		
+	}
+
+	private void createComponents(Composite body) {
+		setLayout( new GridLayout( 3, false ) );
+		
+		specificButton = new Button( body, SWT.RADIO );
+		specificButton.setText( "Specific:" );
+		specificButton.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				setSpecific();
+			}			
+		});
+		
+		new Label( body, SWT.NONE ).setText("Version:");
+		
+		specificText = new Text( body, SWT.BORDER );
+		specificText.addKeyListener( new KeyAdapter() {
+			@Override
+			public void keyReleased(KeyEvent e) {
+				setVersions();
+			}			
+		});
+		
+		rangeButton = new Button( body, SWT.RADIO );
+		rangeButton.setText( "Range:" );
+		
+		new Label(body, SWT.NONE).setText("Minimum:");
+		
+		minimumText = new Text( body, SWT.BORDER );
+		minimumText.addKeyListener( new KeyAdapter() {
+			@Override
+			public void keyReleased(KeyEvent e) {
+				setVersions();
+			}			
+		});
+		
+		minInclusiveButton = new Button( body, SWT.CHECK );
+		minInclusiveButton.setText( "inclusive" );
+		minInclusiveButton.setSelection(true);
+		minInclusiveButton.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				setVersions();
+			}
+		});
+		
+		new Label( body, SWT.NONE ).setText("Maximum:");
+		maximumText = new Text( body, SWT.BORDER );
+		maximumText.addKeyListener( new KeyAdapter() {
+			@Override
+			public void keyReleased(KeyEvent e) {
+				setVersions();
+			}			
+		});
+
+		maxInclusiveButton = new Button( body, SWT.CHECK );
+		maxInclusiveButton.setText( "inclusive" );
+		maxInclusiveButton.setSelection(false);
+		maxInclusiveButton.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				setVersions();
+			}
+		});
+		
+		// Layout
+		specificButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
+		specificText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
+		rangeButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
+		minimumText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		maximumText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+
+		updateFields();
+	}
+	
+	private void setVersions() {
+		try {
+			if ( specificButton.getSelection() ) {
+				if ( "*".equals( specificText.getText() ) ) {
+					versions = VersionRange.ANY_VERSION;
+				}
+				else if ( specificText.getText().trim().length() == 0 ) {
+					versions = null;
+				}
+				else {
+					Version v = Version.parseVersion( specificText.getText().trim() );
+					versions = new VersionRange( false, v, v, false);
+				}
+			}
+			else {
+				Version min = Version.parseVersion( minimumText.getText() );
+				Version max = "*".equals( maximumText.getText() ) ? VersionRange.INFINITE_VERSION : Version.parseVersion( maximumText.getText() );
+				versions = new VersionRange( !minInclusiveButton.getSelection(), min, max, !maxInclusiveButton.getSelection() );
+			}
+			fireValidationMessage(null, IMessageProvider.NONE);
+		}
+		catch (IllegalArgumentException e) {
+			versions = null;
+			fireValidationMessage("Invalid version", IMessageProvider.ERROR);
+		}
+
+		fireVersionChange();
+	}
+	
+	private void fireVersionChange() {
+		synchronized( listeners ) {
+			for ( VersionsChangeListener l : listeners ) {
+				l.versionsChanged(versions);
+			}
+		}
+	}
+
+	private void fireValidationMessage(String message, int level) {
+		for (IValidationListener validationListener : validationListeners) {
+			validationListener.validationMessage(message, level);
+		}
+	}
+
+	private void setSpecific() {
+		boolean specific = specificButton.getSelection();
+		specificButton.setSelection(specific);
+		specificText.setEnabled(specific);
+		minimumText.setEnabled(!specific);
+		maximumText.setEnabled(!specific);
+		minInclusiveButton.setEnabled(!specific);
+		maxInclusiveButton.setEnabled(!specific);
+		setVersions();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/VersionsChangeListener.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/VersionsChangeListener.java
new file mode 100644
index 0000000..d97bdc2
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/VersionsChangeListener.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.cauldron.sigil.model.common.VersionRange;
+
+public interface VersionsChangeListener {
+	void versionsChanged(VersionRange range);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/WrappedContentProposal.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/WrappedContentProposal.java
new file mode 100644
index 0000000..6f0e397
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/editors/project/WrappedContentProposal.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.editors.project;
+
+import org.eclipse.jface.fieldassist.IContentProposal;
+
+public class WrappedContentProposal<T> implements IContentProposal {
+	
+	private final T element;
+	private final IElementDescriptor<? super T> descriptor;
+
+	private WrappedContentProposal(T element, IElementDescriptor<? super T> descriptor) {
+		this.element = element;
+		this.descriptor = descriptor;
+	}
+	
+	public static <T> WrappedContentProposal<T> newInstance(T element, IElementDescriptor<? super T> descriptor) {
+		return new WrappedContentProposal<T>(element, descriptor);
+	}
+
+	public String getContent() {
+		return descriptor.getName(element);
+	}
+
+	public int getCursorPosition() {
+		return 0;
+	}
+
+	public String getDescription() {
+		return null;
+	}
+
+	public String getLabel() {
+		return descriptor.getLabel(element);
+	}
+
+	public T getElement() {
+		return element;
+	}
+
+	@Override
+	public String toString() {
+		return getLabel();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/IFormValueConverter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/IFormValueConverter.java
new file mode 100644
index 0000000..34c8bb6
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/IFormValueConverter.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.form;
+
+public interface IFormValueConverter {
+	String getLabel(Object value);
+	Object getValue(String label);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/ISigilFormEntryListener.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/ISigilFormEntryListener.java
new file mode 100644
index 0000000..89afefa
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/ISigilFormEntryListener.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.form;
+
+public interface ISigilFormEntryListener {
+	void browseButtonSelected(SigilFormEntry form);
+	void textValueChanged(SigilFormEntry form);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilFormEntry.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilFormEntry.java
new file mode 100644
index 0000000..ea5bf9d
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilFormEntry.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.form;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+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.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.forms.IFormColors;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+
+public class SigilFormEntry {
+	private static final IFormValueConverter NULL_DESCRIPTOR = new IFormValueConverter() {
+		public String getLabel(Object value) {
+			return (String) value;
+		}
+
+		public Object getValue(String label) {
+			return label;
+		}
+	};
+	
+	private Label lbl;
+	private Text txt;
+	private Button btn;
+	private IFormValueConverter descriptor;
+	private boolean freeText = true;
+	
+	private Object value;
+	private ISigilFormEntryListener listener;
+	
+	public SigilFormEntry(Composite parent, FormToolkit toolkit, String title) {
+		this(parent, toolkit, title, null, null);
+	}
+	
+	public SigilFormEntry(Composite parent, FormToolkit toolkit, String title, String browse, IFormValueConverter descriptor) {
+		this.descriptor = descriptor == null ? NULL_DESCRIPTOR : descriptor;
+		createComponent(parent, title, browse, toolkit);
+	}
+	
+	public void setFormEntryListener(ISigilFormEntryListener listener) {
+		this.listener = listener;
+	}
+	
+	public void setValue(Object value) {
+		this.value = value;
+		String text = descriptor.getLabel(value);
+		if ( text == null ) {
+			text = "";
+		}
+		txt.setText(text);
+		handleValueChanged();
+	}
+	
+	public Object getValue() {
+		return value;
+	}
+
+	public void setFreeText(boolean freeText) {
+		this.freeText = freeText;
+	}
+	
+	public void setEditable(boolean editable) {
+		lbl.setEnabled(editable);
+		txt.setEditable(editable);
+		if ( btn != null ) {
+			btn.setEnabled(editable);
+		}
+	}	
+		
+	private void createComponent(Composite parent, String title, String browse, FormToolkit toolkit) {
+		lbl = toolkit.createLabel(parent, title);
+		lbl.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
+		
+		txt = toolkit.createText(parent, "", SWT.SINGLE | SWT.BORDER);
+		txt.addKeyListener( new KeyAdapter() {
+			@Override
+			public void keyPressed(KeyEvent e) {
+				if ( freeText ) {
+					switch ( e.character ) {
+					case '\r': handleValueChanged(); 
+					}
+				}
+				else {
+					switch ( e.character ) {
+					case '\b': 
+						setValue(null); 
+						handleValueChanged(); 
+					default:
+						e.doit = false;
+						break;
+					}
+				}
+			}
+		});
+		txt.addFocusListener( new FocusAdapter() {
+			@Override
+			public void focusLost(FocusEvent e) {
+				handleValueChanged();
+			}
+		});
+		
+		if ( browse != null ) {
+			btn = toolkit.createButton(parent, browse, SWT.PUSH);
+			btn.addSelectionListener( new SelectionAdapter() {
+				@Override
+				public void widgetSelected(SelectionEvent e) {
+					handleBrowseSelected();
+				}
+			});
+		}
+		
+		fillIntoGrid(parent);
+	}
+
+	private void handleBrowseSelected() {
+		if ( listener != null ) {
+			listener.browseButtonSelected(this);
+		}
+	}
+
+	private void handleValueChanged() {
+		if ( freeText ) {
+			this.value = descriptor.getValue(txt.getText());
+		}
+		if ( listener != null ) {
+			listener.textValueChanged(this);
+		}
+	}
+
+	private void fillIntoGrid(Composite parent) {
+		if ( parent.getLayout() instanceof GridLayout ) {
+			GridLayout layout = (GridLayout) parent.getLayout();
+			
+			int cols = layout.numColumns;
+			
+			lbl.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, false, false) );
+			
+			if ( btn == null ) {
+				txt.setLayoutData( new GridData(SWT.FILL, SWT.CENTER, true, false, Math.max(1, cols - 1), 1 ) );
+			}
+			else {
+				txt.setLayoutData( new GridData(SWT.FILL, SWT.CENTER, true, false, Math.max(1, cols - 2), 1 ) );
+			}
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilFormEntryAdapter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilFormEntryAdapter.java
new file mode 100644
index 0000000..3170d01
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilFormEntryAdapter.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.form;
+
+public class SigilFormEntryAdapter implements ISigilFormEntryListener{
+	public void browseButtonSelected(SigilFormEntry form) {
+	}
+
+	public void textValueChanged(SigilFormEntry form) {
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilPage.java
new file mode 100644
index 0000000..a2873ad
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilPage.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.form;
+
+import org.eclipse.ui.forms.editor.FormEditor;
+import org.eclipse.ui.forms.editor.FormPage;
+
+public class SigilPage extends FormPage {
+
+	public SigilPage(FormEditor editor, String id, String title) {
+		super(editor, id, title);
+	}
+	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilSection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilSection.java
new file mode 100644
index 0000000..b825ec0
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/form/SigilSection.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.form;
+
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.pde.internal.ui.editor.FormLayoutFactory;
+import org.eclipse.pde.internal.ui.parts.FormEntry;
+import org.eclipse.pde.internal.ui.parts.IFormEntryListener;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.forms.IFormPart;
+import org.eclipse.ui.forms.IPartSelectionListener;
+import org.eclipse.ui.forms.SectionPart;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.widgets.ExpandableComposite;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.TableWrapData;
+import org.eclipse.ui.forms.widgets.TableWrapLayout;
+
+@SuppressWarnings("restriction")
+public abstract class SigilSection extends SectionPart implements IFormEntryListener, IPartSelectionListener {
+
+	private SigilPage page;
+	private ISigilProjectModel project;
+	
+	public SigilSection(SigilPage page, Composite parent, ISigilProjectModel project) throws CoreException {
+		super(parent, page.getManagedForm().getToolkit(), ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED );
+		this.project = project;
+		this.page = page;
+		createSection( getSection(), page.getManagedForm().getToolkit() );		
+	}
+
+	public ISigilProjectModel getProjectModel() {
+		return project;
+	}
+	
+	public SigilPage getPage() {
+		return page;
+	}
+	
+	public void setExpanded( boolean expanded ) {
+		getSection().setExpanded(expanded);
+	}
+	
+	protected abstract void createSection(Section section,FormToolkit toolkit ) throws CoreException;
+	
+	protected void setTitle( String title ) {
+			Section section = getSection();
+	        section.setText( title );
+			section.setLayout(FormLayoutFactory.createClearTableWrapLayout(false, 1));
+			TableWrapData data = new TableWrapData(TableWrapData.FILL_GRAB);
+			section.setLayoutData(data);				
+	}
+	
+	protected void setMarker(String type, String message, int priority, int severity) throws CoreException {
+		IFileEditorInput file = (IFileEditorInput) getPage().getEditor().getEditorInput();
+		IMarker marker = file.getFile().createMarker(type);
+		marker.setAttribute( IMarker.MESSAGE, message );
+		marker.setAttribute(IMarker.PRIORITY, priority);
+		marker.setAttribute( IMarker.SEVERITY, severity );
+	}
+	
+	protected void clearMarkers() throws CoreException {
+		IFileEditorInput file = (IFileEditorInput) getPage().getEditor().getEditorInput();
+		file.getFile().deleteMarkers(null, true, IResource.DEPTH_INFINITE );
+	}
+	
+	protected Composite createTableWrapBody( int columns, FormToolkit toolkit ) {
+		Section section = getSection();
+        Composite client = toolkit.createComposite(section);
+        
+        TableWrapLayout layout = new TableWrapLayout();
+        layout.leftMargin = layout.rightMargin = toolkit.getBorderStyle() != SWT.NULL ? 0 : 2;
+        layout.numColumns = columns;
+        client.setLayout(layout);
+        client.setLayoutData( new TableWrapData( TableWrapData.FILL_GRAB) );
+		
+        section.setClient(client);
+        
+        return client;
+	}
+	
+	protected Composite createGridBody( int columns, boolean columnsSameWidth, FormToolkit toolkit ) {
+		Section section = getSection();
+        Composite client = toolkit.createComposite(section);
+        
+		GridLayout layout = new GridLayout();
+		
+		layout.makeColumnsEqualWidth = columnsSameWidth;
+		layout.numColumns = columns;
+        client.setLayout(layout);
+        
+        client.setLayoutData( new TableWrapData( TableWrapData.FILL_GRAB) );
+		
+        section.setClient(client);
+        
+        return client;
+	}
+	
+	public void browseButtonSelected(FormEntry entry) {
+	}
+
+	public void focusGained(FormEntry entry) {
+	}
+
+	public void selectionChanged(FormEntry entry) {
+	}
+
+	public void textDirty(FormEntry entry) {
+	}
+
+	public void textValueChanged(FormEntry entry) {
+	}
+
+	public void linkActivated(HyperlinkEvent e) {
+	}
+
+	public void linkEntered(HyperlinkEvent e) {
+	}
+
+	public void linkExited(HyperlinkEvent e) {
+	}
+
+	public void selectionChanged(IFormPart part, ISelection selection) {
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/FileSystemRepositoryWizard.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/FileSystemRepositoryWizard.java
new file mode 100644
index 0000000..df5314b
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/FileSystemRepositoryWizard.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.internal.repository;
+
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+
+public class FileSystemRepositoryWizard extends RepositoryWizard {
+	@Override
+	public void addPages() {
+		addPage( new FileSystemRepositoryWizardPage(this) );
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/FileSystemRepositoryWizardPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/FileSystemRepositoryWizardPage.java
new file mode 100644
index 0000000..a46f7d9
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/FileSystemRepositoryWizardPage.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.internal.repository;
+
+import java.io.File;
+
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizardPage;
+import org.eclipse.jface.preference.BooleanFieldEditor;
+import org.eclipse.jface.preference.DirectoryFieldEditor;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+
+public class FileSystemRepositoryWizardPage extends RepositoryWizardPage
+		implements IWizardPage {
+
+	private DirectoryFieldEditor dirEditor;
+
+	protected FileSystemRepositoryWizardPage(RepositoryWizard parent) {
+		super("File System Repository", parent);
+	}
+
+	@Override
+	public void createFieldEditors() {
+		dirEditor = new DirectoryFieldEditor("dir", "Directory:", getFieldEditorParent() );
+		dirEditor.getTextControl( getFieldEditorParent() ).addModifyListener( new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				checkPageComplete();
+			} 
+		} );
+		
+		addField( dirEditor );
+		addField(new BooleanFieldEditor("recurse", "Recurse:", getFieldEditorParent() ) );
+	}
+
+	@Override
+	protected void checkPageComplete() {
+		super.checkPageComplete();
+		if ( isPageComplete() ) {
+			setPageComplete(dirEditor.getStringValue().length() > 0);
+			if ( isPageComplete() ) {
+				if ( new File( dirEditor.getStringValue()).isDirectory() ) {
+					setPageComplete(true);
+					setErrorMessage(null);
+				}
+				else {
+					setPageComplete(false);
+					setErrorMessage("Invalid directory");
+				}
+			}
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/OSGiInstallRepositoryWizard.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/OSGiInstallRepositoryWizard.java
new file mode 100644
index 0000000..ae8da79
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/OSGiInstallRepositoryWizard.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.internal.repository;
+
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+
+public class OSGiInstallRepositoryWizard extends RepositoryWizard {
+	@Override
+	public void addPages() {
+		addPage( new OSGiInstallRepositoryWizardPage(this) );
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/OSGiInstallRepositoryWizardPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/OSGiInstallRepositoryWizardPage.java
new file mode 100644
index 0000000..772b151
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/internal/repository/OSGiInstallRepositoryWizardPage.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.internal.repository;
+
+import java.util.ArrayList;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.install.IOSGiInstall;
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizardPage;
+import org.eclipse.jface.preference.RadioGroupFieldEditor;
+
+public class OSGiInstallRepositoryWizardPage extends RepositoryWizardPage {
+
+	protected OSGiInstallRepositoryWizardPage(RepositoryWizard parent) {
+		super("OSGi Install Repository", parent);
+	}
+
+	@Override
+	public void createFieldEditors() {
+		ArrayList<String[]> installs = new ArrayList<String[]>();
+		for ( String id : SigilCore.getInstallManager().getInstallIDs() ) {
+			IOSGiInstall i = SigilCore.getInstallManager().findInstall(id);
+			installs.add( new String[] { i.getType().getName(), id } );
+		}
+		String[][] strs = installs.toArray( new String[installs.size()][] );
+		
+		RadioGroupFieldEditor editor = new RadioGroupFieldEditor(
+				"id", 
+				"Install", 
+				1, strs, getFieldEditorParent() );
+		
+		addField(editor);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/perspective/SigilPerspectiveFactory.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/perspective/SigilPerspectiveFactory.java
new file mode 100644
index 0000000..34d428c
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/perspective/SigilPerspectiveFactory.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.perspective;
+
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.debug.ui.IDebugUIConstants;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.ui.IFolderLayout;
+import org.eclipse.ui.IPageLayout;
+import org.eclipse.ui.IPerspectiveFactory;
+import org.eclipse.ui.progress.IProgressConstants;
+
+public class SigilPerspectiveFactory implements IPerspectiveFactory {
+	
+	private static final String ID_PROJECT_EXPLORER= "org.eclipse.ui.navigator.ProjectExplorer"; //$NON-NLS-1$
+	private static final String ID_SEARCH_VIEW = "org.eclipse.search.ui.views.SearchView"; //$NON-NLS-1$
+	private static final String ID_CONSOLE_VIEW = "org.eclipse.ui.console.ConsoleView"; //$NON-NLS-1$
+	private static final String ID_TEMPLATES_VIEW = "org.eclipse.ui.texteditor.TemplatesView"; //$NON-NLS-N$
+	
+
+	public void createInitialLayout(IPageLayout layout) {
+		/*
+		 * Use ProjectExplorer vs PackageExplorer due to a bug with Drag and Drop on Mac OS X which affects PackageExplorer
+		 * but not ProjectExplorer. https://bugs.eclipse.org/bugs/show_bug.cgi?id=243529
+		 */
+ 		String editorArea = layout.getEditorArea();
+		
+		IFolderLayout folder= layout.createFolder("left", IPageLayout.LEFT, (float)0.25, editorArea); //$NON-NLS-1$
+		folder.addView(ID_PROJECT_EXPLORER);
+		folder.addView(JavaUI.ID_TYPE_HIERARCHY);
+		folder.addPlaceholder(IPageLayout.ID_RES_NAV);
+		folder.addPlaceholder(SigilUI.ID_REPOSITORY_VIEW);
+		folder.addView(IPageLayout.ID_OUTLINE);		
+		
+		IFolderLayout outputfolder= layout.createFolder("bottom", IPageLayout.BOTTOM, (float)0.75, editorArea); //$NON-NLS-1$
+		outputfolder.addView(IPageLayout.ID_PROBLEM_VIEW);
+		outputfolder.addView(JavaUI.ID_JAVADOC_VIEW);
+		outputfolder.addView(JavaUI.ID_SOURCE_VIEW);
+		outputfolder.addPlaceholder(ID_SEARCH_VIEW);
+		outputfolder.addPlaceholder(ID_CONSOLE_VIEW);
+		outputfolder.addPlaceholder(IPageLayout.ID_BOOKMARKS);
+		outputfolder.addPlaceholder(IProgressConstants.PROGRESS_VIEW_ID);
+		outputfolder.addPlaceholder(SigilUI.ID_DEPENDENCY_VIEW);
+		outputfolder.addPlaceholder(SigilUI.ID_INSTANCES_VIEW);
+		
+		layout.addActionSet(IDebugUIConstants.LAUNCH_ACTION_SET);
+		layout.addActionSet(JavaUI.ID_ACTION_SET);
+		layout.addActionSet(JavaUI.ID_ELEMENT_CREATION_ACTION_SET);
+		layout.addActionSet(IPageLayout.ID_NAVIGATE_ACTION_SET);
+		
+		// views - sigil
+		layout.addShowViewShortcut(SigilUI.ID_REPOSITORY_VIEW);
+		layout.addShowViewShortcut(SigilUI.ID_DEPENDENCY_VIEW);
+		layout.addShowViewShortcut(SigilUI.ID_INSTANCES_VIEW);
+		
+		// views - java
+		layout.addShowViewShortcut(JavaUI.ID_PACKAGES);
+		layout.addShowViewShortcut(JavaUI.ID_TYPE_HIERARCHY);
+		layout.addShowViewShortcut(JavaUI.ID_SOURCE_VIEW);
+		layout.addShowViewShortcut(JavaUI.ID_JAVADOC_VIEW);
+
+		// views - search
+		layout.addShowViewShortcut(ID_SEARCH_VIEW);
+		
+		// views - debugging
+		layout.addShowViewShortcut(ID_CONSOLE_VIEW);
+
+		// views - standard workbench
+		layout.addShowViewShortcut(IPageLayout.ID_OUTLINE);
+		layout.addShowViewShortcut(IPageLayout.ID_PROBLEM_VIEW);
+		layout.addShowViewShortcut(IPageLayout.ID_RES_NAV);
+		layout.addShowViewShortcut(IPageLayout.ID_TASK_LIST);
+		layout.addShowViewShortcut(IProgressConstants.PROGRESS_VIEW_ID);
+		layout.addShowViewShortcut(ID_PROJECT_EXPLORER);
+				
+		// new actions - Java project creation wizard
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewPackageCreationWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewClassCreationWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewInterfaceCreationWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewEnumCreationWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewAnnotationCreationWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewSourceFolderCreationWizard");	 //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewSnippetFileCreationWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.jdt.ui.wizards.NewJavaWorkingSetWizard"); //$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.ui.wizards.new.folder");//$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.ui.wizards.new.file");//$NON-NLS-1$
+		layout.addNewWizardShortcut("org.eclipse.ui.editors.wizards.UntitledTextFileWizard");//$NON-NLS-1$
+		
+		layout.addNewWizardShortcut("org.cauldron.sigil.editors.newCompositeWizard");
+		layout.addNewWizardShortcut("org.cauldron.sigil.editors.newSystemWizard");
+		layout.addNewWizardShortcut("org.cauldron.sigil.editors.newProjectWizard");
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/ExcludedResourcesPrefsPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/ExcludedResourcesPrefsPage.java
new file mode 100644
index 0000000..134f3c3
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/ExcludedResourcesPrefsPage.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.preferences.PrefsUtils;
+import org.eclipse.core.internal.preferences.PrefsMessages;
+import org.eclipse.jface.dialogs.IInputValidator;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+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.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+public class ExcludedResourcesPrefsPage extends PreferencePage implements IWorkbenchPreferencePage {
+	
+	private TableViewer viewer;
+	private IWorkbench workbench;
+	private ArrayList<String> resources;
+
+	public ExcludedResourcesPrefsPage() {
+		super();
+		setDescription("Specify resources that should not be offered for inclusion in a generated bundle.");
+	}
+
+	@Override
+	protected Control createContents(Composite parent) {
+		// Create controls
+		Composite composite = new Composite(parent, SWT.NONE);
+		Table table = new Table(composite, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
+		
+		Button btnAdd = new Button(composite, SWT.PUSH);
+		btnAdd.setText("Add");
+		
+		final Button btnRemove = new Button(composite, SWT.PUSH);
+		btnRemove.setText("Remove");
+		btnRemove.setEnabled(false);
+		
+		// Create viewer
+		viewer = new TableViewer(table);
+		viewer.setContentProvider(new ArrayContentProvider());
+		
+		// Load data
+		loadPreferences(false);
+		viewer.setInput(resources);
+		
+		// Listeners
+		viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				btnRemove.setEnabled(!viewer.getSelection().isEmpty());
+			}
+		});
+		
+		btnRemove.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+				Object[] deleted = selection.toArray();
+				for(Object delete : deleted) {
+					resources.remove(delete);
+				}
+				viewer.remove(deleted);
+			}
+		});
+		
+		btnAdd.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				InputDialog dialog = new InputDialog(getShell(), "Add Resource", "Enter resource name", "", new IInputValidator() {
+					public String isValid(String newText) {
+						String error = null;
+						if(newText == null || newText.length() == 0) {
+							error = "Name must not be empty.";
+						} else if(resources.contains(newText)) {
+							error = "Specified resource name is already on the list.";
+						}
+						return error;
+					}
+				});
+				
+				if(dialog.open() == Window.OK) {
+					String value = dialog.getValue();
+					resources.add(value);
+					viewer.add(value);
+				}
+			}
+		});
+		
+		// Layout
+		composite.setLayout(new GridLayout(2, false));
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3));
+		btnAdd.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		btnRemove.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		
+		return composite;
+	}
+
+	private void loadPreferences(boolean useDefaults) {
+		String resourcesListStr = useDefaults
+				? getPreferenceStore().getDefaultString(SigilCore.DEFAULT_EXCLUDED_RESOURCES)
+				: getPreferenceStore().getString(SigilCore.DEFAULT_EXCLUDED_RESOURCES);
+		String[] resourcesArray = PrefsUtils.stringToArray(resourcesListStr);
+		
+		resources = new ArrayList<String>(resourcesArray.length);
+		for (String resource : resourcesArray) {
+			resources.add(resource);
+		}
+	}
+
+	public void init(IWorkbench workbench) {
+		this.workbench = workbench;
+	}
+	
+	@Override
+	protected IPreferenceStore doGetPreferenceStore() {
+		return SigilCore.getDefault().getPreferenceStore();
+	}
+	
+	@Override
+	public boolean performOk() {
+		String resourcesStr = PrefsUtils.arrayToString(resources.toArray(new String[resources.size()]));
+		getPreferenceStore().setValue(SigilCore.DEFAULT_EXCLUDED_RESOURCES, resourcesStr);
+		return true;
+	}
+	
+	@Override
+	protected void performDefaults() {
+		super.performDefaults();
+		loadPreferences(true);
+		viewer.setInput(resources);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/LibraryConfigurationDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/LibraryConfigurationDialog.java
new file mode 100644
index 0000000..d7b6786
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/LibraryConfigurationDialog.java
@@ -0,0 +1,300 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IPackageModelElement;
+import org.cauldron.sigil.ui.editors.project.NewResourceSelectionDialog;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.util.ResourcesDialogHelper;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+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.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.framework.Version;
+
+public class LibraryConfigurationDialog extends TitleAreaDialog {
+
+	private static final Comparator<IPackageImport> COMPARATOR = new Comparator<IPackageImport>() {
+		public int compare(IPackageImport o1, IPackageImport o2) {
+			return o1.getPackageName().compareTo(o2.getPackageName());
+		}
+	};
+	
+	private String name;
+	private Version version;
+	private TreeSet<IPackageImport> packageImports = new TreeSet<IPackageImport>(COMPARATOR);
+	
+	private boolean editOnly;
+
+	private TableViewer viewer;
+	private Text txtName;
+	private Text txtVersion;
+	
+	public LibraryConfigurationDialog(Shell parentShell) {
+		super(parentShell);
+		name = "";
+		version = Version.emptyVersion;
+	}
+
+	public LibraryConfigurationDialog(Shell parentShell, ILibrary lib) {
+		super(parentShell);
+		editOnly = true;
+		name = lib.getName();
+		version = lib.getVersion();
+		packageImports.addAll(  lib.getImports() );
+	}
+	
+	@Override
+	protected Control createDialogArea(Composite par) {
+		setTitle("Add Library");
+		Composite container = (Composite) super.createDialogArea(par);
+		
+		Composite topPanel = new Composite(container, SWT.NONE);
+				
+		new Label(topPanel, SWT.NONE).setText("Name");
+		
+		txtName = new Text(topPanel, SWT.BORDER);
+		txtName.setEditable( !editOnly );
+		if(name != null) txtName.setText(name);
+		
+		new Label(topPanel, SWT.NONE).setText("Version");
+		
+		txtVersion = new Text(topPanel, SWT.BORDER);
+		txtVersion.setText( version.toString() );
+		txtVersion.setEditable( !editOnly );
+		
+		Composite bottomPanel = new Composite(container, SWT.NONE);
+		
+		Table table = new Table(bottomPanel, SWT.BORDER);
+		table.setSize( new Point( 300, 200 ) );
+		
+		Button add = new Button(bottomPanel, SWT.PUSH);
+		add.setText("Add...");
+		
+		final Button edit = new Button(bottomPanel, SWT.PUSH);
+		edit.setText("Edit...");
+		edit.setEnabled(false);
+		
+		final Button remove = new Button(bottomPanel, SWT.PUSH);
+		remove.setText("Remove");
+		remove.setEnabled(false);
+		
+		updateState();
+		
+		// Hookup Listeners
+		txtName.addModifyListener(new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				updateState();
+			}
+		});
+		txtVersion.addModifyListener(new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				updateState();
+			}
+		});
+		add.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleAdd();
+			}
+		});
+		edit.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleEdit();
+			}
+		});
+		remove.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleRemove();
+			}
+		});
+		
+		// Layout
+		topPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		topPanel.setLayout(new GridLayout(2, false));
+		txtName.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		txtVersion.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		
+		bottomPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+		bottomPanel.setLayout(new GridLayout(2, false));
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 4));
+		add.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		edit.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		remove.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		
+		// Table Viewer
+		viewer = new TableViewer(table);
+		viewer.setLabelProvider(new LabelProvider() {
+			@Override
+			public String getText(Object element) {
+				IPackageImport pi = (IPackageImport) element;
+				return pi.getPackageName() + " " + pi.getVersions();
+			}			
+		});
+		viewer.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				edit.setEnabled(!event.getSelection().isEmpty());
+				remove.setEnabled(!event.getSelection().isEmpty());
+			}
+		});
+		viewer.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		} );
+
+		viewer.setInput(packageImports);
+		return container;
+	}
+	
+	private void updateState() {
+		String error = null;
+		String warning = null;
+		
+		name = txtName.getText();
+		
+		try {
+			version = new Version(txtVersion.getText());
+			if(version.getQualifier().indexOf('_') > -1) {
+				warning = "The use of underscores in a version qualifier is discouraged.";
+			}
+		} catch (IllegalArgumentException e) {
+			version = null;
+			error = "Invalid version format";
+		}
+		
+		Button okButton = getButton(IDialogConstants.OK_ID);
+		if(okButton != null && !okButton.isDisposed()) okButton.setEnabled(allowOkay());
+		
+		setErrorMessage(error);
+		setMessage(warning, IMessageProvider.WARNING);
+	}
+	
+	private boolean allowOkay() {
+		return name != null && name.length() > 0 && version != null;
+	}
+	
+	@Override
+	protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
+		Button button = super.createButton(parent, id, label, defaultButton);
+		if(id == IDialogConstants.OK_ID) {
+			button.setEnabled(allowOkay());
+		}
+		return button;
+	}
+	
+	private void handleAdd() {
+		/*NewResourceSelectionDialog<? extends IPackageModelElement> dialog = ResourcesDialogHelper.createImportDialog(getShell(), "Add Imported Package", null, packageImports);
+		if ( dialog.open() == Window.OK ) {
+			IPackageImport pi = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+			pi.setPackageName(dialog.getSelectedName());
+			pi.setVersions(dialog.getSelectedVersions());
+			pi.setOptional(dialog.isOptional());
+			
+			packageImports.add(pi);
+			viewer.refresh();
+		}*/
+	}
+
+	private void handleEdit() {
+		/*IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+		
+		boolean changed = false;
+		
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IPackageImport> i = selection.iterator(); i.hasNext(); ) {	
+				IPackageImport packageImport = i.next();
+				NewResourceSelectionDialog<? extends IPackageModelElement> dialog = ResourcesDialogHelper.createImportDialog( getShell(), "Edit Imported Package", packageImport, packageImports );
+				dialog.setVersions( packageImport.getVersions() );
+				dialog.setOptional(packageImport.isOptional());
+				if ( dialog.open() == Window.OK ) {
+					changed = true;
+					String packageName = dialog.getSelectedName();
+					VersionRange versionRange = dialog.getSelectedVersions();
+					
+					IPackageImport newImport = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+					newImport.setPackageName(packageName);
+					newImport.setVersions(versionRange);
+					newImport.setOptional(dialog.isOptional());
+					
+					packageImports.remove(packageImport);
+					packageImports.add(newImport);
+				}
+			}
+		}
+		
+		if ( changed ) {
+			viewer.refresh();
+		} */
+	}
+
+	private void handleRemove() {
+		IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+
+		if ( !selection.isEmpty() ) {
+			for ( Iterator<IPackageImport> i = selection.iterator(); i.hasNext(); ) {			
+				packageImports.remove( i.next() );
+			}		
+			
+			viewer.refresh();
+		}
+	}
+
+	public ILibrary getLibrary() {
+		ILibrary library = ModelElementFactory.getInstance().newModelElement(ILibrary.class);
+		
+		library.setName(name);
+		library.setVersion(version);
+		
+		for (IPackageImport pi : packageImports) {
+			library.addImport(pi);
+		}
+		
+		return library;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/LibraryPreferencePage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/LibraryPreferencePage.java
new file mode 100644
index 0000000..cf77287
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/LibraryPreferencePage.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;

+

+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.eclipse.ILibrary;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.preference.IPreferenceStore;

+import org.eclipse.jface.preference.PreferencePage;

+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;

+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.Button;
+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.swt.widgets.Control;

+import org.eclipse.swt.widgets.Label;

+import org.eclipse.swt.widgets.Table;
+import org.eclipse.ui.IWorkbench;

+import org.eclipse.ui.IWorkbenchPreferencePage;

+

+public class LibraryPreferencePage extends PreferencePage implements

+		IWorkbenchPreferencePage {

+

+	private TreeSet<ILibrary> libraries;
+	private TableViewer libraryView;
+	
+	private Table table;
+	private Button btnAdd;
+	private Button btnEdit;
+	private Button btnRemove;
+	
+	public void init(IWorkbench workbench) {

+	}

+	

+	@Override

+	protected Control createContents(Composite parent) {

+		Control control = initContents( parent );

+		loadPreferences();

+		return control;

+	}

+

+	@Override

+	protected IPreferenceStore doGetPreferenceStore() {

+		return SigilCore.getDefault().getPreferenceStore();

+	}

+

+	@Override

+	protected void performDefaults() {

+		super.performDefaults();

+	}

+

+	@Override

+	public boolean performOk() {

+		IPreferenceStore prefs = getPreferenceStore();
+		for ( String key : prefs.getString( SigilCore.LIBRARY_KEYS_PREF ).split( "," ) ) {
+			prefs.setToDefault(key);
+		}
+		
+		StringBuffer keys = new StringBuffer();
+		
+		for ( ILibrary lib : libraries ) {
+			throw new IllegalStateException( "XXX-FIXME-XXX");
+		}
+		
+		prefs.setValue( SigilCore.LIBRARY_KEYS_PREF, keys.toString() );
+		
+		return true;
+	}

+	

+	private Control initContents(Composite parent) {

+		Composite control = new Composite(parent, SWT.NONE);

+		control.setFont(parent.getFont());
+		

+		GridLayout grid = new GridLayout(3, false);

+		control.setLayout(grid);

+		

+		initRepositories(control);
+		
+		return control;

+	}
+	
+	private void initRepositories(Composite composite) {
+		// Create controls
+		new Label(composite, SWT.NONE).setText("Libraries:");
+		new Label(composite, SWT.NONE); // Spacer
+		table = new Table(composite, SWT.SINGLE | SWT.BORDER );
+		//table.setFont(control.getFont());
+		btnAdd = new Button(composite, SWT.PUSH);
+		btnAdd.setText("Add...");
+		//add.setFont(control.getFont());
+		btnEdit = new Button(composite, SWT.PUSH);
+		btnEdit.setText("Edit...");
+		//edit.setFont(control.getFont());
+		btnRemove = new Button(composite, SWT.PUSH);
+		btnRemove.setText("Remove");
+		//remove.setFont(control.getFont());
+		
+		// Table Model
+		libraries = new TreeSet<ILibrary>(new Comparator<ILibrary> () {
+			public int compare(ILibrary l1, ILibrary l2) {
+				int c = l1.getName().compareTo( l2.getName() );
+				if ( c == 0 ) {
+					c = l1.getVersion().compareTo( l2.getVersion() );
+				}
+				return c;
+			}
+		});
+		libraryView = new TableViewer(table);
+		libraryView.setLabelProvider(new LabelProvider() {
+			public String getText(Object element) {
+				ILibrary rep = (ILibrary) element;
+				return rep.getName() + " " + rep.getVersion();
+			}			
+		});
+		libraryView.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		} );
+		libraryView.setInput(libraries);
+		
+		// Initialize controls
+		updateButtonStates();
+		
+		// Hookup Listeners
+		libraryView.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				updateButtonStates();
+			}
+		});
+		btnAdd.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleAdd();
+			}
+		});
+		btnEdit.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleEdit();
+			}
+		});
+		btnRemove.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				handleRemove();
+			}
+		});
+		
+		// Layout
+		composite.setLayout(new GridLayout(2, false));
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 4));
+		GridDataFactory buttonGD = GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER);
+		btnAdd.setLayoutData(buttonGD.create());
+		btnEdit.setLayoutData(buttonGD.create());
+		btnRemove.setLayoutData(buttonGD.create());
+	}
+	
+	private void updateButtonStates() {
+		ISelection sel = libraryView.getSelection();
+		btnEdit.setEnabled(!sel.isEmpty());
+		btnRemove.setEnabled(!sel.isEmpty());
+	}
+	
+
+	private void handleAdd() {
+		LibraryConfigurationDialog d = new LibraryConfigurationDialog(getShell());
+		if ( d.open() == Window.OK ) {
+			libraries.add(d.getLibrary());
+			libraryView.refresh();
+		}
+	}
+
+	private void handleEdit() {
+		IStructuredSelection sel = (IStructuredSelection) libraryView.getSelection();
+		boolean change = false;
+		
+		
+		for ( @SuppressWarnings("unchecked") Iterator<ILibrary> i = sel.iterator(); i.hasNext(); ) {
+			ILibrary lib = i.next();
+			LibraryConfigurationDialog d = new LibraryConfigurationDialog(getShell(), lib );
+			if ( d.open() == Window.OK ) {
+				libraries.remove(lib);
+				libraries.add(d.getLibrary());
+				change = true;
+			}
+		}
+		
+		if ( change ) {
+			libraryView.refresh();
+		}
+	}
+
+	private void handleRemove() {
+		IStructuredSelection sel = (IStructuredSelection) libraryView.getSelection();
+		for ( @SuppressWarnings("unchecked") Iterator<ILibrary> i = sel.iterator(); i.hasNext(); ) {
+			libraries.remove(i);
+		}
+		libraryView.refresh();
+	}
+
+	private void loadPreferences() {

+		IPreferenceStore prefs = getPreferenceStore();
+		String keys = prefs.getString( SigilCore.LIBRARY_KEYS_PREF );
+		if ( keys.trim().length() > 0 ) {
+			for ( String key : keys.split( "," ) ) {
+				String libStr = prefs.getString(key);
+				// XXX-FIXME-XXX parse library string
+				// lib = parse(libstr);
+				// libraries.add(lib);
+			}
+			libraryView.refresh();
+		}
+	}

+}

diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/OptionalPrompt.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/OptionalPrompt.java
new file mode 100644
index 0000000..45ace9f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/OptionalPrompt.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;
+
+import org.cauldron.sigil.preferences.PromptablePreference;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialogWithToggle;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.widgets.Shell;
+
+public class OptionalPrompt {
+	public static boolean optionallyPrompt(IPreferenceStore prefStore, String prefName, String title, String text, Shell parentShell) {
+		boolean result = false;
+		
+		PromptablePreference value = PromptablePreference.valueOf(prefStore.getString(prefName));
+		switch(value) {
+		case Always:
+			result = true;
+			break;
+		case Never:
+			result = false;
+			break;
+		case Prompt:
+			MessageDialogWithToggle dialog = MessageDialogWithToggle.openYesNoQuestion(parentShell, title, text, "Do not ask this again", false, null, null);
+			result = (dialog.getReturnCode() == IDialogConstants.YES_ID);
+			if(dialog.getToggleState()) {
+				// User said don't ask again... take the current answer as the new preference
+				prefStore.setValue(prefName, result ? PromptablePreference.Always.name() : PromptablePreference.Never.name());
+			}
+		}
+		
+		return result;
+	}
+	
+	public static int optionallyPromptWithCancel(IPreferenceStore prefStore, String prefName, String title, String text, Shell parentShell) {
+		int result = IDialogConstants.NO_ID;
+		
+		PromptablePreference value = PromptablePreference.valueOf(prefStore.getString(prefName));
+		switch(value) {
+		case Always:
+			result = IDialogConstants.YES_ID;
+			break;
+		case Never:
+			result = IDialogConstants.NO_ID;
+			break;
+		case Prompt:
+			MessageDialogWithToggle dialog = MessageDialogWithToggle.openYesNoCancelQuestion(parentShell, title, text, "Do not ask this again", false, null, null);
+			result = dialog.getReturnCode();
+			if ( result != IDialogConstants.CANCEL_ID ) {
+				if(dialog.getToggleState()) {
+					// User said don't ask again... take the current answer as the new preference
+					prefStore.setValue(prefName, (result == IDialogConstants.YES_ID) ? PromptablePreference.Always.name() : PromptablePreference.Never.name());
+				}
+			}
+		}
+		
+		return result;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/ProjectDependentPreferencesPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/ProjectDependentPreferencesPage.java
new file mode 100644
index 0000000..07a564c
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/ProjectDependentPreferencesPage.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;
+
+import org.cauldron.sigil.ui.util.ProjectUtils;
+import org.eclipse.jface.preference.PreferencePage;
+
+public abstract class ProjectDependentPreferencesPage extends PreferencePage {
+
+	public ProjectDependentPreferencesPage(String title) {
+		super(title);
+	}
+
+	@Override
+	public boolean performOk() {
+		if ( isDirty() ) {
+			return ProjectUtils.runTaskWithRebuildCheck(new Runnable() {
+				public void run() {
+					doSave();
+				}
+				
+			}, getShell());
+		}
+		else {
+			return true;
+		}
+	}
+
+	protected abstract void doSave();
+
+	protected abstract boolean isDirty();
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/SigilPreferencePage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/SigilPreferencePage.java
new file mode 100644
index 0000000..4a16093
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/SigilPreferencePage.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.preferences.PromptablePreference;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.RadioGroupFieldEditor;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+public class SigilPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
+
+	@Override
+	protected void createFieldEditors() {
+		RadioGroupFieldEditor impExpField = new RadioGroupFieldEditor(SigilCore.PREFERENCES_ADD_IMPORT_FOR_EXPORT, "Add Imports for New Exports", 1, new String[][] {
+				new String[] { "Always (Recommended)", PromptablePreference.Always.toString() },
+				new String[] { "Prompt", PromptablePreference.Prompt.toString() },
+				new String[] { "Never", PromptablePreference.Never.toString() }
+		}, getFieldEditorParent(), true);
+		
+		addField(impExpField);
+		
+		RadioGroupFieldEditor rebuildExpField = new RadioGroupFieldEditor(SigilCore.PREFERENCES_REBUILD_PROJECTS, "Rebuild Projects On Install Change", 1, new String[][] {
+				new String[] { "Always (Recommended)", PromptablePreference.Always.toString() },
+				new String[] { "Prompt", PromptablePreference.Prompt.toString() },
+				new String[] { "Never", PromptablePreference.Never.toString() }
+		}, getFieldEditorParent(), true);
+		
+		addField(rebuildExpField);		
+	}
+	
+	@Override
+	protected IPreferenceStore doGetPreferenceStore() {
+		return SigilCore.getDefault().getPreferenceStore();
+	}
+
+	public void init(IWorkbench workbench) {
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/VersionsPreferencePage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/VersionsPreferencePage.java
new file mode 100644
index 0000000..82cd79c
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/VersionsPreferencePage.java
@@ -0,0 +1,368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.common.VersionRangeBoundingRule;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+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.Control;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.osgi.framework.Version;
+
+public class VersionsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
+
+	private static final Version SAMPLE_VERSION = new Version(1, 2, 3, "qualifier");
+
+	private IWorkbench workbench;
+
+	private Button btnLowerBoundExact;
+	private Button btnLowerBoundMicro;
+	private Button btnLowerBoundMinor;
+	private Button btnLowerBoundMajor;
+	private Button btnLowerBoundAny;
+
+	private Button btnUpperBoundExact;
+	private Button btnUpperBoundMicro;
+	private Button btnUpperBoundMinor;
+	private Button btnUpperBoundMajor;
+	private Button btnUpperBoundAny;
+
+	private Text txtSampleVersion;
+	private Label lblCalculatedRange;
+	private Text txtMatchVersion;
+	private Label lblMatchResult;
+	
+	private VersionRangeBoundingRule lowerBoundRule;
+	private VersionRangeBoundingRule upperBoundRule;
+
+	private Version sampleVersion = SAMPLE_VERSION;
+	private Version matchVersion = null;
+	
+	public VersionsPreferencePage() {
+		super();
+		setDescription("Specify the Lower and Upper bounds for a default version range calculated from a point version, e.g. \"1.2.3.qualifier\"");
+	}
+	
+	@Override
+	protected Control createContents(Composite parent) {
+		// Create controls
+		Composite composite = new Composite(parent, SWT.NONE);
+
+		Group grpLowerBound = new Group(composite, SWT.NONE);
+		grpLowerBound.setText("Lower Bound");
+		btnLowerBoundExact = new Button(grpLowerBound, SWT.RADIO);
+		btnLowerBoundExact.setText("Exact e.g. [1.2.3.qualifer, ...)");
+		btnLowerBoundMicro = new Button(grpLowerBound, SWT.RADIO);
+		btnLowerBoundMicro.setText("Micro e.g. [1.2.3, ...)");
+		btnLowerBoundMinor = new Button(grpLowerBound, SWT.RADIO);
+		btnLowerBoundMinor.setText("Minor e.g. [1.2, ...)");
+		btnLowerBoundMajor = new Button(grpLowerBound, SWT.RADIO);
+		btnLowerBoundMajor.setText("Major e.g. [1, ...)");
+		btnLowerBoundAny = new Button(grpLowerBound, SWT.RADIO);
+		btnLowerBoundAny.setText("Any e.g. [0, ...)");
+
+		Group grpUpperBound = new Group(composite, SWT.NONE);
+		grpUpperBound.setText("Upper Bound");
+
+		btnUpperBoundExact = new Button(grpUpperBound, SWT.RADIO);
+		btnUpperBoundExact.setText("Exact e.g. [..., 1.2.3.qualifer]");
+		btnUpperBoundMicro = new Button(grpUpperBound, SWT.RADIO);
+		btnUpperBoundMicro.setText("Micro e.g. [..., 1.2.4)");
+		btnUpperBoundMinor = new Button(grpUpperBound, SWT.RADIO);
+		btnUpperBoundMinor.setText("Minor e.g. [..., 1.3)");
+		btnUpperBoundMajor = new Button(grpUpperBound, SWT.RADIO);
+		btnUpperBoundMajor.setText("Major e.g. [..., 2)");
+		btnUpperBoundAny = new Button(grpUpperBound, SWT.RADIO);
+		btnUpperBoundAny.setText("Any e.g. [..., \u221e)");
+		
+		Group grpRangeTest = new Group(composite, SWT.NONE);
+		grpRangeTest.setText("Range Test");
+		new Label(grpRangeTest, SWT.NONE).setText("Sample Input Version: ");
+		txtSampleVersion = new Text(grpRangeTest, SWT.BORDER);
+		new Label(grpRangeTest, SWT.NONE).setText("Calculated Version Range: ");
+		lblCalculatedRange = new Label(grpRangeTest, SWT.NONE);
+		
+		new Label(grpRangeTest, SWT.NONE).setText("Test: ");
+		txtMatchVersion = new Text(grpRangeTest, SWT.BORDER);
+		new Label(grpRangeTest, SWT.NONE).setText("Result: ");
+		lblMatchResult = new Label(grpRangeTest, SWT.NONE);
+
+		// Initialize controls
+		loadPreferences(false);
+		updateRadioButtons();
+		
+		txtSampleVersion.setText(sampleVersion.toString());
+		updateCalculatedRange();
+
+		// Add listeners
+		SelectionListener buttonListener = new SelectionListener() {
+			public void widgetSelected(SelectionEvent e) {
+				readRadioButtons();
+				updateCalculatedRange();
+			}
+
+			public void widgetDefaultSelected(SelectionEvent e) {
+				readRadioButtons();
+				updateCalculatedRange();
+			}
+		};
+		btnLowerBoundAny.addSelectionListener(buttonListener);
+		btnLowerBoundMajor.addSelectionListener(buttonListener);
+		btnLowerBoundMinor.addSelectionListener(buttonListener);
+		btnLowerBoundMicro.addSelectionListener(buttonListener);
+		btnLowerBoundExact.addSelectionListener(buttonListener);
+
+		btnUpperBoundAny.addSelectionListener(buttonListener);
+		btnUpperBoundMajor.addSelectionListener(buttonListener);
+		btnUpperBoundMinor.addSelectionListener(buttonListener);
+		btnUpperBoundMicro.addSelectionListener(buttonListener);
+		btnUpperBoundExact.addSelectionListener(buttonListener);
+		
+		txtSampleVersion.addModifyListener(new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				try {
+					sampleVersion = new Version(txtSampleVersion.getText());
+				} catch (IllegalArgumentException x) {
+					sampleVersion = null;
+				}
+				updateCalculatedRange();
+			}
+		});
+		txtMatchVersion.addModifyListener(new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				try {
+					matchVersion = new Version(txtMatchVersion.getText());
+				} catch (IllegalArgumentException x) {
+					matchVersion = null;
+				}
+				updateCalculatedRange();
+			}
+		});
+
+		// Layout
+		GridLayout layout = new GridLayout(1, false);
+		layout.verticalSpacing = 20;
+		composite.setLayout(layout);
+
+		grpLowerBound.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+		grpLowerBound.setLayout(new GridLayout(1, false));
+
+		grpUpperBound.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+		grpUpperBound.setLayout(new GridLayout(1, false));
+		
+		grpRangeTest.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true));
+		grpRangeTest.setLayout(new GridLayout(2, false));
+		
+		txtSampleVersion.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		lblCalculatedRange.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		txtMatchVersion.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		lblMatchResult.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+
+		return composite;
+	}
+
+	private void loadPreferences(boolean useDefaults) {
+		IPreferenceStore prefs = getPreferenceStore();
+		String lowerBoundStr;
+		if(useDefaults) {
+			lowerBoundStr = prefs.getDefaultString(SigilCore.DEFAULT_VERSION_LOWER_BOUND);
+		} else {
+			lowerBoundStr = prefs.getString(SigilCore.DEFAULT_VERSION_LOWER_BOUND);
+		}
+		
+		String upperBoundStr;
+		if(useDefaults) {
+			upperBoundStr = prefs.getDefaultString(SigilCore.DEFAULT_VERSION_UPPER_BOUND);
+		} else {
+			upperBoundStr = prefs.getString(SigilCore.DEFAULT_VERSION_UPPER_BOUND);
+		}
+		
+		lowerBoundRule = VersionRangeBoundingRule.valueOf(lowerBoundStr);
+		upperBoundRule = VersionRangeBoundingRule.valueOf(upperBoundStr);
+	}
+
+	private void updateRadioButtons() {
+		switch (lowerBoundRule) {
+		case Exact:
+			btnLowerBoundExact.setSelection(true);
+			btnLowerBoundMicro.setSelection(false);
+			btnLowerBoundMinor.setSelection(false);
+			btnLowerBoundMajor.setSelection(false);
+			btnLowerBoundAny.setSelection(false);
+			break;
+		case Micro:
+			btnLowerBoundExact.setSelection(false);
+			btnLowerBoundMicro.setSelection(true);
+			btnLowerBoundMinor.setSelection(false);
+			btnLowerBoundMajor.setSelection(false);
+			btnLowerBoundAny.setSelection(false);
+			break;
+		case Minor:
+			btnLowerBoundExact.setSelection(false);
+			btnLowerBoundMicro.setSelection(false);
+			btnLowerBoundMinor.setSelection(true);
+			btnLowerBoundMajor.setSelection(false);
+			btnLowerBoundAny.setSelection(false);
+			break;
+		case Major:
+			btnLowerBoundExact.setSelection(false);
+			btnLowerBoundMicro.setSelection(false);
+			btnLowerBoundMinor.setSelection(false);
+			btnLowerBoundMajor.setSelection(true);
+			btnLowerBoundAny.setSelection(false);
+			break;
+		case Any:
+			btnLowerBoundExact.setSelection(false);
+			btnLowerBoundMicro.setSelection(false);
+			btnLowerBoundMinor.setSelection(false);
+			btnLowerBoundMajor.setSelection(false);
+			btnLowerBoundAny.setSelection(true);
+			break;
+		}
+
+		switch (upperBoundRule) {
+		case Exact:
+			btnUpperBoundExact.setSelection(true);
+			btnUpperBoundMicro.setSelection(false);
+			btnUpperBoundMinor.setSelection(false);
+			btnUpperBoundMajor.setSelection(false);
+			btnUpperBoundAny.setSelection(false);
+			break;
+		case Micro:
+			btnUpperBoundExact.setSelection(false);
+			btnUpperBoundMicro.setSelection(true);
+			btnUpperBoundMinor.setSelection(false);
+			btnUpperBoundMajor.setSelection(false);
+			btnUpperBoundAny.setSelection(false);
+			break;
+		case Minor:
+			btnUpperBoundExact.setSelection(false);
+			btnUpperBoundMicro.setSelection(false);
+			btnUpperBoundMinor.setSelection(true);
+			btnUpperBoundMajor.setSelection(false);
+			btnUpperBoundAny.setSelection(false);
+			break;
+		case Major:
+			btnUpperBoundExact.setSelection(false);
+			btnUpperBoundMicro.setSelection(false);
+			btnUpperBoundMinor.setSelection(false);
+			btnUpperBoundMajor.setSelection(true);
+			btnUpperBoundAny.setSelection(false);
+			break;
+		case Any:
+			btnUpperBoundExact.setSelection(false);
+			btnUpperBoundMicro.setSelection(false);
+			btnUpperBoundMinor.setSelection(false);
+			btnUpperBoundMajor.setSelection(false);
+			btnUpperBoundAny.setSelection(true);
+		}
+	}
+
+	private void readRadioButtons() {
+		if (btnLowerBoundExact.getSelection()) {
+			lowerBoundRule = VersionRangeBoundingRule.Exact;
+		} else if (btnLowerBoundMicro.getSelection()) {
+			lowerBoundRule = VersionRangeBoundingRule.Micro;
+		} else if (btnLowerBoundMinor.getSelection()) {
+			lowerBoundRule = VersionRangeBoundingRule.Minor;
+		} else if (btnLowerBoundMajor.getSelection()) {
+			lowerBoundRule = VersionRangeBoundingRule.Major;
+		} else if(btnLowerBoundAny.getSelection()) {
+			lowerBoundRule = VersionRangeBoundingRule.Any;
+		}
+
+		if (btnUpperBoundExact.getSelection()) {
+			upperBoundRule = VersionRangeBoundingRule.Exact;
+		} else if (btnUpperBoundMicro.getSelection()) {
+			upperBoundRule = VersionRangeBoundingRule.Micro;
+		} else if (btnUpperBoundMinor.getSelection()) {
+			upperBoundRule = VersionRangeBoundingRule.Minor;
+		} else if (btnUpperBoundMajor.getSelection()) {
+			upperBoundRule = VersionRangeBoundingRule.Major;
+		} else if(btnUpperBoundAny.getSelection()) {
+			upperBoundRule = VersionRangeBoundingRule.Any;
+		}
+	}
+
+	private void updateCalculatedRange() {
+		VersionRange range;
+		String rangeStr;
+		String matchResult;
+		
+		if(sampleVersion == null) {
+			range = null;
+			rangeStr = "";
+		} else {
+			range = VersionRange.newInstance(sampleVersion, lowerBoundRule, upperBoundRule);
+			rangeStr = range.toString();
+		}
+		lblCalculatedRange.setText(rangeStr);
+		
+		if(matchVersion == null || range == null) {
+			matchResult = "";
+		} else {
+			matchResult = range.contains(matchVersion) ? "MATCH!" : "No Match";
+		}
+		lblMatchResult.setText(matchResult);
+	}
+
+	public void init(IWorkbench workbench) {
+		this.workbench = workbench;
+	}
+
+	@Override
+	protected IPreferenceStore doGetPreferenceStore() {
+		return SigilCore.getDefault().getPreferenceStore();
+	}
+
+	@Override
+	public boolean performOk() {
+		getPreferenceStore().setValue(SigilCore.DEFAULT_VERSION_LOWER_BOUND, lowerBoundRule.name());
+		getPreferenceStore().setValue(SigilCore.DEFAULT_VERSION_UPPER_BOUND, upperBoundRule.name());
+
+		return true;
+	}
+	
+	@Override
+	protected void performDefaults() {
+		super.performDefaults();
+		loadPreferences(true);
+		updateRadioButtons();
+		updateCalculatedRange();
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/installs/OSGiInstallsPreferencePage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/installs/OSGiInstallsPreferencePage.java
new file mode 100644
index 0000000..9e205d3
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/installs/OSGiInstallsPreferencePage.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.installs;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.install.IOSGiInstallType;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.preferences.ProjectDependentPreferencesPage;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+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.Control;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+public class OSGiInstallsPreferencePage extends ProjectDependentPreferencesPage implements
+		IWorkbenchPreferencePage {
+
+	private class Install {
+		private String id;
+		private String location;
+		private IOSGiInstallType type;
+		
+		private Install(String id, String location) {
+			this.id = id;
+			this.location = location;
+		}
+
+		private IOSGiInstallType getType() {
+			if ( type == null ) {
+				type = SigilCore.getInstallManager().findInstallType(location);
+			}
+			return type;
+		}
+	}
+
+	private HashMap<String, Install> installs = new HashMap<String, Install>();
+	private CheckboxTableViewer viewer;
+	private boolean changed;
+	
+	public OSGiInstallsPreferencePage() {
+		super("OSGi Installs");
+	}
+	
+	public void init(IWorkbench workbench) {
+	}
+
+
+	@Override
+	protected Control createContents(Composite parent) {
+		Composite control = new Composite(parent, SWT.NONE);
+		
+		buildComponents(control);
+		
+		load();
+		
+		checkValid();
+		
+		return control;
+	}
+
+	@Override
+	protected boolean isDirty() {
+		return changed;
+	}
+
+	
+	private void buildComponents(Composite control) {
+		new Label(control, SWT.NONE).setText("Installs:");
+		new Label(control, SWT.NONE); // padding
+		
+		Table table = new Table(control, SWT.CHECK | SWT.SINGLE | SWT.BORDER);
+		
+		Button add = new Button(control, SWT.PUSH);
+		add.setText("Add");
+		add.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				add();
+			}			
+		});
+		
+		final Button remove = new Button(control, SWT.PUSH);
+		remove.setEnabled(false);
+		remove.setText("Remove");
+		remove.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				remove();
+			}			
+		});
+		
+		// viewers
+		viewer = new CheckboxTableViewer(table);
+		viewer.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}			
+		});
+				
+		viewer.setLabelProvider( new LabelProvider() {
+			@Override
+			public String getText(Object element) {
+				Install i = (Install) element;
+				IOSGiInstallType type = i.getType();
+				if ( type == null ) {
+					return "<invalid> [" + i.location + "]";
+				}
+				else {
+					return type.getName() + " " + type.getVersion() + " [" + i.location + "]";
+				}
+			}
+
+			@Override
+			public Image getImage(Object element) {
+				Install i = (Install) element;
+				IOSGiInstallType type = i.getType();
+				
+				if (type == null) {
+					return null;
+				} else {
+				return type.getIcon();
+				}
+			}			
+		});
+		
+		viewer.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				boolean enabled = !event.getSelection().isEmpty();
+				remove.setEnabled(enabled );
+			}
+		});
+		
+		viewer.addCheckStateListener( new ICheckStateListener () {
+			public void checkStateChanged(CheckStateChangedEvent event) {
+				if ( event.getChecked() ) {
+					changed = true;
+				}
+				viewer.setCheckedElements( new Object[] { event.getElement() } );
+			}
+		});
+		
+		viewer.setInput(installs.values());
+		
+		// layout
+		control.setLayout( new GridLayout(2, false) );
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3));
+		add.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		remove.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));		
+	}
+
+	private void load() {
+		String pref = getPreferenceStore().getString(SigilCore.OSGI_INSTALLS);
+		if ( pref != null && pref.length() > 0 ) {
+			for ( String id : pref.split(",") ) {
+				String loc = getPreferenceStore().getString( SigilCore.OSGI_INSTALL_PREFIX + id );
+				installs.put( id, new Install( id, loc ) );
+			}
+		}
+		
+		viewer.refresh();
+		
+		if ( !installs.isEmpty() ) {
+			String defId = getPreferenceStore().getString( SigilCore.OSGI_DEFAULT_INSTALL_ID );
+			if ( defId == null || defId.trim().length() == 0 ) {
+				viewer.setCheckedElements( new Object[] { installs.values().iterator().next() } );
+			}
+			else {
+				viewer.setCheckedElements( new Object[] { installs.get( defId ) } );
+			}
+		}
+	}
+	
+	protected void doSave() {
+		// zero out old configs
+		String pref = getPreferenceStore().getString(SigilCore.OSGI_INSTALLS);
+		if ( pref != null && pref.length() > 0 ) {
+			for ( String id : pref.split(",") ) {
+				getPreferenceStore().setToDefault( SigilCore.OSGI_INSTALL_PREFIX + id );
+			}
+		}
+		
+		// store new configs
+		if ( installs.isEmpty() ) {
+			getPreferenceStore().setToDefault(SigilCore.OSGI_INSTALLS);
+			getPreferenceStore().setToDefault(SigilCore.OSGI_DEFAULT_INSTALL_ID);
+		}
+		else {
+			StringBuffer buf = new StringBuffer();
+			for (Install i : installs.values() ) {
+				if ( buf.length() > 0 ) {
+					buf.append(",");
+				}
+				buf.append( i.id );
+				getPreferenceStore().setValue( SigilCore.OSGI_INSTALL_PREFIX + i.id, i.location );
+			}
+			
+			getPreferenceStore().setValue( SigilCore.OSGI_INSTALLS, buf.toString() );
+			Install def = (Install) viewer.getCheckedElements()[0];
+			getPreferenceStore().setValue(SigilCore.OSGI_DEFAULT_INSTALL_ID, def.id);
+		}
+		changed = false;
+	}
+
+	private boolean isOK() {
+		return installs.isEmpty() || viewer.getCheckedElements().length > 0;
+	}
+	
+	private void add() {
+		Shell shell = SigilUI.getActiveWorkbenchShell();
+		DirectoryDialog dialog = new DirectoryDialog(shell);
+		String dir = dialog.open();
+		if ( dir != null ) {
+			Install install = new Install( UUID.randomUUID().toString(), dir );
+			if ( install.getType() == null ) {
+				MessageDialog.openError(shell, "Error", "Invalid OSGi install directory" );
+			}
+			else {
+				boolean empty = installs.isEmpty();
+				
+				installs.put( install.id, install );
+				viewer.refresh();
+				
+				if ( empty ) {
+					viewer.setCheckedElements( new Object[] { install } );
+				}
+				
+				checkValid();
+				changed = true;
+			}
+		}
+	}
+
+	private void checkValid() {
+		if ( isOK() ) {
+			setErrorMessage(null);
+			setValid(true);
+		}
+		else {
+			setErrorMessage("Missing default OSGi install");
+			setValid(false);
+		}
+	}
+
+	private void remove() {
+		IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
+		Install i = (Install) sel.getFirstElement();
+		boolean def = viewer.getChecked(i);
+		installs.remove(i.id);
+		viewer.refresh();
+		if ( def && installs.size() > 0 ) {
+			viewer.setCheckedElements( new Object[] { installs.values().iterator().next() } );
+		}
+		checkValid();
+		changed = true;
+	}
+
+	@Override
+	protected IPreferenceStore doGetPreferenceStore() {
+		return SigilCore.getDefault().getPreferenceStore();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/project/ProjectPropertyPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/project/ProjectPropertyPage.java
new file mode 100644
index 0000000..cbfa165
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/project/ProjectPropertyPage.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.project;
+
+import java.util.concurrent.Callable;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.util.ProjectUtils;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.jface.viewers.ComboViewer;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+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.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.IWorkbenchPropertyPage;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.osgi.service.prefs.BackingStoreException;
+
+public class ProjectPropertyPage extends PropertyPage implements
+		IWorkbenchPropertyPage {
+
+	private boolean projectSpecific;
+	private ComboViewer setView;
+	private Composite settings;
+	private Button projectSpecificBtn;
+
+	@Override
+	protected Control createContents(Composite parent) {
+		final Composite control = new Composite(parent, SWT.NONE);
+		
+		projectSpecificBtn = new Button(control, SWT.CHECK);
+		projectSpecificBtn.setText( "Enable project specific settings");
+		projectSpecificBtn.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				setProjectSpecific(!projectSpecific);
+			}
+		});
+		
+		Label link = new Label( control, SWT.UNDERLINE_SINGLE );
+		link.addMouseListener( new MouseAdapter() {
+			@Override
+			public void mouseDown(MouseEvent e) {
+				PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(null, SigilCore.REPOSITORIES_PREFERENCES_ID, null, null);
+				dialog.open();
+			}			
+		});
+		
+		link.setText("Configure workspace settings" );
+		
+		settings = new Composite( control, SWT.BORDER );
+		settings.setLayout( new GridLayout(1,false));
+		createSettings( settings );
+		
+		setFonts(control);
+		
+		// layout
+		control.setLayout( new GridLayout(2, false));
+		projectSpecificBtn.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, false ) );
+		settings.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1 ) );
+
+		// load settings
+		String currentSet = getCurrentSet();
+		
+		if ( currentSet == null ) {
+			setProjectSpecific(false);
+		}
+		else {
+			setView.setSelection( new StructuredSelection(currentSet) );
+			setProjectSpecific(true);
+		}
+		
+		return control;
+	}
+
+	private void setFonts(Composite control) {
+		Composite p = control.getParent();
+		for ( Control c : control.getChildren() ) {
+			c.setFont( p.getFont() );
+			if ( c instanceof Composite ) {
+				setFonts( (Composite) c );
+			}
+		}
+	}
+
+	private void setProjectSpecific(boolean projectSpecific) {
+		if ( this.projectSpecific != projectSpecific ) {
+			this.projectSpecific = projectSpecific;
+			settings.setEnabled(projectSpecific);
+			for ( Control c : settings.getChildren() ) {
+				c.setEnabled( projectSpecific );
+			}
+			projectSpecificBtn.setSelection(projectSpecific);
+		}
+	}
+
+	private void createSettings(Composite parent) {
+		Composite control = new Composite(parent, SWT.NONE);
+		
+		new Label( control, SWT.NONE).setText( "Repository Set:" );
+		Combo combo = new Combo(control, SWT.SINGLE);
+		
+		setView = new ComboViewer(combo);
+		setView.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		});
+		
+		setView.setInput( SigilCore.getRepositoryConfiguration().loadRepositorySets().keySet() );
+		
+		// layout
+		control.setLayout( new GridLayout(2, false ) );
+	}
+
+	private String getCurrentSet() {
+		try {
+			IProject p = (IProject) getElement().getAdapter(IProject.class);
+			ISigilProjectModel model = SigilCore.create(p);
+			return model.getPreferences().get( SigilCore.REPOSITORY_SET, null );
+		} catch (CoreException e) {
+			SigilCore.error("Failed to read repository set", e );
+			return null;
+		}
+	}
+
+	@Override
+	public boolean okToLeave() {
+		if ( projectSpecific ) {
+			if ( setView.getSelection().isEmpty() ) {
+				setErrorMessage("Must select a repository set");
+				return false;
+			}
+		}
+		setErrorMessage(null);
+		return true;
+	}
+
+	@Override
+	public boolean performOk() {
+		return ProjectUtils.runTaskWithRebuildCheck( new Callable<Boolean>() {
+			public Boolean call() throws CoreException, BackingStoreException {
+				String set = null;
+				if ( projectSpecific ) {
+					set = (String) ((IStructuredSelection) setView.getSelection()).getFirstElement();
+				}
+
+				IProject p = (IProject) getElement().getAdapter(IProject.class);
+				ISigilProjectModel model = SigilCore.create(p);
+				model.getPreferences().put( SigilCore.REPOSITORY_SET, set );
+				model.getPreferences().flush();
+				return true;
+			}
+
+		}, getShell());
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/NewRepositoryWizard.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/NewRepositoryWizard.java
new file mode 100644
index 0000000..bf410ae
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/NewRepositoryWizard.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.eclipse.jface.wizard.Wizard;
+
+public class NewRepositoryWizard extends Wizard {
+
+	private IRepositoryModel repository;
+	
+	private RepositoryTypeSelectionPage page = new RepositoryTypeSelectionPage();
+	
+	public void addPages() {
+		addPage( page );
+	}
+	
+	@Override
+	public boolean performFinish() {
+		repository = page.getRepository();
+		return true;
+	}
+	@Override
+	public boolean needsPreviousAndNextButtons() {
+		return true;
+	}
+
+	public IRepositoryModel getRepository() {
+		return repository;
+	}
+
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoriesPreferencePage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoriesPreferencePage.java
new file mode 100644
index 0000000..9503a98
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoriesPreferencePage.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;

+

+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.repository.IRepositoryConfiguration;
+import org.cauldron.sigil.model.repository.IRepositorySet;
+import org.cauldron.sigil.model.repository.RepositorySet;
+import org.cauldron.sigil.ui.preferences.ProjectDependentPreferencesPage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.TabFolder;
+import org.eclipse.swt.widgets.TabItem;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+

+public class RepositoriesPreferencePage extends ProjectDependentPreferencesPage implements

+		IWorkbenchPreferencePage {

+
+	private boolean changed;
+	private RepositoriesView viewPage;
+	private RepositorySetsView setPage;
+
+	public RepositoriesPreferencePage() {
+		super( "Repository Preferences" );
+	}
+	@Override

+	protected Control createContents(Composite parent) {

+		Control control = initContents( parent );
+		return control;

+	}

+

+	@Override

+	protected IPreferenceStore doGetPreferenceStore() {

+		return SigilCore.getDefault().getPreferenceStore();

+	}

+
+	protected void changed() {
+		changed = true;
+		updateApplyButton();
+	}

+	
+	private Control initContents(Composite parent) {
+		viewPage = new RepositoriesView(this);
+		setPage = new RepositorySetsView(this);
+		
+		Composite control = new Composite(parent, SWT.NONE);
+		
+		TabFolder folder = new TabFolder(control, SWT.TOP);
+		
+		TabItem view = new TabItem(folder, SWT.NONE);
+		view.setText("Repositories");
+		view.setControl(viewPage.createContents(folder) );
+		
+		TabItem sets = new TabItem(folder, SWT.NONE);
+		sets.setText("Sets");
+		sets.setControl(setPage.createContents(folder) );
+
+		control.setLayout(new GridLayout(1, true));
+		folder.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, true));
+		
+		return control;
+	}
+
+	public void init(IWorkbench workbench) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	protected void doSave() {
+		try {
+			IRepositoryConfiguration config = SigilCore.getRepositoryConfiguration();
+			config.saveRepositories(viewPage.getRepositories());
+			config.saveRepositorySets(setPage.getSets());
+			IRepositorySet defaultSet = new RepositorySet(setPage.getDefaultRepositories());
+			config.setDefaultRepositorySet(defaultSet);
+			
+			setErrorMessage(null);
+			getApplyButton().setEnabled(false);
+			changed = false;
+		} catch (CoreException e) {
+			setErrorMessage("Failed to save repositories:" + e.getStatus().getMessage());
+			SigilCore.error("Failed to save repositories", e);
+		}
+	}
+
+	@Override
+	protected boolean isDirty() {
+		return changed;
+	}	

+}

diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoriesView.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoriesView.java
new file mode 100644
index 0000000..f9394e7
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoriesView.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.actions.RefreshRepositoryAction;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.jface.wizard.WizardDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+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.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+
+public class RepositoriesView {
+	private final RepositoriesPreferencePage page;
+
+	private List<IRepositoryModel> repositories;
+	
+	private TableViewer repositoryView;
+
+	public RepositoriesView(RepositoriesPreferencePage page) {
+		this.page = page;
+	}
+
+	public Control createContents(Composite parent) {
+		// Create Controls
+		Composite composite = new Composite(parent, SWT.NONE);
+		
+		Table table = new Table(composite, SWT.MULTI | SWT.BORDER);
+
+		// Table Viewer Setup
+		repositoryView = new TableViewer(table);
+		repositoryView.setLabelProvider(new LabelProvider() {
+			@Override
+			public String getText(Object element) {
+				IRepositoryModel rep = (IRepositoryModel) element;
+				return rep.getName();
+			}
+
+			@Override
+			public Image getImage(Object element) {
+				IRepositoryModel rep = (IRepositoryModel) element;				
+				return rep.getType().getIcon();
+			}
+		});
+
+		repositoryView.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		} );
+		
+		// Layout
+		composite.setLayout(new GridLayout(2, false));
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 6));
+		
+		createButtons(composite, repositoryView);	
+		
+		repositories = SigilCore.getRepositoryConfiguration().loadRepositories();
+		repositoryView.setInput(repositories);		
+		
+		return composite;
+	}
+	
+	private void createButtons(final Composite composite, final TableViewer repositoryView) {
+		final Button add = new Button(composite, SWT.PUSH);
+		add.setText("Add...");
+		add.setEnabled(true);
+		
+		final Button edit = new Button(composite, SWT.PUSH);
+		edit.setText("Edit...");
+		edit.setEnabled(false);
+				
+		final Button remove = new Button(composite, SWT.PUSH);
+		remove.setText("Remove");
+		remove.setEnabled(false);
+		
+		final Button refresh = new Button(composite, SWT.PUSH);
+		refresh.setText("Refresh");
+		refresh.setEnabled( false );
+		
+		// Listeners
+		add.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				add(composite);
+			}			
+		});
+		
+		edit.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				IStructuredSelection sel = (IStructuredSelection) repositoryView.getSelection();
+				edit(composite, sel);
+			}			
+		});
+		
+		remove.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				IStructuredSelection sel = (IStructuredSelection) repositoryView.getSelection();
+				remove(sel);
+			}			
+		});
+		
+		refresh.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				IStructuredSelection sel = (IStructuredSelection) repositoryView.getSelection();
+				refresh(composite, sel);
+			}			
+		});
+		
+		repositoryView.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				boolean selected = !event.getSelection().isEmpty();
+				if ( selected ) {
+					refresh.setEnabled(true);
+					
+					IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+					
+					checkEditEnabled(edit, sel);
+					checkRemoveEnabled(remove, sel);
+				}
+				else {
+					refresh.setEnabled(false);
+					edit.setEnabled(false);
+					remove.setEnabled(false);
+				}
+			}
+		});
+		
+		add.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		edit.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		remove.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+	}
+	
+	@SuppressWarnings("unchecked")
+	private void checkRemoveEnabled(Button button, IStructuredSelection sel) {
+		boolean alldynamic = true;
+		for ( Iterator i = sel.iterator(); i.hasNext(); ) {
+			IRepositoryModel model = (IRepositoryModel) i.next();
+			if ( !model.getType().isDynamic() ) {
+				alldynamic = false;
+				break;
+			}
+		}
+		button.setEnabled(alldynamic);
+	}
+
+	private void checkEditEnabled(Button edit, IStructuredSelection sel) {
+		if ( sel.size() == 1 ) {
+			IRepositoryModel element = (IRepositoryModel) sel.getFirstElement();
+			if ( WizardHelper.hasWizard( element.getType()) ) {
+				edit.setEnabled(true);						
+			}
+			else {
+				edit.setEnabled(false);
+			}
+		}
+		else {
+			edit.setEnabled(false);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	protected void refresh(Control parent, IStructuredSelection sel) {
+		ArrayList<IRepositoryModel> models = new ArrayList<IRepositoryModel>(sel.size());
+		
+		for ( Iterator i = sel.iterator(); i.hasNext(); ) {
+			IRepositoryModel model = (IRepositoryModel) i.next();
+			models.add( model );
+		}
+		
+		new RefreshRepositoryAction(models.toArray(new IRepositoryModel[models.size()])).run();
+	}
+
+	private void add(Control parent) {
+		NewRepositoryWizard wizard = new NewRepositoryWizard();
+		WizardDialog dialog = new WizardDialog(getShell(parent), wizard);
+		if ( dialog.open() == Window.OK ) {
+			repositories.add(wizard.getRepository());
+			updated();
+		}
+	}
+
+	private void edit(Control parent, IStructuredSelection sel) {
+		IRepositoryModel model = (IRepositoryModel) sel.getFirstElement();
+		try {
+			RepositoryWizard wizard = WizardHelper.loadWizard(model.getType());
+			wizard.init(model);
+			WizardDialog dialog = new WizardDialog(getShell(parent), wizard);
+			if ( dialog.open() == Window.OK ) {
+				updated();
+			}
+		}
+		catch (CoreException e) {
+			SigilCore.error( "Failed to load wizard", e);
+			MessageDialog.openError(getShell(parent), "Error", "Failed to load wizard:" + e.getStatus().getMessage() );
+		}
+	}
+
+	private Shell getShell(Control parent) {
+		return parent.getShell();
+	}
+
+	@SuppressWarnings("unchecked")
+	private void remove(IStructuredSelection sel) {
+		boolean change = false;
+		for ( Iterator i = sel.iterator(); i.hasNext(); ) {
+			change = repositories.remove(i.next());
+		}
+		
+		if ( change ) {
+			updated();
+		}
+	}
+
+	private void updated() {
+		repositoryView.refresh();
+		page.changed();
+	}
+
+	public List<IRepositoryModel> getRepositories() {
+		return repositories;
+	}	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositorySetDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositorySetDialog.java
new file mode 100644
index 0000000..8136f55
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositorySetDialog.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.ui.util.DefaultLabelProvider;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+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.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class RepositorySetDialog extends TitleAreaDialog {
+	
+	private CheckboxTableViewer viewer;
+	private Text nameTxt;
+	private Button upBtn;
+	private Button downBtn;
+	private final String setName;
+	private List<IRepositoryModel> repositories;
+	private final boolean nameEditable;
+	private final Set<String> set;
+	
+	private String newName;
+	
+	public RepositorySetDialog(Shell shell, Set<String> set) {
+		this(shell, null, true, set);
+	}
+	
+	public RepositorySetDialog(Shell parent, RepositoryViewData data, boolean nameEditable, Set<String> set) {
+		super(parent);
+		this.set = set;
+		this.setName = data == null ? "" : data.getName();
+		this.repositories = data == null ? new ArrayList<IRepositoryModel>() : new ArrayList<IRepositoryModel>(Arrays.asList(data.getRepositories()));
+		this.nameEditable = nameEditable;
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		Composite area = (Composite) super.createDialogArea(parent);
+		createControl(area);
+		return area;
+	}
+
+	public void createControl(Composite parent) {
+		// controls
+		Composite body = new Composite(parent, SWT.NONE);
+		body.setLayoutData( new GridData(GridData.FILL_BOTH) );
+		
+		if ( nameEditable ) {
+			new Label( body, SWT.NONE ).setText( "Name" );
+			
+			nameTxt = new Text( body, SWT.BORDER );
+			
+			nameTxt.setText(setName);
+			
+			nameTxt.addKeyListener( new KeyAdapter() {
+				@Override
+				public void keyReleased(KeyEvent e) {
+					checkComplete();
+				}
+			});
+		}
+		
+		Composite table = new Composite(body, SWT.NONE);
+		table.setLayout( new GridLayout(2, false ) );
+		createTable( table );
+		
+		// layout
+		body.setLayout( new GridLayout( 2, false ) );
+		if ( nameEditable ) {
+			nameTxt.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, false) );
+		}
+		table.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1) );
+	}
+	
+	public RepositoryViewData getData() {
+		String name = nameEditable ? newName : setName;
+		IRepositoryModel[] reps = repositories.toArray( new IRepositoryModel[repositories.size()]);
+		return new RepositoryViewData( name, reps );
+	}
+
+	private void checkComplete() {
+		if ( nameEditable ) {
+			String name = nameTxt.getText();
+			if ( !name.equals( setName ) && set.contains( name ) ) {
+				setErrorMessage("Set " + name + " already exists" );
+				Button b = getButton(IDialogConstants.OK_ID);
+				b.setEnabled(false);
+			}
+		}		
+		setErrorMessage(null);
+		Button b = getButton(IDialogConstants.OK_ID);
+		b.setEnabled(true);
+	}
+
+
+	@Override
+	protected void okPressed() {
+		if ( nameEditable ) {
+			newName = nameTxt.getText();
+		}
+		repositories = getRepositories();
+		super.okPressed();
+	}
+
+	private void createTable(Composite body) {
+		createViewer(body);
+		
+		Composite btns = new Composite(body, SWT.NONE);
+		btns.setLayout( new GridLayout( 1, true ) );
+		
+		createButtons(btns);
+		
+		// layout
+		viewer.getTable().setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
+		btns.setLayoutData( new GridData( SWT.RIGHT, SWT.TOP, false, false ) );
+	}
+
+	private void createButtons(Composite parent) {
+		upBtn = new Button(parent, SWT.PUSH);
+		upBtn.setText( "Up" );
+		upBtn.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				up();
+			}
+		});
+		
+		downBtn = new Button(parent, SWT.PUSH);
+		downBtn.setText( "Down" );
+		downBtn.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				down();
+			}
+		});
+		
+		setUpDownEnabled(false);
+	}
+
+	private void up() {
+		IRepositoryModel model = (IRepositoryModel) ((StructuredSelection) viewer.getSelection()).getFirstElement();
+		int i = repositories.indexOf(model);
+		if ( i > 0 ) {
+			repositories.remove( i );
+			repositories.add( i - 1, model );
+			viewer.refresh();
+		}
+	}
+
+	private void down() {
+		IRepositoryModel model = (IRepositoryModel) ((StructuredSelection) viewer.getSelection()).getFirstElement();
+		int i = repositories.indexOf(model);
+		if ( i < repositories.size() - 1 ) {
+			repositories.remove( i );
+			repositories.add( i + 1, model );
+			viewer.refresh();
+		}
+	}
+
+	private void createViewer(Composite parent) {
+		viewer = CheckboxTableViewer.newCheckList(parent, SWT.BORDER);
+		
+		viewer.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				setUpDownEnabled( !viewer.getSelection().isEmpty() ); 
+			}
+		});
+		
+		viewer.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		});
+		
+		viewer.setLabelProvider( new DefaultLabelProvider() {
+			public Image getImage(Object element) {
+				return null;
+			}
+
+			public String getText(Object element) {
+				IRepositoryModel m = (IRepositoryModel) element;
+				return m.getName();
+			}			
+		});
+		
+		viewer.setInput( repositories );
+		
+		for ( IRepositoryModel m : repositories ) {
+			viewer.setChecked(m, true);
+		}
+		
+		List<IRepositoryModel> allRepositories = SigilCore.getRepositoryConfiguration().loadRepositories();
+		
+		for ( IRepositoryModel m : allRepositories ) {
+			if ( !repositories.contains(m) ) {
+				repositories.add(m);
+			}
+		}
+		
+		viewer.refresh();		
+	}
+
+	private void setUpDownEnabled(boolean enabled) {
+		upBtn.setEnabled(enabled);
+		downBtn.setEnabled(enabled);
+	}
+
+	private List<IRepositoryModel> getRepositories() {
+		ArrayList<IRepositoryModel> reps = new ArrayList<IRepositoryModel>();
+		
+		for ( IRepositoryModel m : repositories ) {
+			if ( viewer.getChecked(m) ) {
+				reps.add( m );
+			}
+		}
+		
+		return reps;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositorySetsView.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositorySetsView.java
new file mode 100644
index 0000000..0470675
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositorySetsView.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.model.repository.IRepositorySet;
+import org.cauldron.sigil.model.repository.RepositorySet;
+import org.cauldron.sigil.ui.util.DefaultLabelProvider;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+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.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+
+public class RepositorySetsView {
+	private static final String DEFAULT = "default";
+
+	private final RepositoriesPreferencePage page;
+
+	private ArrayList<RepositoryViewData> sets = new ArrayList<RepositoryViewData>();
+	
+	private TableViewer setView;
+
+	private RepositoryViewData defaultSet;
+	
+	public RepositorySetsView(RepositoriesPreferencePage page) {
+		this.page = page;
+	}
+
+	public Control createContents(Composite parent) {
+		// Create Controls
+		Composite composite = new Composite(parent, SWT.NONE);
+		
+		Table table = new Table(composite, SWT.SINGLE | SWT.BORDER);
+
+		// Table Viewer Setup
+		setView = new TableViewer(table);
+
+		setView.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		} );
+		
+		defaultSet = new RepositoryViewData(DEFAULT, SigilCore.getRepositoryConfiguration().getDefaultRepositorySet().getRepositories());
+		
+		sets.add( defaultSet );
+		
+		for( Map.Entry<String, IRepositorySet> e : SigilCore.getRepositoryConfiguration().loadRepositorySets().entrySet() ) {
+			IRepositorySet s = e.getValue();
+			sets.add( new RepositoryViewData( e.getKey(), s.getRepositories() ) );
+		}
+		
+		setView.setLabelProvider( new DefaultLabelProvider() {
+			public Image getImage(Object element) {
+				return null;
+			}
+
+			public String getText(Object element) {
+				RepositoryViewData data = (RepositoryViewData) element;
+				return data.getName();
+			}
+		});
+		
+		setView.setInput(sets);
+		
+		// Layout
+		composite.setLayout(new GridLayout(2, false));
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 6));
+		
+		createButtons(composite);	
+				
+		return composite;
+	}
+	
+	private void createButtons(final Composite composite) {
+		final Button add = new Button(composite, SWT.PUSH);
+		add.setText("Add...");
+		add.setEnabled(true);
+		
+		final Button edit = new Button(composite, SWT.PUSH);
+		edit.setText("Edit...");
+		edit.setEnabled(false);
+				
+		final Button remove = new Button(composite, SWT.PUSH);
+		remove.setText("Remove");
+		remove.setEnabled(false);
+		// Listeners
+		add.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				add(composite);
+			}			
+		});
+		
+		edit.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				IStructuredSelection sel = (IStructuredSelection) setView.getSelection();
+				edit(composite, sel);
+			}			
+		});
+		
+		remove.addSelectionListener( new SelectionAdapter() {
+			public void widgetSelected(SelectionEvent e) {
+				IStructuredSelection sel = (IStructuredSelection) setView.getSelection();
+				remove(sel);
+			}			
+		});
+
+		setView.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				boolean enabled = !event.getSelection().isEmpty();
+				if ( enabled ) {
+					RepositoryViewData element = (RepositoryViewData) ((IStructuredSelection) event.getSelection()).getFirstElement();
+					edit.setEnabled(true);
+					remove.setEnabled( element != defaultSet );
+				}
+				else {
+					edit.setEnabled(false);
+					remove.setEnabled(false);
+				}
+			}
+		});
+		
+		add.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		edit.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+		remove.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));		
+	}
+	
+	private void add(Control parent) {
+		RepositorySetDialog wizard = new RepositorySetDialog(getShell(parent), getNames());
+		if ( wizard.open() == Window.OK ) {
+			sets.add( wizard.getData() );
+			updated();
+		}
+	}
+
+	private void edit(Control parent, IStructuredSelection sel) {
+		RepositoryViewData data = (RepositoryViewData) sel.getFirstElement();
+		RepositorySetDialog wizard = new RepositorySetDialog(getShell(parent), data, data != defaultSet, getNames());
+		if ( wizard.open() == Window.OK ) {
+			if ( data != defaultSet ) {
+				data.setName( wizard.getData().getName() );
+			}
+			data.setRepositories( wizard.getData().getRepositories() );
+			updated();
+		}
+	}
+
+	private Set<String> getNames() {
+		HashSet<String> names = new HashSet<String>();
+		
+		for ( RepositoryViewData view : sets ) {
+			if ( view != defaultSet ) {
+				names.add( view.getName() );
+			}
+		}
+		
+		return names;
+	}
+
+	private Shell getShell(Control parent) {
+		return parent.getShell();
+	}
+
+	private void remove(IStructuredSelection sel) {
+		if ( sets.remove(sel.getFirstElement())) {
+			updated();
+		}
+	}
+
+	private void updated() {
+		setView.refresh();
+		page.changed();
+	}
+
+	public Map<String, IRepositorySet> getSets() {
+		HashMap<String, IRepositorySet> ret = new HashMap<String, IRepositorySet>();
+		
+		for ( RepositoryViewData data : sets ) {
+			if ( data != defaultSet ) {
+				IRepositorySet set = new RepositorySet(data.getRepositories());
+				ret.put( data.getName(), set );
+			}
+		}
+		
+		return ret;
+	}
+
+	public IRepositoryModel[] getDefaultRepositories() {
+		return defaultSet.getRepositories();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryTypeSelectionPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryTypeSelectionPage.java
new file mode 100644
index 0000000..3dfe8b8
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryTypeSelectionPage.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.model.repository.IRepositoryType;
+import org.cauldron.sigil.ui.util.DefaultTableProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.WizardSelectionPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+
+public class RepositoryTypeSelectionPage extends WizardSelectionPage implements IWizardPage {
+
+	private static final String TITLE = "Select Repository";
+	
+	private TableViewer repositoryView;
+	private IRepositoryModel repositoryElement;
+	
+	public RepositoryTypeSelectionPage() {
+		super(TITLE);
+		setTitle(TITLE);
+	}
+
+	public void createControl(Composite parent) {
+		Composite control = new Composite(parent, SWT.NONE);
+		
+		// components
+		new Label(control, SWT.NONE).setText("Repositories" );
+		Table table = new Table(control, SWT.SINGLE | SWT.BORDER);
+		
+		// layout
+		control.setLayout( new GridLayout(1, true) );
+		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+		
+		// view
+		repositoryView = new TableViewer(table);
+		repositoryView.setLabelProvider( new LabelProvider() {
+			@Override
+			public String getText(Object element) {
+				IRepositoryType rep = (IRepositoryType) element;
+				return rep.getType();
+			}
+
+			@Override
+			public Image getImage(Object element) {
+				IRepositoryType rep = (IRepositoryType) element;
+				return rep.getIcon();
+			}			
+		});
+		
+		repositoryView.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		} );
+		
+		repositoryView.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				if ( !event.getSelection().isEmpty() ) {
+					IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+					IRepositoryType type = (IRepositoryType) sel.getFirstElement();
+					repositoryElement = SigilCore.getRepositoryConfiguration().newRepositoryElement(type);
+					selectWizardNode(new RepositoryWizardNode(repositoryElement));
+				}
+			}
+		});
+		
+		ArrayList<IRepositoryType> descriptors = new ArrayList<IRepositoryType>(SigilCore.getRepositoryConfiguration().loadRepositoryTypes());
+		
+		for ( Iterator<IRepositoryType> i = descriptors.iterator(); i.hasNext(); ) {
+			if ( !i.next().isDynamic() ) {
+				i.remove();
+			}
+		}
+		
+		repositoryView.setInput( descriptors );
+		
+		setControl(control);
+	}
+	
+	public void selectWizardNode(RepositoryWizardNode node) {
+		setSelectedNode(node);
+	}
+	
+	public RepositoryWizardNode getSelectedWizardNode() {
+		return (RepositoryWizardNode) getSelectedNode();
+	}
+
+	public IRepositoryModel getRepository() {		
+		return repositoryElement;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryViewData.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryViewData.java
new file mode 100644
index 0000000..85124c7
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryViewData.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+
+class RepositoryViewData {
+	private String name;
+	private IRepositoryModel[] reps;
+
+	RepositoryViewData(String name, IRepositoryModel[] reps) {
+		this.name = name;
+		this.reps = reps;
+	}
+
+	String getName() {
+		return name;
+	}
+
+	IRepositoryModel[] getRepositories() {
+		return reps;
+	}
+	
+	void setName(String name) {
+		this.name = name;
+	}
+
+	void setRepositories(IRepositoryModel[] reps) {
+		this.reps = reps;
+	}
+
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryWizardNode.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryWizardNode.java
new file mode 100644
index 0000000..5ea458a
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/RepositoryWizardNode.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.jface.wizard.IWizardNode;
+import org.eclipse.swt.graphics.Point;
+
+public class RepositoryWizardNode implements IWizardNode {
+
+	private IRepositoryModel repository;
+	
+	private RepositoryWizard wizard;
+	
+	public RepositoryWizardNode(IRepositoryModel repository) {
+		this.repository = repository;
+	}
+
+	public void dispose() {
+		if ( wizard != null ) {
+			wizard.dispose();
+			wizard = null;
+		}
+	}
+
+	public Point getExtent() {
+		return new Point(-1, -1);
+	}
+
+	public IWizard getWizard() {
+		if ( wizard == null ) {
+			try {
+				wizard = WizardHelper.loadWizard(repository.getType());
+				wizard.init( repository );
+			} catch (CoreException e) {
+				SigilCore.error( "Failed to create wizard for " + repository.getType(), e);
+			}
+		}
+		return wizard;
+	}
+	
+	public IRepositoryModel getRepository() {
+		return repository;
+	}
+
+	public boolean isContentCreated() {
+		return wizard != null;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/WizardHelper.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/WizardHelper.java
new file mode 100644
index 0000000..101b57e
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/preferences/repository/WizardHelper.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.preferences.repository;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.repository.IRepositoryType;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.wizard.repository.RepositoryWizard;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+
+public class WizardHelper {
+	public static RepositoryWizard loadWizard(IRepositoryType type) throws CoreException {
+		IConfigurationElement e = findWizardConfig(type.getId());
+		
+		if ( e == null ) {
+			throw SigilCore.newCoreException("No wizard registered for repository " + type, null);
+		}
+		
+		return (RepositoryWizard) e.createExecutableExtension("class");
+	}
+	
+	public static boolean hasWizard(IRepositoryType type) {
+		return findWizardConfig(type.getId()) != null;
+	}	
+		
+	private static IConfigurationElement findWizardConfig(String id) {
+		IExtensionRegistry registry = Platform.getExtensionRegistry();		
+		IExtensionPoint p = registry.getExtensionPoint(SigilUI.REPOSITORY_WIZARD_EXTENSION_POINT_ID);
+		
+		for ( IExtension e : p.getExtensions() ) {
+			for ( IConfigurationElement c : e.getConfigurationElements() ) {
+				if ( id.equals( c.getAttribute("repository") ) ) {
+					return c;
+				}
+			}
+		}
+		
+		return null;
+	}
+	
+	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportPackageProposal.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportPackageProposal.java
new file mode 100644
index 0000000..163154d
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportPackageProposal.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.quickfix;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.common.VersionRangeBoundingRule;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+import org.osgi.framework.Version;
+
+public class ImportPackageProposal implements IJavaCompletionProposal {
+
+	private IPackageExport e;
+	private ISigilProjectModel n;
+	
+	public ImportPackageProposal(IPackageExport e, ISigilProjectModel n) {
+		this.e = e;
+		this.n = n;
+	}
+
+	public int getRelevance() {
+		return 100;
+	}
+
+	public void apply(IDocument document) {
+		try {
+			
+			final IPackageImport i = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+			i.setPackageName(e.getPackageName());
+			IPreferenceStore store = SigilCore.getDefault().getPreferenceStore();
+			VersionRangeBoundingRule lowerBoundRule = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_LOWER_BOUND));
+			VersionRangeBoundingRule upperBoundRule = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_UPPER_BOUND));
+			
+			Version version = e.getVersion();
+			VersionRange selectedVersions = VersionRange.newInstance(version, lowerBoundRule, upperBoundRule);
+			i.setVersions( selectedVersions );
+			
+			WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+				@Override
+				protected void execute(IProgressMonitor monitor)
+						throws CoreException {
+					n.getBundle().getBundleInfo().addImport( i );
+					n.save(null);
+				}
+			};
+			
+			SigilUI.runWorkspaceOperation(op, null);
+		} catch (ModelElementFactoryException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	public String getAdditionalProposalInfo() {
+		return null;
+	}
+
+	public IContextInformation getContextInformation() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public String getDisplayString() {
+		return "Import package " + e.getPackageName() + " version " + e.getVersion() + " to bundle";
+	}
+
+	public Image getImage() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public Point getSelection(IDocument document) {
+		return null;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportQuickFixProcessor.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportQuickFixProcessor.java
new file mode 100644
index 0000000..ac3f9ba
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportQuickFixProcessor.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.quickfix;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.util.JavaHelper;
+import org.cauldron.sigil.search.ISearchResult;
+import org.cauldron.sigil.search.SigilSearch;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ArrayType;
+import org.eclipse.jdt.core.dom.ClassInstanceCreation;
+import org.eclipse.jdt.core.dom.ImportDeclaration;
+import org.eclipse.jdt.core.dom.MethodInvocation;
+import org.eclipse.jdt.core.dom.Name;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.SimpleType;
+import org.eclipse.jdt.core.dom.Type;
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.ui.text.java.IInvocationContext;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jdt.ui.text.java.IProblemLocation;
+import org.eclipse.jdt.ui.text.java.IQuickFixProcessor;
+
+@SuppressWarnings("restriction")
+public class ImportQuickFixProcessor implements IQuickFixProcessor {
+
+	private static final Object SYNC_FLAG = new Object();
+	
+	public boolean hasCorrections(ICompilationUnit unit, int problemId) {
+		switch ( problemId ) {
+		case IProblem.ImportNotFound:
+		case IProblem.ForbiddenReference:
+		case IProblem.NotVisibleType:
+		case IProblem.UndefinedType:
+			return true;
+		default:
+			return false;
+		}
+	}
+
+	public IJavaCompletionProposal[] getCorrections(IInvocationContext context,
+			IProblemLocation[] locations) throws CoreException {
+		try {
+			HashMap<Object, IJavaCompletionProposal> results = new HashMap<Object, IJavaCompletionProposal>();
+			
+			ISigilProjectModel project = findProject(context);
+			
+			if ( project != null ) {
+				for ( int i = 0; i < locations.length; i++ ) {
+					switch ( locations[i].getProblemId() ) {
+					case IProblem.ForbiddenReference:
+						handleImportNotFound(project, context, locations[i], results);
+						break;
+					case IProblem.ImportNotFound:
+						handleImportNotFound(project, context, locations[i], results);
+						break;
+					case IProblem.IsClassPathCorrect:
+						handleIsClassPathCorrect(project, context, locations[i], results);
+						break;
+					case IProblem.UndefinedType:
+						handleUndefinedType(project, context, locations[i], results);
+						break;
+					case IProblem.UndefinedName:
+						handleUndefinedName(project, context, locations[i], results);
+						break;
+					}
+				}
+			}		
+			
+			return (IJavaCompletionProposal[])results.values().toArray(new IJavaCompletionProposal[results.size()]);
+		}
+		catch ( RuntimeException e ) {
+			e.printStackTrace();
+			throw e;
+		}
+	}
+
+	private void handleUndefinedName(ISigilProjectModel project,
+			IInvocationContext context, IProblemLocation problem,
+			HashMap<Object, IJavaCompletionProposal> results) {
+		Name node = findNode(context, problem);
+		
+		if ( node == null ) {
+			return;
+		}
+		addSearchResults(node, project, results);
+	}
+
+	private void handleIsClassPathCorrect(ISigilProjectModel project, final IInvocationContext context,
+			IProblemLocation problemLocation,
+			final HashMap<Object, IJavaCompletionProposal> results) {
+		for ( final String type : problemLocation.getProblemArguments() ) {
+			final String iPackage = type.substring(0, type.lastIndexOf("."));
+			
+			for ( IPackageExport pe : JavaHelper.findExportsForPackage(project, iPackage) ) { 
+				results.put( type, new ImportPackageProposal( pe, project ) );
+			}
+		}
+		
+		if ( !results.containsKey(SYNC_FLAG) ) {
+			//results.put( SYNC_FLAG, null);
+		}
+	}
+
+	private void handleUndefinedType(ISigilProjectModel project, IInvocationContext context,
+			IProblemLocation problem,
+			HashMap<Object, IJavaCompletionProposal> results) throws CoreException {
+		Name node = findNode(context, problem);
+		
+		if ( node == null ) {
+			return;
+		}
+		addSearchResults(node, project, results);
+	}
+
+	private void handleImportNotFound(ISigilProjectModel project, final IInvocationContext context, IProblemLocation location, final HashMap<Object, IJavaCompletionProposal> results) throws CoreException {
+		ASTNode selectedNode= location.getCoveringNode(context.getASTRoot());
+		if (selectedNode == null) return;
+		
+		if ( selectedNode instanceof ClassInstanceCreation ) {
+			ClassInstanceCreation c = (ClassInstanceCreation) selectedNode;
+			Type t = c.getType();
+			Name node = findName( t );
+			if ( node != null ) {
+				addSearchResults(node, project, results);
+			}
+		}
+		else {
+			for ( final String iPackage : readPackage(selectedNode, location) ) {
+				if ( !results.containsKey(iPackage) ) {
+					for ( IPackageExport pe : JavaHelper.findExportsForPackage(project, iPackage) ) { 
+						results.put( iPackage, new ImportPackageProposal( pe, project ) );
+					}
+				}
+			}
+		}
+	}
+
+	private void addSearchResults(Name node, ISigilProjectModel project,
+			HashMap<Object, IJavaCompletionProposal> results) {
+		for ( ISearchResult result : SigilSearch.findProviders( node.getFullyQualifiedName(), project, null) ) {
+			if ( project.getBundle().findImport( result.getPackageName() ) == null ) {
+				String type = result.getPackageName() + "." + node.getFullyQualifiedName();
+				results.put( type, new ImportSearchResultProposal( SigilUI.getActiveWorkbenchShell(), result, node, project ) );
+			}
+		}		
+	}
+
+	private Name findName(Type t) {
+		if ( t.isSimpleType() ) {
+			SimpleType st = (SimpleType) t;
+			return st.getName();
+		}
+		else if ( t.isArrayType() ) {
+			ArrayType at = (ArrayType) t;
+			return findName(at.getElementType());
+		}
+		else {
+			return null;
+		}
+	}
+
+	private Name findNode(IInvocationContext context, IProblemLocation problem) {
+		ASTNode selectedNode= problem.getCoveringNode(context.getASTRoot());
+		if (selectedNode == null) {
+			return null;
+		}
+
+		while (selectedNode.getLocationInParent() == QualifiedName.NAME_PROPERTY) {
+			selectedNode= selectedNode.getParent();
+		}
+
+		Name node= null;
+		
+		if (selectedNode instanceof Type) {
+			node = findName( (Type) selectedNode );
+		} else if (selectedNode instanceof Name) {
+			node= (Name) selectedNode;
+		}
+		
+		return node;
+	}
+
+	private ISigilProjectModel findProject(IInvocationContext context) throws CoreException {
+		IProject project = context.getCompilationUnit().getJavaProject().getProject();
+		if ( project.hasNature( SigilCore.NATURE_ID ) ) {
+			return SigilCore.create(project);
+		}
+		else {
+			return null;
+		}
+	}
+
+	private String[] readPackage(ASTNode selectedNode, IProblemLocation location) {
+		ArrayList<String> packages = new ArrayList<String>();
+		
+		ImportDeclaration id = (ImportDeclaration) ASTNodes.getParent(selectedNode, ASTNode.IMPORT_DECLARATION);
+
+		if ( id == null ) {
+			MethodInvocation m = (MethodInvocation) ASTNodes.getParent(selectedNode, ASTNode.METHOD_INVOCATION);
+
+			if (m != null) {
+				packages.add( readPackage( m ) );
+				while ( m.getExpression() != null && m.getExpression() instanceof MethodInvocation) {
+					m = (MethodInvocation) m.getExpression();
+					packages.add( readPackage( m ) );
+				}
+			}
+		}
+		else {
+			if ( id.isOnDemand() ) {
+				packages.add(id.getName().toString());
+			}
+			else {
+				String iStr = id.getName().toString();
+				packages.add(iStr.substring(0, iStr.lastIndexOf( "." ) ));
+			}
+		}
+		
+		return packages.toArray( new String[packages.size()] );
+	}
+
+	private String readPackage(MethodInvocation m) {
+		return m.resolveMethodBinding().getDeclaringClass().getPackage().getName();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportSearchResultProposal.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportSearchResultProposal.java
new file mode 100644
index 0000000..fbbf524
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportSearchResultProposal.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.quickfix;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.ModelElementFactory;
+import org.cauldron.sigil.model.ModelElementFactoryException;
+import org.cauldron.sigil.model.common.VersionRange;
+import org.cauldron.sigil.model.common.VersionRangeBoundingRule;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.search.ISearchResult;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.Name;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+import org.eclipse.jdt.ui.CodeStyleConfiguration;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+import org.osgi.framework.Version;
+
+public class ImportSearchResultProposal implements IJavaCompletionProposal {
+
+	private ISigilProjectModel project;
+	private ICompilationUnit fCompilationUnit;
+	private final ISearchResult result;
+	private final Shell shell;
+	
+	public ImportSearchResultProposal(Shell shell, ISearchResult result, Name node, ISigilProjectModel project) {
+		this.shell = shell;
+		this.result = result;
+		this.project = project;
+		if ( node != null ) {
+			CompilationUnit cu = (CompilationUnit) ASTNodes.getParent(node, ASTNode.COMPILATION_UNIT);
+			this.fCompilationUnit = (ICompilationUnit) cu.getJavaElement();
+		}
+	}
+
+	public int getRelevance() {
+		return 100;
+	}
+
+	public void apply(IDocument document) {
+		IPackageExport e = result.getExport();
+		if ( result.getExport() == null ) {
+			if ( MessageDialog.openQuestion(shell, "Modify " + result.getProvider().getBundleInfo().getSymbolicName(), result.getPackageName() + " is not exported. Do you want to export it now?" ) ) {
+				final IPackageExport pe = ModelElementFactory.getInstance().newModelElement(IPackageExport.class);
+				pe.setPackageName(result.getPackageName());
+				//e.setVersion(version)
+				final ISigilProjectModel mod = result.getProvider().getAncestor(ISigilProjectModel.class);
+				if ( mod == null ) {
+					throw new IllegalStateException( "Attempt to modify binary package export" );
+				}
+				WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+					@Override
+					protected void execute(IProgressMonitor monitor)
+					throws CoreException {
+						mod.getBundle().getBundleInfo().addExport(pe);
+						mod.save(null);
+					}
+				};
+
+				SigilUI.runWorkspaceOperation(op, null);
+				e = pe;
+			}
+		}
+
+		final IPackageImport i = ModelElementFactory.getInstance().newModelElement(IPackageImport.class);
+		i.setPackageName(e.getPackageName());
+		IPreferenceStore store = SigilCore.getDefault().getPreferenceStore();
+		VersionRangeBoundingRule lowerBoundRule = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_LOWER_BOUND));
+		VersionRangeBoundingRule upperBoundRule = VersionRangeBoundingRule.valueOf(store.getString(SigilCore.DEFAULT_VERSION_UPPER_BOUND));
+
+		Version version = e.getVersion();
+		VersionRange selectedVersions = VersionRange.newInstance(version, lowerBoundRule, upperBoundRule);
+		i.setVersions( selectedVersions );
+
+		WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
+			@Override
+			protected void execute(IProgressMonitor monitor)
+			throws CoreException {
+				project.getBundle().getBundleInfo().addImport( i );
+				project.save(null);
+			}
+		};
+
+		SigilUI.runWorkspaceOperation(op, null);
+		addSourceImport();
+	}
+
+	private void addSourceImport() {
+		// add import
+		try {
+			ImportRewrite rewrite= CodeStyleConfiguration.createImportRewrite(fCompilationUnit, true);
+			rewrite.addImport(result.getClassName());
+			JavaModelUtil.applyEdit(fCompilationUnit, rewrite.rewriteImports(null), false, null);
+		} catch (CoreException e) {
+			SigilCore.error( "Failed to add import", e);
+		}
+	}
+
+	public String getAdditionalProposalInfo() {
+		return null;
+	}
+
+	public IContextInformation getContextInformation() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+	
+	public String getDisplayString() {
+		String type = result.getClassName();
+		String loc = result.getExport() == null ? " from " + result.getProvider().getBundleInfo().getSymbolicName() : " version " + result.getExport().getVersion();
+		return "Import " + type + loc;
+	}
+
+	public Image getImage() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	public Point getSelection(IDocument document) {
+		return null;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportedClassReference.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportedClassReference.java
new file mode 100644
index 0000000..3a264c2
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/quickfix/ImportedClassReference.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.quickfix;
+
+import org.cauldron.sigil.model.osgi.IPackageImport;
+
+public class ImportedClassReference {
+	private IPackageImport pi;
+	private String type;
+	
+	public ImportedClassReference(IPackageImport packageImport, String typeName) {
+		this.pi = packageImport;
+		this.type = typeName;
+	}
+
+	public IPackageImport getPackageImport() {
+		return pi;
+	}
+
+	public String getFullType() {
+		return type;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/refactor/RenameCompositeRefactoring.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/refactor/RenameCompositeRefactoring.java
new file mode 100644
index 0000000..ce5075f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/refactor/RenameCompositeRefactoring.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.refactor;
+
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.RefactoringContribution;
+import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+
+public class RenameCompositeRefactoring extends Refactoring {
+
+	@Override
+	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException,
+			OperationCanceledException {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException,
+			OperationCanceledException {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public Change createChange(IProgressMonitor pm) throws CoreException,
+			OperationCanceledException {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/AccumulatorAdapter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/AccumulatorAdapter.java
new file mode 100644
index 0000000..3ad4fe9
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/AccumulatorAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+public abstract class AccumulatorAdapter<E> implements IAccumulator<E> {
+	public void addElement(E element) {
+		LinkedList<E> list = new LinkedList<E>();
+		list.add(element);
+		addElements(list);
+	};
+	
+	public void addElements(Collection<? extends E> elements) {
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/BackgroundLoadingSelectionDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/BackgroundLoadingSelectionDialog.java
new file mode 100644
index 0000000..8ad02de
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/BackgroundLoadingSelectionDialog.java
@@ -0,0 +1,487 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.cauldron.sigil.ui.editors.project.IElementDescriptor;
+import org.cauldron.sigil.ui.editors.project.WrappedContentProposal;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.fieldassist.ContentProposalAdapter;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposalListener;
+import org.eclipse.jface.fieldassist.TextContentAdapter;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.IOpenListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.OpenEvent;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.jface.viewers.ViewerSorter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+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.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.progress.IJobRunnable;
+
+public class BackgroundLoadingSelectionDialog<E> extends TitleAreaDialog implements IAccumulator<E> {
+
+	private final ILabelProvider DEFAULT_LABEL_PROVIDER = new LabelProvider() {
+		@SuppressWarnings("unchecked")
+		public String getText(Object element) {
+			String result;
+			if(element instanceof WrappedContentProposal<?>) {
+				WrappedContentProposal<E> contentProposal = (WrappedContentProposal<E>) element;
+				result = contentProposal.getLabel();
+			} else {
+				result = descriptor.getLabel((E) element);
+			}
+			return result;
+		}
+	};
+
+	private final IElementDescriptor<E> DEFAULT_DESCRIPTOR = new IElementDescriptor<E>() {
+		public String getLabel(E element) {
+			return getName(element);
+		}
+		public String getName(E element) {
+			return element == null ? "null" : element.toString();
+		}
+	};
+	
+	private final String selectionLabel;
+	private IFilter<? super E> filter;
+	private IElementDescriptor<? super E> descriptor = DEFAULT_DESCRIPTOR;
+	private ILabelProvider labelProvider = DEFAULT_LABEL_PROVIDER;
+	private final boolean multi;
+	
+	private final List<E> elements;
+	
+	private List<E> selection = null;
+	private String selectedName = null;
+	
+	private TableViewer viewer = null;
+	private Comparator<? super E> comparator;
+	
+	private HashMap<String, IJobRunnable> background = new HashMap<String, IJobRunnable>(); 
+	
+	public BackgroundLoadingSelectionDialog(Shell parentShell, String selectionLabel, boolean multi) {
+		super(parentShell);
+		elements = new ArrayList<E>();		
+		this.selectionLabel = selectionLabel;
+		this.multi = multi;
+	}
+	
+	public void setFilter(IFilter<? super E> filter) {
+		this.filter = filter;		
+	}
+	
+	public void setDescriptor(final IElementDescriptor<? super E> descriptor) {
+		if(descriptor != null) {
+			this.descriptor = descriptor;
+		} else {
+			this.descriptor = DEFAULT_DESCRIPTOR;
+		}
+	}
+	
+	public IElementDescriptor<? super E> getDescriptor() {
+		return descriptor;
+	}
+
+	public void setComparator(Comparator<? super E> comparator) {
+		this.comparator = comparator;		
+	}
+	
+	public void setLabelProvider(ILabelProvider labelProvider) {
+		if(labelProvider != null) {
+			this.labelProvider = labelProvider;
+		} else {
+			this.labelProvider = DEFAULT_LABEL_PROVIDER;
+		}
+	}
+	
+	public void addBackgroundJob(String name, IJobRunnable job) {
+		background.put(name, job);
+	}
+	
+	@Override
+	public int open() {
+		Job[] jobs = scheduleJobs();
+		try {
+			return super.open();
+		}
+		finally {
+			for ( Job j : jobs ) {
+				j.cancel();
+			}
+		}
+	}
+
+	private Job[] scheduleJobs() {
+		if ( background.isEmpty() ) {
+			return new Job[] {};
+		}
+		else {
+			ArrayList<Job> jobs = new ArrayList<Job>(background.size()); 
+			for ( Map.Entry<String, IJobRunnable> e : background.entrySet() ) {
+				final IJobRunnable run = e.getValue();
+				Job job = new Job(e.getKey()) {
+					@Override
+					protected IStatus run(IProgressMonitor monitor) {
+						return run.run(monitor);
+					}
+				};				
+				job.schedule();
+			}
+			
+			return jobs.toArray( new Job[jobs.size()] );
+		}
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		// Create Controls
+		Composite container = (Composite) super.createDialogArea(parent);
+		Composite composite = new Composite(container, SWT.NONE);
+		
+		new Label(composite, SWT.NONE).setText(selectionLabel);
+		
+		ContentProposalAdapter proposalAdapter = null;
+		Text txtSelection = null;
+		
+		Table table = null;
+		if(multi) {
+			table = new Table(composite, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER);
+			viewer = new TableViewer(table);
+			viewer.setContentProvider(new ArrayContentProvider());
+			viewer.addFilter(new ViewerFilter() {
+				public boolean select(Viewer viewer, Object parentElement, Object element) {
+					@SuppressWarnings("unchecked") E castedElement = (E) element;
+					return filter == null || filter.select(castedElement);
+				}
+			});
+			if(comparator != null) {
+				viewer.setSorter(new ViewerSorter() {
+					@Override
+					public int compare(Viewer viewer, Object o1, Object o2) {
+						@SuppressWarnings("unchecked")
+						E e1 = (E) o1;
+						@SuppressWarnings("unchecked")
+						E e2 = (E) o2;
+						return comparator.compare(e1, e2);
+					}
+				});
+			}
+			synchronized (elements) {
+				viewer.setInput(elements);
+			}
+			
+			if(labelProvider != null) {
+				viewer.setLabelProvider(labelProvider);
+			}
+		} else {
+			txtSelection = new Text(composite, SWT.BORDER);
+			ControlDecoration selectionDecor = new ControlDecoration(txtSelection, SWT.LEFT | SWT.TOP);
+			FieldDecoration proposalDecor = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL);
+			selectionDecor.setImage(proposalDecor.getImage());
+			selectionDecor.setDescriptionText(proposalDecor.getDescription());
+			
+			ExclusionContentProposalProvider<E> proposalProvider = new ExclusionContentProposalProvider<E>(elements, filter, descriptor);
+			
+			proposalAdapter = new ContentProposalAdapter(txtSelection, new TextContentAdapter(), proposalProvider, null, null);
+			proposalAdapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
+			if(labelProvider != null) {
+				proposalAdapter.setLabelProvider(labelProvider);
+			}
+			
+			if(selectedName != null) {
+				txtSelection.setText(selectedName);
+			}
+		}
+		updateSelection();
+		updateButtons();
+		
+		// Hookup listeners
+		if(proposalAdapter != null) {
+			proposalAdapter.addContentProposalListener(new IContentProposalListener() {
+				public void proposalAccepted(IContentProposal proposal) {
+					@SuppressWarnings("unchecked")
+					WrappedContentProposal<E> valueProposal = (WrappedContentProposal<E>) proposal;
+					E selected = valueProposal.getElement();
+					selection = new ArrayList<E>(1);
+					selection.add(selected);
+					
+					elementSelected(selected);
+					
+					updateButtons();
+				}
+			});
+		}
+		if(txtSelection != null) {
+			txtSelection.addModifyListener(new ModifyListener() {
+				public void modifyText(ModifyEvent e) {
+					selectedName = ((Text) e.widget).getText();
+					updateButtons();
+				}
+			});
+		}
+		if(viewer != null) {
+			viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+				public void selectionChanged(SelectionChangedEvent event) {
+					IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+					selection = new ArrayList<E>(sel.size());
+					for(Iterator<?> iter = sel.iterator(); iter.hasNext(); ) {
+						@SuppressWarnings("unchecked")
+						E element = (E) iter.next();
+						selection.add(element);
+					}
+					updateButtons();
+				}
+			});
+			viewer.addOpenListener(new IOpenListener() {
+				public void open(OpenEvent event) {
+					if(canComplete()) {
+						setReturnCode(IDialogConstants.OK_ID);
+						close();
+					}
+				}
+			});
+		}
+		
+		// Layout
+		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		if(multi) {
+			composite.setLayout(new GridLayout(1, false));
+			GridData layoutTable = new GridData(SWT.FILL, SWT.FILL, true, true);
+			layoutTable.heightHint = 200;
+			table.setLayoutData(layoutTable);
+		} else {
+			composite.setLayout(new GridLayout(2, false));
+			txtSelection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		}
+
+		return container;
+	}
+	
+	protected void elementSelected(E selection) {
+	}
+	
+	@Override
+	protected Control createButtonBar(Composite parent) {
+		Control bar = super.createButtonBar(parent);
+		updateButtons();
+		return bar;
+	}
+
+	/**
+	 * Can be called from any thread
+	 */
+	protected final void updateButtons() {
+		Runnable updateButtonsRunnable = new Runnable() {
+			public void run() {
+				Shell shell = getShell();
+				if(shell != null && !shell.isDisposed()) {
+					Button okButton = getButton(IDialogConstants.OK_ID);
+					if(okButton != null && !okButton.isDisposed()) {
+						okButton.setEnabled(canComplete());
+					}
+				}
+			}
+		};
+		Shell shell = getShell();
+		if (shell != null) {
+			onUIThread(shell, updateButtonsRunnable);
+		}
+	}
+	
+	/**
+	 * Subclasses may override but must call super.canComplete
+	 * @return
+	 */
+	protected synchronized boolean canComplete() {
+		boolean result = false;
+		
+		if ( selection != null ) {
+			if(multi) {
+				result = selection.size() > 0;
+			} else {
+				E sel = getSelectedElement();
+				result = sel != null && descriptor.getName(sel).equals(selectedName);
+			}
+		}
+		
+		return result;
+	}
+	
+	public final void addElement(E added) {
+		addElements(Collections.singleton(added));
+	}
+	
+	/**
+	 * Can be called from any thread
+	 */
+	public final void addElements(Collection<? extends E> added) {
+		final LinkedList<E> toAdd = new LinkedList<E>();
+		synchronized (elements) {
+			for ( E e : added ) {
+				if ( !elements.contains(e) ) {
+					elements.add( e );
+					toAdd.add(e);
+				}
+			}
+			Collections.sort(elements, comparator);
+		}
+		if(viewer != null) {
+			onUIThread(viewer.getControl(), new Runnable() {
+				public void run() {
+					if(!viewer.getControl().isDisposed()) {
+						viewer.add( toAdd.toArray() );
+						viewer.refresh();
+					}
+				}
+			});
+		}
+		else {
+			
+		}
+		updateSelection();
+		updateButtons();
+	}
+
+	protected void updateSelection() {
+		onUIThread(getShell(), new Runnable() {
+			public void run() {
+				if(selectedName != null) {
+					ArrayList<E> newSelection = new ArrayList<E>();
+					synchronized (elements) {
+						for (E e : elements) {
+							if(selectedName.equals(descriptor.getName(e))) {
+								newSelection.add(e);
+								break;
+							}
+						}
+					}
+					selection = newSelection;
+				}
+				else {
+					selection = Collections.emptyList();
+				}
+				if(viewer != null && !viewer.getControl().isDisposed()) {
+					viewer.setSelection(selection.isEmpty() ? StructuredSelection.EMPTY : new StructuredSelection(selection));
+				}
+			}
+		});			
+	}
+
+	private static final void onUIThread(Control control, Runnable r) {
+		if(control != null && !control.isDisposed()) {
+			try {
+				Display display = control.getDisplay();
+				if(Thread.currentThread() == display.getThread()) {
+					// We are on the UI thread already, just do the work
+					r.run();
+				} else {
+					// Not on the UI thread, need to bung over the runnable
+					display.asyncExec(r);
+				}
+			}
+			catch (SWTError e) {
+				if ( e.code == SWT.ERROR_WIDGET_DISPOSED ) {
+					// ignore
+				}
+				else {
+					throw e;
+				}
+			}
+		}
+	}
+	
+	public String getSelectedName() {
+		return selectedName;
+	}
+	
+	public void setSelectedName(String selectedName) {
+		this.selectedName = selectedName;
+		boolean change = false;
+		if ( selectedName == null ) {
+			if ( selection != null && !selection.isEmpty() ) {
+				change = true;
+			}
+		}
+		else {
+			if ( selection == null ) {
+				change = true;
+			}
+			else if ( selection.size() != 1 || !descriptor.getLabel(selection.get(0)).equals(selectedName)) {
+				change = true;				
+			}
+		}
+		
+		if ( change ) {
+			updateSelection();
+			updateButtons();
+		}
+	}
+	
+	public List<E> getSelectedElements() {
+		return selection;
+	}
+	
+	public E getSelectedElement() {
+		E result;
+		if(selection == null || selection.isEmpty()) {
+			result = null;
+		} else {
+			result = selection.get(0);
+		}
+		return result;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ColumnModelLabelProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ColumnModelLabelProvider.java
new file mode 100644
index 0000000..2febfbd
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ColumnModelLabelProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+public class ColumnModelLabelProvider extends ColumnLabelProvider {
+
+	private ModelLabelProvider provider = new ModelLabelProvider();
+	
+	@Override
+	public Image getImage(Object element) {
+		return provider.getImage(element);
+	}
+
+	@Override
+	public String getText(Object element) {
+		return provider.getText(element);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultContentProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultContentProvider.java
new file mode 100644
index 0000000..5fb4237
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultContentProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+public class DefaultContentProvider implements IContentProvider {
+
+	public void dispose() {
+	}
+
+	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultLabelProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultLabelProvider.java
new file mode 100644
index 0000000..29fb24b
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultLabelProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+
+public abstract class DefaultLabelProvider implements IBaseLabelProvider, ILabelProvider {
+
+	public boolean isLabelProperty(Object element, String property) {
+		return false;
+	}
+
+	public void dispose() {
+	}
+
+	public void addListener(ILabelProviderListener listener) {
+	}
+
+	public void removeListener(ILabelProviderListener listener) {
+	}
+
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultTableProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultTableProvider.java
new file mode 100644
index 0000000..715afa9
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultTableProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.Collection;
+
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+
+public abstract class DefaultTableProvider extends DefaultContentProvider implements IStructuredContentProvider {
+
+	/**
+	 * Utility method to convert the input element to an Object[].
+	 * 
+	 * @param inputElement
+	 * 
+	 * @return if inputElement is null -> empty array <br/>
+	 *         if inputElement is a {@link Collection} returns {@link Collection#toArray()}<br/>
+	 *         if inputElement is an Array class cast of inputElement to Object[]<br/>
+	 *  
+	 * @throws IllegalArgumentException if the element cannot be converted. 
+	 */
+	public Object[] toArray(Object inputElement) {
+		if ( inputElement == null ) {
+			return new Object[] {};
+		}
+		else if ( inputElement instanceof Collection ) {
+			Collection<?> col = (Collection<?>) inputElement;
+			return col.toArray();
+		}
+		else if ( inputElement.getClass().isArray() ) {
+			return (Object[]) inputElement;
+		}
+		else {
+			throw new IllegalArgumentException( "Invalid inputElement " + inputElement.getClass() );
+		}		
+	}
+	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultTreeContentProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultTreeContentProvider.java
new file mode 100644
index 0000000..d223835
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/DefaultTreeContentProvider.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import org.eclipse.jface.viewers.ITreeContentProvider;
+
+public abstract class DefaultTreeContentProvider extends DefaultContentProvider implements ITreeContentProvider {
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ExclusionContentProposalProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ExclusionContentProposalProvider.java
new file mode 100644
index 0000000..1663e2f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ExclusionContentProposalProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cauldron.sigil.ui.editors.project.IElementDescriptor;
+import org.cauldron.sigil.ui.editors.project.WrappedContentProposal;
+import org.cauldron.sigil.utils.GlobCompiler;
+import org.eclipse.jface.fieldassist.IContentProposal;
+import org.eclipse.jface.fieldassist.IContentProposalProvider;
+
+public class ExclusionContentProposalProvider<T> implements
+		IContentProposalProvider {
+	
+	private final Collection<? extends T> elements;
+	private final IFilter<? super T> filter;
+	private final IElementDescriptor<? super T> descriptor;
+
+	public ExclusionContentProposalProvider(Collection<? extends T> elements, IFilter<? super T> filter, IElementDescriptor<? super T> descriptor) {
+		this.elements = elements;
+		this.filter = filter;
+		this.descriptor = descriptor;
+	}
+	
+	public IContentProposal[] getProposals(String contents, int position) {
+		String matchString = contents.substring(0, position);
+		Pattern pattern = GlobCompiler.compile(matchString);
+
+		// Get a snapshot of the elements
+		T[] elementArray;
+		synchronized (elements) {
+			@SuppressWarnings("unchecked") T[] temp = (T[]) elements.toArray();
+			elementArray = temp;
+		}
+		
+		List<IContentProposal> result = new ArrayList<IContentProposal>();
+		
+		for (T element : elementArray) {
+			if(filter != null && filter.select(element)) {
+				IContentProposal proposal = WrappedContentProposal.newInstance(element, descriptor);
+				Matcher matcher = pattern.matcher(proposal.getContent());
+				if(matcher.find()) {
+					result.add(proposal);
+				}
+			}
+		}
+		
+		return result.toArray(new IContentProposal[result.size()]);
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ExportedPackageFinder.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ExportedPackageFinder.java
new file mode 100644
index 0000000..5c85724
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ExportedPackageFinder.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.IModelWalker;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.ui.progress.IJobRunnable;
+
+public class ExportedPackageFinder implements IJobRunnable {
+	
+	private final IAccumulator<? super IPackageExport> accumulator;
+	private final ISigilProjectModel sigil;
+
+	public ExportedPackageFinder(ISigilProjectModel sigil, IAccumulator<? super IPackageExport> accumulator) {
+		this.sigil = sigil;
+		this.accumulator = accumulator;
+	}
+
+	public IStatus run(final IProgressMonitor monitor) {
+		final List<IPackageExport> exports = new ArrayList<IPackageExport>(ResourcesDialogHelper.UPDATE_BATCH_SIZE);
+		final IModelWalker walker = new IModelWalker() {
+			public boolean visit(IModelElement element) {
+				if ( element instanceof IPackageExport ) {
+					IPackageExport pkgExport = (IPackageExport) element;
+					exports.add(pkgExport);
+
+					if(exports.size() >= ResourcesDialogHelper.UPDATE_BATCH_SIZE) {
+						accumulator.addElements(exports);
+						exports.clear();
+					}
+				}
+				return !monitor.isCanceled();
+			}
+		};
+		SigilCore.getRepositoryManager(sigil).visit(walker);
+		if(exports.size() > 0) {
+			accumulator.addElements(exports);
+		}
+
+		return Status.OK_STATUS;
+	}
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/FileUtils.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/FileUtils.java
new file mode 100644
index 0000000..60cb121
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/FileUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;

+

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.widgets.DirectoryDialog;

+import org.eclipse.swt.widgets.FileDialog;

+import org.eclipse.swt.widgets.Shell;

+import org.eclipse.swt.widgets.Text;

+

+public class FileUtils {

+	public static void loadFile( Shell shell, Text text, String msg, boolean isDirectory ) {

+		if ( isDirectory ) {

+			DirectoryDialog dialog = new DirectoryDialog(shell, SWT.NONE);

+			dialog.setMessage(msg);

+			String value = dialog.open();

+			if ( value != null ) {

+				text.setText( value );

+			}

+		}

+		else {

+			FileDialog dialog = new FileDialog(shell, SWT.NONE);

+			dialog.setText(msg);

+			String value = dialog.open();

+			if ( value != null ) {

+				text.setText( value );

+			}

+		}

+	}

+

+}

diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IAccumulator.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IAccumulator.java
new file mode 100644
index 0000000..99384be
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IAccumulator.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.Collection;
+
+public interface IAccumulator<E> {
+	public void addElement(E element);
+	public void addElements(Collection<? extends E> elements);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IExportToImportConverter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IExportToImportConverter.java
new file mode 100644
index 0000000..915d16c
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IExportToImportConverter.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+public interface IExportToImportConverter<E,I> {
+	I convert(E exportElement);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IFilter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IFilter.java
new file mode 100644
index 0000000..6cd3f23
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IFilter.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+public interface IFilter<T> {
+	boolean select(T element);
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IValidationListener.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IValidationListener.java
new file mode 100644
index 0000000..6c1b701
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/IValidationListener.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+public interface IValidationListener {
+
+	void validationMessage(String message, int level);
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ModelLabelProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ModelLabelProvider.java
new file mode 100644
index 0000000..6ff6901
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ModelLabelProvider.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.osgi.framework.Version;
+
+public class ModelLabelProvider extends LabelProvider {
+	
+	private volatile Set<? extends IModelElement> unresolvedElements = null;
+	
+	public Image getImage(Object element) {
+		boolean unresolved = (unresolvedElements == null) ? false : unresolvedElements.contains(element);
+		
+		if ( element instanceof ISigilBundle || element instanceof IBundleModelElement) {
+			return findBundle();
+		} else if(element instanceof IRequiredBundle) {
+			boolean optional = ((IRequiredBundle) element).isOptional();
+			return findRequiredBundle(optional, unresolved);
+		}
+		else if ( element instanceof IPackageImport ) {
+			boolean optional = ((IPackageImport) element).isOptional();
+			return findPackageImport(optional, unresolved);
+		}
+		else if ( element instanceof IPackageExport ) {
+			return findPackageExport();
+		}
+		else if ( element instanceof IPackageFragmentRoot ) {
+			IPackageFragmentRoot root = (IPackageFragmentRoot) element;
+			try {
+				if ( root.getKind() == IPackageFragmentRoot.K_SOURCE ) {
+					return findPackage();
+				}
+				else {
+					return findBundle();
+				}
+			} catch (JavaModelException e) {
+				SigilCore.error( "Failed to inspect package fragment root", e );
+			}
+		}
+		else if ( element instanceof IClasspathEntry ) {
+			return findPackage();
+		}
+		if ( element instanceof IBundleRepository ) {
+			IBundleRepository rep = (IBundleRepository) element;
+			IRepositoryModel config = SigilCore.getRepositoryConfiguration().findRepository(rep.getId());
+			return config.getType().getIcon();
+		}
+	
+		return null;
+	}
+
+	public String getText(Object element) {
+		if ( element instanceof ISigilBundle ) {
+			ISigilBundle bundle = (ISigilBundle) element;
+			return bundle.getBundleInfo().getSymbolicName() + " " + bundle.getBundleInfo().getVersion(); 
+		}
+		if ( element instanceof IBundleModelElement ) {
+			IBundleModelElement bundle = (IBundleModelElement) element;
+			return bundle.getSymbolicName();
+		}
+		if ( element instanceof IRequiredBundle ) {
+			IRequiredBundle req = (IRequiredBundle) element;
+			return req.getSymbolicName() + " " + req.getVersions();
+		}
+		
+		if ( element instanceof IPackageImport ) {
+			IPackageImport req = (IPackageImport) element;
+			return req.getPackageName() + " " + req.getVersions();
+		}
+		
+		if ( element instanceof IPackageExport ) {
+			IPackageExport pe = (IPackageExport) element;
+			Version rawVersion = pe.getRawVersion();
+			return rawVersion != null ? pe.getPackageName() + " " + rawVersion : pe.getPackageName();
+		}
+		
+		if ( element instanceof IResource ) {
+			IResource resource = (IResource) element;
+			return resource.getName();
+		}
+		
+		if ( element instanceof IPackageFragment )  {
+			IPackageFragment f = (IPackageFragment) element;
+			return f.getElementName();
+		}
+		
+		if ( element instanceof IPackageFragmentRoot ) {
+			IPackageFragmentRoot f = (IPackageFragmentRoot) element;
+			try {
+				return f.getUnderlyingResource().getName();
+			} catch (JavaModelException e) {
+				return "unknown";
+			}
+		}
+		
+		if ( element instanceof IClasspathEntry ) {
+			IClasspathEntry cp = (IClasspathEntry) element;
+			return cp.getPath().toString();
+		}
+		
+		if ( element instanceof IBundleRepository ) {
+			IBundleRepository rep = (IBundleRepository) element;
+			IRepositoryModel config = SigilCore.getRepositoryConfiguration().findRepository(rep.getId());
+			return config.getName();
+		}
+		
+		return element.toString();
+	}
+
+	private Image findPackage() {
+		return cacheImage( "icons/package_obj.png" ); 
+	}
+
+	private Image findPackageImport(boolean optional, boolean unresolved) {
+		String path;
+		if(optional) {
+			path = unresolved ? "icons/package_obj_import_opt_error.png" : "icons/package_obj_import_opt.png";
+		} else {
+			path = unresolved ? "icons/package_obj_import_error.png" : "icons/package_obj_import.png";
+		}
+		return cacheImage(path);
+	}
+	
+	private Image findPackageExport() {
+		return cacheImage("icons/package_obj_export.png");
+	}
+	
+	private Image findBundle() {
+		return cacheImage("icons/jar_obj.png");
+	}
+	
+	private Image findRequiredBundle(boolean optional, boolean unresolved) {
+		String path;
+		if(optional) {
+			path = unresolved ? "icons/required_bundle_opt_error.png" : "icons/required_bundle_opt.png";
+		} else {
+			path = unresolved ? "icons/required_bundle_error.png" : "icons/jar_obj.png";
+		}
+		return cacheImage(path);
+	}
+	
+	private static Image cacheImage(String path) {
+		return SigilUI.cacheImage(path, ModelLabelProvider.class.getClassLoader());
+	}
+
+	public void setUnresolvedElements(Set<? extends IModelElement> elements) {
+		this.unresolvedElements = elements;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/PackageFilter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/PackageFilter.java
new file mode 100644
index 0000000..d451538
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/PackageFilter.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+
+public class PackageFilter implements IFilter<IPackageImport> {
+	
+	private Set<String> names = new HashSet<String>();
+	
+	public PackageFilter(String[] packageNames) {
+		for (String name : packageNames) {
+			names.add(name);
+		}
+	}
+	
+	public PackageFilter(IPackageExport[] packages) {
+		for (IPackageExport packageExport : packages) {
+			names.add(packageExport.getPackageName());
+		}
+	}
+
+	public boolean select(IPackageImport element) {
+		return !names.contains(element.getPackageName());
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ProjectUtils.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ProjectUtils.java
new file mode 100644
index 0000000..4cf8ad4
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ProjectUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.concurrent.Callable;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.preferences.OptionalPrompt;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+public class ProjectUtils {
+	public static boolean runTaskWithRebuildCheck(final Runnable task, Shell shell) {
+		return runTaskWithRebuildCheck( new Callable<Boolean>() {
+			public Boolean call() throws Exception {
+				task.run();
+				return true;
+			}
+		}, shell);
+	}
+	
+	public static boolean runTaskWithRebuildCheck(Callable<Boolean> callable,
+			Shell shell) {
+		int result = checkRebuild(shell);
+		if ( result == IDialogConstants.CANCEL_ID ) {
+			return false;
+		}
+		else {
+			try {
+				if ( Boolean.TRUE == callable.call() ) {
+					if ( result == IDialogConstants.YES_ID ) {
+						SigilUI.runWorkspaceOperation( new WorkspaceModifyOperation() {
+							@Override
+							protected void execute(IProgressMonitor monitor) {
+								SigilCore.rebuildAllBundleDependencies(monitor);
+							}
+						}, shell );
+					}
+					return true;
+				}
+				else {
+					return false;
+				}
+			} catch (Exception e) {
+				SigilCore.error( "Failed to run caller", e);
+				return false;
+			}
+		}
+	}
+	
+	private static int checkRebuild(Shell shell) {
+		if ( SigilCore.getRoot().getProjects().isEmpty() ) {
+			return IDialogConstants.NO_ID;
+		}
+		else {
+			return OptionalPrompt.optionallyPromptWithCancel(SigilCore.getDefault().getPreferenceStore(), SigilCore.PREFERENCES_REBUILD_PROJECTS, "Rebuild", "Do you wish to rebuild all Sigil projects", shell );
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ResourceReviewDialog.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ResourceReviewDialog.java
new file mode 100644
index 0000000..bebe867
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ResourceReviewDialog.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.Collection;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+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.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+
+public class ResourceReviewDialog<T extends IModelElement> extends TitleAreaDialog {
+
+	private String title;	
+	private Collection<T> resources;
+	
+	private TableViewer viewer;
+
+	public ResourceReviewDialog(Shell parentShell, String title, Collection<T> resources) {
+		super(parentShell);
+		this.title = title;
+		this.resources = resources;
+	}
+
+	public Collection<T> getResources() {
+		return resources;
+	}
+
+	@Override
+	protected Control createDialogArea(Composite parent) {
+		setTitle(title);
+
+		// Create controls
+		Composite container = (Composite) super.createDialogArea(parent);
+		Composite composite = new Composite(container, SWT.NONE);		
+		Table table = new Table(composite, SWT.BORDER | SWT.VIRTUAL);
+		
+		final Button remove = new Button(composite, SWT.PUSH);
+		remove.setText("Remove");
+		remove.setEnabled(false);
+		
+		remove.addSelectionListener( new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				handleRemove();
+			}
+		} );
+		
+		viewer = new TableViewer(table);
+		viewer.setContentProvider( new DefaultTableProvider() {
+			public Object[] getElements(Object inputElement) {
+				return toArray(inputElement);
+			}
+		});
+		
+		viewer.setInput( resources );
+		viewer.addSelectionChangedListener( new ISelectionChangedListener() {
+			public void selectionChanged(SelectionChangedEvent event) {
+				remove.setEnabled(!event.getSelection().isEmpty());
+			}
+		});
+		
+		// layout
+		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		composite.setLayout(new GridLayout(2, false));
+		GridData tableLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 4);
+		tableLayoutData.heightHint = 150;
+		table.setLayoutData(tableLayoutData);
+		
+		return container;
+	}
+
+	private void handleRemove() {
+		ISelection s = viewer.getSelection();
+		if ( !s.isEmpty() ) {
+			IStructuredSelection sel = (IStructuredSelection) s;
+			resources.remove( sel.getFirstElement() );
+			viewer.refresh();
+		}
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ResourcesDialogHelper.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ResourcesDialogHelper.java
new file mode 100644
index 0000000..4644fe3
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/ResourcesDialogHelper.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.IModelWalker;
+import org.cauldron.sigil.model.osgi.IBundleModelElement;
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IPackageModelElement;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.model.util.JavaHelper;
+import org.cauldron.sigil.ui.editors.project.IElementDescriptor;
+import org.cauldron.sigil.ui.editors.project.NewPackageExportDialog;
+import org.cauldron.sigil.ui.editors.project.NewResourceSelectionDialog;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeRoot;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.progress.IJobRunnable;
+
+public class ResourcesDialogHelper {
+	
+	static final int UPDATE_BATCH_SIZE = 100;
+
+	public static BackgroundLoadingSelectionDialog<String> createClassSelectDialog(Shell shell, String title, final ISigilProjectModel project, String selected, final String ifaceOrParentClass) {
+		final BackgroundLoadingSelectionDialog<String> dialog = new BackgroundLoadingSelectionDialog<String>(shell, "Class Name", true);
+		
+		IJobRunnable job = new IJobRunnable() {
+			public IStatus run(IProgressMonitor monitor) {
+				try {
+					for ( IJavaElement e : JavaHelper.findTypes(project.getJavaModel(), IJavaElement.PACKAGE_FRAGMENT_ROOT ) ) {
+						IPackageFragmentRoot root = (IPackageFragmentRoot) e;
+						if ( project.isInBundleClasspath(root)) {
+							for ( IJavaElement e1 : JavaHelper.findTypes(root, IJavaElement.COMPILATION_UNIT, IJavaElement.CLASS_FILE ) ) {
+								ITypeRoot typeRoot = (ITypeRoot) e1;
+								IType type = (IType) JavaHelper.findType(typeRoot, IJavaElement.TYPE);
+								if ( JavaHelper.isAssignableTo( ifaceOrParentClass, type ) ) {
+									dialog.addElement(type.getFullyQualifiedName());					
+								}							
+							}
+						}
+					}
+					
+					return Status.OK_STATUS;
+				} catch (JavaModelException e) {
+					return e.getStatus();
+				}				
+			}
+
+		};
+		
+		dialog.addBackgroundJob("Scanning for activators in project", job);
+
+		return dialog;
+	}
+	
+	public static NewResourceSelectionDialog<IPackageExport> createImportDialog(
+			Shell shell, 
+			String title, 
+			ISigilProjectModel sigil, 
+			final IPackageImport selected, 
+			final Collection<IPackageImport> existing) {
+		final Set<String> existingNames = new HashSet<String>();
+		
+		for (IPackageImport existingImport : existing) {
+			existingNames.add(existingImport.getPackageName());
+		}
+		
+		final NewResourceSelectionDialog<IPackageExport> dialog = new NewResourceSelectionDialog<IPackageExport>(shell, "Package Name:", false);
+		
+		dialog.setFilter( new IFilter<IPackageModelElement>() {
+			public boolean select(IPackageModelElement element) {
+				return !existingNames.contains(element.getPackageName());
+			}
+		} );
+		
+		dialog.setComparator(new Comparator<IPackageExport>() {
+			public int compare(IPackageExport o1, IPackageExport o2) {
+				return o1.compareTo(o2);
+			}
+		});
+		
+		dialog.setDescriptor(new IElementDescriptor<IPackageExport>() {
+			public String getLabel(IPackageExport element) {
+				return getName(element) + " (" + element.getVersion().toString() + ")";
+			}
+
+			public String getName(IPackageExport element) {
+				return element.getPackageName();
+			}
+		});
+		
+		dialog.setLabelProvider(new WrappedContentProposalLabelProvider<IPackageExport>(dialog.getDescriptor()));
+		
+		if(selected != null) {
+			dialog.setSelectedName(selected.getPackageName());
+			dialog.setVersions(selected.getVersions());
+			dialog.setOptional(selected.isOptional());
+		}
+
+		IJobRunnable job = new ExportedPackageFinder(sigil, dialog);
+		dialog.addBackgroundJob("Scanning for exports in workspace", job);
+		
+		return dialog;
+	}
+	
+	public static NewPackageExportDialog createNewExportDialog(Shell shell, String title, final IPackageExport selected, final ISigilProjectModel project, boolean multiSelect) {
+		IFilter<IJavaElement> selectFilter = new IFilter<IJavaElement>() {
+			public boolean select(IJavaElement e) {
+				if ( selected != null && e.getElementName().equals( selected.getPackageName() ) ) {
+					return true;
+				}
+				
+				if ( e.getElementName().trim().length() > 0 && isLocal( e ) ) {
+					for ( IPackageExport p : project.getBundle().getBundleInfo().getExports() ) {
+						if ( p.getPackageName().equals( e.getElementName() ) ) {
+							return false;
+						}
+					}
+					
+					return true;
+				}
+				else {
+					return false;
+				}
+			}
+			
+			private boolean isLocal(IJavaElement java) {
+				try {
+					switch ( java.getElementType() ) {
+					case IJavaElement.PACKAGE_FRAGMENT:
+						IPackageFragment fragment= (IPackageFragment) java;
+						return fragment.containsJavaResources();
+					default:
+						throw new IllegalStateException( "Unexpected resource type " + java );
+					}
+				}
+				catch (JavaModelException e) {
+					SigilCore.error( "Failed to inspect java element ", e );
+					return false;
+				}
+			}
+			
+		};
+		
+		final NewPackageExportDialog dialog = new NewPackageExportDialog(shell, multiSelect);
+		dialog.setFilter(selectFilter);
+		
+		dialog.setProjectVersion(project.getVersion());
+		if ( selected != null ) {
+			dialog.setSelectedName(selected.getPackageName());
+			dialog.setVersion(selected.getRawVersion());
+		}
+		
+		IJobRunnable job = new IJobRunnable() {
+			public IStatus run(IProgressMonitor monitor) {
+				try {
+					ArrayList<IPackageFragment> list = new ArrayList<IPackageFragment>(UPDATE_BATCH_SIZE);
+					for ( IJavaElement e : JavaHelper.findTypes(project.getJavaModel(), IJavaElement.PACKAGE_FRAGMENT_ROOT) ) {
+						IPackageFragmentRoot root = (IPackageFragmentRoot) e;
+						if ( project.isInBundleClasspath(root) ) {
+							for ( IJavaElement e1 : JavaHelper.findTypes(root, IJavaElement.PACKAGE_FRAGMENT) ) {
+								list.add((IPackageFragment) e1);
+								if(list.size() >= UPDATE_BATCH_SIZE) {
+									dialog.addElements(list);
+									list.clear();
+								}
+							}
+						}
+					}
+					if(!list.isEmpty()) {
+						dialog.addElements(list);
+					}
+					return Status.OK_STATUS;
+				} catch (JavaModelException e) {
+					return e.getStatus();
+				}
+			}
+		};
+		
+		dialog.addBackgroundJob("Scanning for packages in project", job);
+		
+		return dialog;
+	}
+	
+	public static NewResourceSelectionDialog<IBundleModelElement> createRequiredBundleDialog(Shell shell, String title, final ISigilProjectModel sigil, final IRequiredBundle selected, final Collection<IRequiredBundle> existing) {
+		final Set<String> existingNames = new HashSet<String>();
+		for(IRequiredBundle existingBundle : existing) {
+			existingNames.add(existingBundle.getSymbolicName());
+		}
+		
+		final NewResourceSelectionDialog<IBundleModelElement> dialog = new NewResourceSelectionDialog<IBundleModelElement>(shell, "Bundle:", false);
+		
+		dialog.setDescriptor(new IElementDescriptor<IBundleModelElement>() {
+			public String getLabel(IBundleModelElement element) {
+				return getName(element) + " (" + element.getVersion() + ")";
+			}
+			public String getName(IBundleModelElement element) {
+				return element.getSymbolicName();
+			}
+		});
+		
+		dialog.setLabelProvider(new WrappedContentProposalLabelProvider<IBundleModelElement>(dialog.getDescriptor()));
+		
+		dialog.setFilter(new IFilter<IBundleModelElement>() {
+			public boolean select(IBundleModelElement element) {
+				return !existingNames.contains(element.getSymbolicName());
+			}
+		});
+		
+		dialog.setComparator(new Comparator<IBundleModelElement>() {
+			public int compare(IBundleModelElement o1, IBundleModelElement o2) {
+				return o1.getSymbolicName().compareTo(o2.getSymbolicName());
+			}
+		});
+		
+		if(selected != null) {
+			dialog.setSelectedName(selected.getSymbolicName());
+			dialog.setVersions(selected.getVersions());
+			dialog.setOptional(selected.isOptional());
+		}
+				
+		IJobRunnable job = new IJobRunnable() {
+			public IStatus run(final IProgressMonitor monitor) {
+				final List<IBundleModelElement> bundles = new ArrayList<IBundleModelElement>(UPDATE_BATCH_SIZE);
+				final IModelWalker walker = new IModelWalker() {
+					//int count = 0;
+					public boolean visit(IModelElement element) {
+						if ( element instanceof  IBundleModelElement) {
+							IBundleModelElement b = (IBundleModelElement) element;
+							bundles.add(b);
+
+							if(bundles.size() >= UPDATE_BATCH_SIZE) {
+								dialog.addElements(bundles);
+								bundles.clear();
+							}
+							// no need to recurse further.
+							return false;
+						}
+						return !monitor.isCanceled();
+					}
+				};
+				SigilCore.getRepositoryManager(sigil).visit(walker);
+				if(!bundles.isEmpty()) {
+					dialog.addElements(bundles);
+				}
+				return Status.OK_STATUS;
+			}
+		};
+		
+		dialog.addBackgroundJob("Scanning for bundles in workspace", job);
+		
+		return dialog;
+	}
+}
+
+
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/SingletonSelection.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/SingletonSelection.java
new file mode 100644
index 0000000..22a0a46
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/SingletonSelection.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jface.viewers.IStructuredSelection;
+
+@SuppressWarnings("unchecked")
+public class SingletonSelection implements IStructuredSelection {
+
+	private final Object singleton;
+	
+	public SingletonSelection(Object singleton) {
+		this.singleton = singleton;
+	}
+
+	public Object getFirstElement() {
+		return singleton;
+	}
+
+	public Iterator iterator() {
+		return Collections.singleton(singleton).iterator();
+	}
+
+	public int size() {
+		return 1;
+	}
+
+	public Object[] toArray() {
+		return new Object[] { singleton };
+	}
+
+	public List toList() {
+		ArrayList list = new ArrayList(1);
+		list.add( singleton );
+		return list;
+	}
+
+	public boolean isEmpty() {
+		return false;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/WrappedContentProposalLabelProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/WrappedContentProposalLabelProvider.java
new file mode 100644
index 0000000..f46b4b2
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/util/WrappedContentProposalLabelProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.util;
+
+import org.cauldron.sigil.ui.editors.project.IElementDescriptor;
+import org.cauldron.sigil.ui.editors.project.WrappedContentProposal;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+public class WrappedContentProposalLabelProvider<E> extends LabelProvider {
+	
+	private final IElementDescriptor<? super E> descriptor;
+	private final ModelLabelProvider projectLabelProvider;
+
+	public WrappedContentProposalLabelProvider(IElementDescriptor<? super E> descriptor) {
+		this.descriptor = descriptor;
+		projectLabelProvider = new ModelLabelProvider();
+	}
+	
+	@SuppressWarnings("unchecked")
+	private E adapt(Object element) {
+		E result;
+		if(element instanceof WrappedContentProposal<?>) {
+			WrappedContentProposal<?> proposal = (WrappedContentProposal<?>) element;
+			result = (E) proposal.getElement();
+		} else {
+			result = (E) element;
+		}
+		return result;
+	}
+	
+	@Override
+	public Image getImage(Object element) {
+		Object value = adapt(element);
+		return projectLabelProvider.getImage(value);
+	}
+	
+	@Override
+	public String getText(Object element) {
+		return descriptor.getLabel(adapt(element));
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/ModelElementComparator.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/ModelElementComparator.java
new file mode 100644
index 0000000..cbfbbbe
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/ModelElementComparator.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views;
+
+import org.cauldron.sigil.model.osgi.IPackageExport;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.eclipse.jface.viewers.ContentViewer;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+
+public class ModelElementComparator extends ViewerComparator {
+	private static final int EXPORT_GROUP = 0;
+	private static final int IMPORT_GROUP = 1;
+	private static final int REQUIRE_GROUP = 2;
+	private static final int OTHER_GROUP = 4;
+	public int category(Object element) {
+		if ( element instanceof IPackageImport ) {
+			return IMPORT_GROUP;
+		}
+		else if ( element instanceof IPackageExport ) {
+			return EXPORT_GROUP;
+		}
+		else if ( element instanceof IRequiredBundle ) {
+			return REQUIRE_GROUP;
+		}
+		else {
+			return OTHER_GROUP;
+		}
+	}
+	@SuppressWarnings("unchecked")
+	@Override
+	public int compare(Viewer viewer, Object e1, Object e2) {
+        int cat1 = category(e1);
+        int cat2 = category(e2);
+
+        if (cat1 != cat2) {
+			return cat1 - cat2;
+		}
+        
+        if ( cat1 == OTHER_GROUP ) {
+            String name1;
+            String name2;
+
+            if (viewer == null || !(viewer instanceof ContentViewer)) {
+                name1 = e1.toString();
+                name2 = e2.toString();
+            } else {
+                IBaseLabelProvider prov = ((ContentViewer) viewer)
+                        .getLabelProvider();
+                if (prov instanceof ILabelProvider) {
+                    ILabelProvider lprov = (ILabelProvider) prov;
+                    name1 = lprov.getText(e1);
+                    name2 = lprov.getText(e2);
+                } else {
+                    name1 = e1.toString();
+                    name2 = e2.toString();
+                }
+            }
+            if (name1 == null) {
+    			name1 = "";//$NON-NLS-1$
+    		}
+            if (name2 == null) {
+    			name2 = "";//$NON-NLS-1$
+    		}
+
+            // use the comparator to compare the strings
+            return getComparator().compare(name1, name2);
+        }
+        else {
+        	Comparable c1 = (Comparable) e1;
+        	Comparable c2 = (Comparable) e2;
+        	return c1.compareTo(c2);
+        }
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/RepositoryViewPart.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/RepositoryViewPart.java
new file mode 100644
index 0000000..4fdb238
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/RepositoryViewPart.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.ICompoundModelElement;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.cauldron.sigil.model.util.ModelHelper;
+import org.cauldron.sigil.repository.IBundleRepository;
+import org.cauldron.sigil.repository.IRepositoryChangeListener;
+import org.cauldron.sigil.repository.IRepositoryVisitor;
+import org.cauldron.sigil.repository.RepositoryChangeEvent;
+import org.cauldron.sigil.ui.SigilUI;
+import org.cauldron.sigil.ui.util.DefaultTreeContentProvider;
+import org.cauldron.sigil.ui.util.ModelLabelProvider;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.util.LocalSelectionTransfer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DragSourceAdapter;
+import org.eclipse.swt.dnd.DragSourceEvent;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.part.ViewPart;
+
+public class RepositoryViewPart extends ViewPart implements IRepositoryChangeListener {
+
+	public class FindUsersAction extends Action {
+		@Override
+		public String getText() {
+			return "Find Uses";
+		}
+
+		@Override
+		public void run() {
+			ISelection s = treeViewer.getSelection();
+			if ( !s.isEmpty() ) {
+				IStructuredSelection sel = (IStructuredSelection) s;
+				IModelElement e = (IModelElement) sel.getFirstElement();
+				List<IModelElement>	users = ModelHelper.findUsers(e);
+				String msg = null;
+				if ( users.isEmpty() ) {
+					msg = "No users of " + e;
+				}
+				else {
+					StringBuilder b = new StringBuilder();	
+					for ( IModelElement u : users ) {
+						ISigilBundle bndl = u.getAncestor(ISigilBundle.class);
+						b.append( bndl );
+						b.append( "->" );
+						b.append( u );
+						b.append( "\n" );
+					}
+					msg = b.toString();
+				}
+				MessageDialog.openInformation(getViewSite().getShell(), "Information", msg );
+			}
+		}
+	}
+
+	class RepositoryAction extends Action {
+		final IBundleRepository rep;
+		final IRepositoryModel model;
+		
+		public RepositoryAction(IBundleRepository rep) {
+			this.rep = rep;
+			this.model = SigilCore.getRepositoryConfiguration().findRepository(rep.getId());
+		}
+		
+		
+		@Override
+		public void run() {
+			treeViewer.setInput(rep);
+			createMenu();
+		}
+
+		@Override
+		public String getText() {
+			String name = model.getName(); 
+			if ( treeViewer.getInput() == rep ) {
+				name = "> " + name;
+			}
+			return name;
+		}
+		
+		@Override
+		public ImageDescriptor getImageDescriptor() {
+			Image img = model.getType().getIcon();
+			if ( img == null ) {
+				return ImageDescriptor.createFromFile(RepositoryViewPart.class, "/icons/jars_obj.png");
+			}
+			else {
+				return ImageDescriptor.createFromImage(img);
+			}
+		}
+	}
+	
+	class RefreshAction extends Action {
+		@Override
+		public void run() {
+			IBundleRepository rep = (IBundleRepository) treeViewer.getInput();
+			if ( rep != null ) {
+				rep.refresh();
+				treeViewer.refresh();
+			}
+		}
+
+		@Override
+		public String getText() {
+			return "Refresh";
+		}
+		
+		@Override
+		public ImageDescriptor getImageDescriptor() {
+			return ImageDescriptor.createFromFile(RepositoryViewPart.class, "/icons/refreshBundle.png");
+		}
+
+	}
+
+	private TreeViewer treeViewer;
+
+	@Override
+	public void createPartControl(Composite parent) {
+		createBody(parent);
+		createMenu();
+		SigilCore.getGlobalRepositoryManager().addRepositoryChangeListener(this);
+	}
+
+	@Override
+	public void dispose() {
+		SigilCore.getGlobalRepositoryManager().removeRepositoryChangeListener(this);
+		super.dispose();
+	}
+
+	private void createMenu() {
+		createTopMenu();
+		createLocalMenu();
+	}
+
+	private void createLocalMenu() {
+		/*MenuManager menuMgr = new MenuManager();
+		menuMgr.add( new FindUsersAction() );
+		Menu menu = menuMgr.createContextMenu(treeViewer.getControl());
+
+		treeViewer.getControl().setMenu(menu);
+		getViewSite().registerContextMenu(menuMgr, treeViewer); */
+		IActionBars bars = getViewSite().getActionBars();
+		IToolBarManager toolBar = bars.getToolBarManager();
+		toolBar.add( new RefreshAction() );
+	}
+
+	private void createTopMenu() {
+		IActionBars bars = getViewSite().getActionBars();
+		IMenuManager menu = bars.getMenuManager();
+		menu.removeAll();
+		for ( final IBundleRepository rep : SigilCore.getGlobalRepositoryManager().getRepositories() ) {
+			if ( treeViewer.getInput() == null ) {
+				treeViewer.setInput(rep);
+			}
+			
+			RepositoryAction action = new RepositoryAction(rep);
+			menu.add( action );			
+		}
+	}
+
+	private void createBody(Composite parent) {
+		// components
+		Composite control = new Composite(parent, SWT.NONE);
+		Tree tree = new Tree(control, SWT.NONE);
+		
+		// layout
+		control.setLayout( new GridLayout(1, false ) );
+		tree.setLayoutData( new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1) );
+		
+		// viewer
+		treeViewer = new TreeViewer(tree);
+		treeViewer.setContentProvider( new DefaultTreeContentProvider() {
+			public Object[] getChildren(Object parentElement) {
+				if ( parentElement instanceof ICompoundModelElement ) {
+					ICompoundModelElement model = (ICompoundModelElement) parentElement;
+					return model.children();
+				}
+				
+				return null;
+			}
+
+			public Object getParent(Object element) {
+				if ( element instanceof IModelElement ) {
+					IModelElement model = (IModelElement) element;
+					return model.getParent();
+				}
+				
+				return null;
+			}
+
+			public boolean hasChildren(Object element) {
+				if ( element instanceof ICompoundModelElement ) {
+					ICompoundModelElement model = (ICompoundModelElement) element;
+					return model.children().length > 0;
+				}
+				return false;
+			}
+
+			public Object[] getElements(Object inputElement) {
+				IBundleRepository rep = (IBundleRepository) inputElement;
+				return getBundles(rep);
+			}
+		});
+		
+		treeViewer.setComparator( new ModelElementComparator() );
+		
+		treeViewer.setLabelProvider( new ModelLabelProvider() );
+		
+		treeViewer.addDragSupport(DND.DROP_LINK, new Transfer[] { LocalSelectionTransfer.getTransfer() }, new DragSourceAdapter() {
+			@Override
+			public void dragFinished(DragSourceEvent event) {
+				// TODO Auto-generated method stub
+				super.dragFinished(event);
+			}
+
+			@Override
+			public void dragSetData(DragSourceEvent event) {
+				// TODO Auto-generated method stub
+				super.dragSetData(event);
+			}
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public void dragStart(DragSourceEvent event) {
+				if ( treeViewer.getSelection().isEmpty() ) {
+					IStructuredSelection sel = (IStructuredSelection) treeViewer.getSelection();
+					for ( Iterator<IModelElement> i = sel.iterator(); i.hasNext(); ) {
+						IModelElement e = i.next();
+						if ( e instanceof ISigilBundle ) {
+							event.data = e;
+						}
+						else {
+							event.doit = false;
+						}
+					}
+				}
+				else {
+					event.doit = false;
+				}
+			}
+		});
+	}
+
+	@Override
+	public void setFocus() {
+	}
+
+	public void repositoryChanged(RepositoryChangeEvent event) {
+		switch ( event.getType() ) {
+		case ADDED:
+			createTopMenu();
+			break;
+		case CHANGED:
+			if ( event.getRepository() == treeViewer.getInput() ) {
+				SigilUI.runInUI( new Runnable() {
+					public void run() {
+						treeViewer.refresh();
+					} 					
+				} );
+			}
+			break;
+		case REMOVED:
+			if ( event.getRepository() == treeViewer.getInput() ) {
+				treeViewer.setInput(null);
+			}
+			createTopMenu();
+		}
+	}
+	
+	private Object[] getBundles(IBundleRepository repository) {
+		final LinkedList<ISigilBundle> bundles = new LinkedList<ISigilBundle>();
+		repository.accept(new IRepositoryVisitor() {
+			public boolean visit(ISigilBundle bundle) {
+				bundles.add(bundle);
+				return true;
+			}
+		});
+		return bundles.toArray();
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleConnectionHighlighter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleConnectionHighlighter.java
new file mode 100644
index 0000000..586375e
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleConnectionHighlighter.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import java.util.Set;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.eclipse.draw2d.ColorConstants;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.zest.core.widgets.GraphConnection;
+import org.eclipse.zest.core.widgets.GraphItem;
+import org.eclipse.zest.core.widgets.GraphNode;
+
+public class BundleConnectionHighlighter implements ISelectionChangedListener {
+
+	private BundleResolverView view;
+	
+	public BundleConnectionHighlighter(BundleResolverView view) {
+		this.view = view;
+	}
+	
+	public void selectionChanged(SelectionChangedEvent event) {
+		ISelection selection = event.getSelection();
+		if ( !selection.isEmpty() ) {
+			IStructuredSelection str = (IStructuredSelection) selection;
+
+			Object sel = str.getFirstElement();
+			
+			if ( sel instanceof ISigilBundle ) {
+				BundleGraph graph = (BundleGraph) view.getBundlegraph();
+
+				ISigilBundle selected = (ISigilBundle) sel;
+				Set<ISigilBundle> connected = graph.getTargets(selected);
+
+				highlightLinks(graph, selected, connected);
+				highlightBundles(graph, selected, connected);
+			}
+			else if ( sel instanceof Link ) {
+				GraphConnection c = (GraphConnection) findGraphItem(sel);
+				if ( c != null ) {
+					c.unhighlight();
+					c.setHighlightColor(ColorConstants.blue);
+					c.highlight();
+				}
+			}
+		}
+		else {
+			// TODO clear highlights...
+		}
+	}
+
+	private void highlightBundles(BundleGraph graph, ISigilBundle selected, Set<ISigilBundle> connected) {
+		for ( ISigilBundle bundle : graph.getBundles() ) {
+			GraphNode node = (GraphNode) findGraphItem(bundle);
+			if ( node != null ) {
+				node.unhighlight();
+
+				if ( bundle == selected ) {
+					node.setHighlightColor(ColorConstants.yellow);
+					node.highlight();
+				}
+				else if ( view.isDisplayed(BundleResolverView.DEPENDENTS) ) {
+					if ( connected.contains(bundle) ) {
+						node.setHighlightColor(ColorConstants.lightBlue);
+						node.highlight();
+					}
+				}
+			}
+		}
+	}
+	
+	private void highlightLinks(BundleGraph graph, ISigilBundle selected, Set<ISigilBundle> connected) {
+		for ( Link l : graph.getLinks() ) {
+			GraphConnection c = (GraphConnection) findGraphItem(l);
+			if ( c != null ) {
+				c.unhighlight();
+
+				if ( view.isDisplayed(BundleResolverView.DEPENDENTS) ) {
+					if ( l.getSource() == selected && connected.contains( l.getTarget() ) ) {
+						c.setHighlightColor(ColorConstants.lightBlue);
+						c.highlight();
+					}
+				}
+			}
+		}
+	}
+
+	private GraphItem findGraphItem(Object l) {
+		try {
+			return view.getGraphViewer().findGraphItem(l);
+		}
+		catch (ArrayIndexOutOfBoundsException e) {
+			// temporary fix for issue 
+			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=242523
+			return null;
+		}
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraph.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraph.java
new file mode 100644
index 0000000..8fad30f
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraph.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+
+public class BundleGraph {
+
+	private HashMap<ISigilBundle, LinkedList<Link>> lookup = new HashMap<ISigilBundle, LinkedList<Link>>();
+	private LinkedList<Link> links = new LinkedList<Link>();
+	private HashSet<ISigilBundle> bundles = new HashSet<ISigilBundle>();
+	
+	public void startResolution(IModelElement requirement) {
+	}
+
+	public void endResolution(IModelElement requirement, ISigilBundle target) {
+		ISigilBundle source = requirement.getAncestor(ISigilBundle.class);
+
+		bundles.add(source);
+		bundles.add(target);
+
+		LinkedList<Link> links = lookup.get(source);
+
+		if ( links == null ) {
+			links = new LinkedList<Link>();
+			lookup.put(source, links);
+		}
+
+		Link l = null;
+		for ( Link c : links ) {
+			if ( c.getTarget() == target ) {
+				l = c;
+				break;
+			}
+		}
+
+		if ( l == null ) {
+			l = new Link(source, target);
+			links.add(l);
+			this.links.add(l);
+		}
+
+		l.addRequirement(requirement);
+	}
+	
+	public List<Link> getLinks() {
+		return links;
+	}
+
+	public Set<ISigilBundle> getBundles() {
+		return bundles;
+	}
+
+	public Set<ISigilBundle> getTargets(ISigilBundle bundle) {
+		HashSet<ISigilBundle> targets = new HashSet<ISigilBundle>();
+		
+		for ( Link l : getLinks(bundle) ) {
+			targets.add(l.getTarget());
+		}
+		
+		return targets;
+	}
+
+	public List<Link> getLinks(ISigilBundle selected) {
+		List<Link> l = lookup.get(selected);
+		return l == null ? Collections.<Link>emptyList() : l;
+	}
+
+	public List<Link> getDependentLinks(ISigilBundle bundle) {
+		ArrayList<Link> found = new ArrayList<Link>(links.size());
+		
+		for  (Link l : links) {
+			if ( l.getTarget() == bundle ) {
+				found.add( l );
+			}
+		}
+		
+		found.trimToSize();
+		
+		return found;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphContentProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphContentProvider.java
new file mode 100644
index 0000000..0c0547c
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphContentProvider.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.zest.core.viewers.IGraphContentProvider;
+
+public class BundleGraphContentProvider implements IGraphContentProvider {
+
+	public Object[] getElements(Object input) {
+		BundleGraph graph = (BundleGraph) input;
+		return graph.getLinks().toArray();
+	}
+
+	public Object getDestination(Object element) {
+		Link l = (Link) element;
+		return l.isSatisfied() ? l.getTarget() : new Link.Unsatisfied();
+	}
+
+	public Object getSource(Object element) {
+		Link l = (Link) element;
+		return l.getSource();
+	}
+
+	public void dispose() {
+		// TODO Auto-generated method stub
+		
+	}
+
+	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+		
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphLabelProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphLabelProvider.java
new file mode 100644
index 0000000..d48c253
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphLabelProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+public class BundleGraphLabelProvider extends LabelProvider {
+
+	private BundleResolverView view;
+	
+	public BundleGraphLabelProvider(BundleResolverView view) {
+		this.view = view;
+	}
+
+	@Override
+	public String getText(Object element) {
+		if ( element instanceof Link ) {
+			Link l = (Link) element;
+			if ( l.isSatisfied() ) {
+				if ( view.isDisplayed(BundleResolverView.LINK_LABELS) ) {
+					return view.getLinkText((Link) element);
+				}
+				else {
+					return "";
+				}
+			}
+			else {
+				return view.getLinkText((Link) element);				
+			}
+		}
+		else if ( element instanceof ISigilBundle ) {
+			ISigilBundle b = (ISigilBundle) element;
+			return b.getBundleInfo().getSymbolicName() + ": " + b.getBundleInfo().getVersion();
+		}
+		else if ( element instanceof Link.Unsatisfied ) {
+			return "unsatisfied";
+		}
+		else {
+			return "unknown:" + element;
+		}
+	}
+	
+	@Override
+	public Image getImage(Object element) {
+		Image result = null;
+		if ( element instanceof ISigilBundle ) {
+			result = SigilUI.cacheImage("icons/jar_obj.png", BundleGraphLabelProvider.class.getClassLoader());
+		}
+		else if ( element instanceof Link.Unsatisfied ) {
+			result = SigilUI.cacheImage("icons/error.gif", BundleGraphLabelProvider.class.getClassLoader());
+		}
+		
+		return result;
+	}	
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphViewFilter.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphViewFilter.java
new file mode 100644
index 0000000..4100ad8
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleGraphViewFilter.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+
+public class BundleGraphViewFilter extends ViewerFilter {
+
+	private BundleResolverView view;
+	
+	public BundleGraphViewFilter(BundleResolverView view) {
+		this.view = view;
+	}
+
+	@Override
+	public boolean select(Viewer viewer, Object parentElement, Object element) {
+		if ( !view.isDisplayed(BundleResolverView.LOCAL_LINKS) ) {
+			if ( element instanceof Link ) {
+				Link l = (Link) element;
+				return l.getSource() != l.getTarget();
+			}
+		}
+		if ( !view.isDisplayed(BundleResolverView.SATISFIED) ) {
+			if ( element instanceof Link ) {
+				Link l = (Link) element;
+				return !l.isSatisfied();
+			}
+			else if ( element instanceof ISigilBundle ) {
+				ISigilBundle bundle = (ISigilBundle) element;
+				for ( Link l : view.getBundlegraph().getLinks(bundle)) {
+					if ( !l.isSatisfied() ) {
+						return true;
+					}
+				}
+				return false;
+			}
+		}
+		
+		if ( !view.isDisplayed(BundleResolverView.UNSATISFIED) ) {
+			if ( element instanceof Link ) {
+				Link l = (Link) element;
+				return l.isSatisfied();
+			}
+			else if ( element instanceof Link.Unsatisfied ) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleResolverView.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleResolverView.java
new file mode 100644
index 0000000..91c0dcb
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/BundleResolverView.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.cauldron.sigil.repository.IRepositoryManager;
+import org.cauldron.sigil.repository.IResolutionMonitor;
+import org.cauldron.sigil.repository.ResolutionConfig;
+import org.cauldron.sigil.repository.ResolutionException;
+import org.cauldron.sigil.repository.ResolutionMonitorAdapter;
+import org.cauldron.sigil.ui.SigilUI;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.Label;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IPartService;
+import org.eclipse.ui.part.ViewPart;
+import org.eclipse.zest.core.viewers.GraphViewer;
+import org.eclipse.zest.core.widgets.Graph;
+import org.eclipse.zest.core.widgets.GraphConnection;
+import org.eclipse.zest.layouts.LayoutStyles;
+import org.eclipse.zest.layouts.algorithms.RadialLayoutAlgorithm;
+import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;
+
+public class BundleResolverView extends ViewPart {
+
+	private static final String SHOW_LINK_LABELS = "Show link labels";
+	private static final String HIDE_LINK_LABELS = "Hide link labels";
+	private static final String SHOW_LOCAL_LINKS = "Show local links";
+	private static final String HIDE_LOCAL_LINKS = "Hide local links";
+	private static final String SHOW_DEPENDENTS = "Show dependents";
+	private static final String HIDE_DEPENDENTS = "Hide dependents";
+	private static final String SHOW_SATISFIED = "Show satisfied bundles";
+	private static final String HIDE_SATISFIED = "Hide satisfied bundles";
+	private static final String SHOW_UNSATISFIED = "Show unsatisfied bundles";
+	private static final String HIDE_UNSATISFIED = "Hide unsatisfied bundles";
+	private static final String SHOW_OPTIONAL = "Show optional dependencies";
+	private static final String HIDE_OPTIONAL = "Hide optional dependencies";
+	
+	public static final String LINK_LABELS = "link.labels";
+	public static final String LOCAL_LINKS = "local.links";
+	public static final String DEPENDENTS = "dependents";
+	public static final String SATISFIED = "satisified";
+	public static final String UNSATISFIED = "unsatisfied";
+	public static final String OPTIONAL = "optional";
+
+	private GraphViewer viewer;
+	private IModelElement current;
+	private Job job;
+	private int lastX;
+	private int lastY;	
+	
+	private Map<String, Boolean> displayed = new HashMap<String, Boolean>();
+		
+	private class ToggleDisplayAction extends Action {	
+		private String key;
+		private String showMsg;
+		private String hideMsg;
+
+		ToggleDisplayAction(String key, String showMsg, String hideMsg) {
+			this.key = key;
+			this.showMsg = showMsg;
+			this.hideMsg = hideMsg;
+			setText(BundleResolverView.this.isDisplayed(key) ? hideMsg : showMsg);
+		}
+		
+		@Override
+		public void run() {
+			BundleResolverView.this.setDisplayed( key, !BundleResolverView.this.isDisplayed(key) );
+			setText(BundleResolverView.this.isDisplayed(key) ? hideMsg : showMsg);
+		}
+	}
+	
+	public void setInput(final IModelElement element) {
+		if ( current == null || !current.equals(element) ) {
+			SigilCore.log( "Set input " + element );
+			current = element;
+			redraw();
+		}
+	}
+	
+	public void setDisplayed(String key, boolean b) {
+		displayed.put(key, b);
+		
+		if ( key == DEPENDENTS ) {
+			int style = LayoutStyles.NO_LAYOUT_NODE_RESIZING;
+			viewer.setLayoutAlgorithm( b ? new TreeLayoutAlgorithm(style) : new RadialLayoutAlgorithm(style));
+			redraw();
+		}
+		else if ( key == OPTIONAL ) {
+			redraw();
+		}
+		else if ( key == SATISFIED || key == UNSATISFIED ) {
+			viewer.refresh();
+		}
+	}
+
+	public boolean isDisplayed(String key) {
+		return displayed.get(key);
+	}
+
+	@Override
+	public void setFocus() {
+	}
+	
+	@Override
+    public void createPartControl( Composite parent ) {
+		init();
+		createViewer(parent);
+		createListeners();
+		createMenu();
+    }
+	
+	private void init() {
+		displayed.put(LINK_LABELS, false);
+		displayed.put(LOCAL_LINKS, false);
+		displayed.put(DEPENDENTS, false);
+		displayed.put(OPTIONAL, false);
+		displayed.put(SATISFIED, true);
+		displayed.put(UNSATISFIED, true);
+	}
+
+	public BundleGraph getBundlegraph() {
+		return (BundleGraph) viewer.getInput();
+	}
+
+	GraphViewer getGraphViewer() {
+		return viewer;
+	}
+
+	String getLinkText(Link link) {
+		StringBuffer buf = new StringBuffer();
+
+		for ( IModelElement e : link.getRequirements() ) {
+			if ( buf.length() > 0 ) {
+				buf.append( "\n" );
+			}
+			if ( e instanceof IPackageImport ) {
+				IPackageImport pi = (IPackageImport) e;
+				buf.append( "import " + pi.getPackageName() + " : " + pi.getVersions() + ": " + (pi.isOptional() ? "optional" : "mandatory" ) );
+			}
+			else if ( e instanceof IRequiredBundle ) {
+				IRequiredBundle rb = (IRequiredBundle) e;
+				buf.append( "required bundle " + rb.getSymbolicName() + " : " + rb.getVersions() + ": " + (rb.isOptional() ? "optional" : "mandatory" ) );
+			}
+		}
+		
+		return buf.toString();
+	}	
+
+	private synchronized void redraw() {
+		final IModelElement element = current;
+		if ( job != null ) {
+			job.cancel();
+		}
+		
+		job = new Job("Resolving " + current) {
+			@Override
+			protected IStatus run(IProgressMonitor progress) {
+				try {
+					resolve(element, progress);
+					return Status.OK_STATUS;
+				} catch (CoreException e) {
+					return e.getStatus();
+				}
+			}
+		};
+		job.schedule();
+	}
+
+	private void resolve(IModelElement element, IProgressMonitor progress) throws CoreException {
+		final BundleGraph graph = new BundleGraph();
+		
+		IResolutionMonitor monitor = new ResolutionMonitorAdapter(progress) {
+			@Override
+			public void startResolution(IModelElement requirement) {
+				graph.startResolution(requirement);
+			}
+
+			@Override
+			public void endResolution(IModelElement requirement, ISigilBundle provider) {
+				graph.endResolution(requirement, provider);
+			}
+		};
+
+		ISigilProjectModel project = findProject(element);
+		IRepositoryManager repository = SigilCore.getRepositoryManager(project);
+		
+		int options = ResolutionConfig.IGNORE_ERRORS;
+		
+		if ( isDisplayed(DEPENDENTS) ) {
+			options |= ResolutionConfig.INCLUDE_DEPENDENTS;
+		}
+		if ( isDisplayed(OPTIONAL) ) {
+			options |= ResolutionConfig.INCLUDE_OPTIONAL;
+		}
+		
+		ResolutionConfig config = new ResolutionConfig(options);
+		
+		try {
+			repository.getBundleResolver().resolve(element, config, monitor);
+		} catch (ResolutionException e) {
+			throw SigilCore.newCoreException("Failed to resolve " + element, e);
+		}
+		
+		SigilUI.runInUI( new Runnable() {
+			public void run() {
+				viewer.setInput(graph);
+				addCustomUIElements();
+			}			
+		} );
+	}
+
+	private static ISigilProjectModel findProject(IModelElement element) {
+		if ( element == null ) {
+			return null;
+		}
+		if ( element instanceof ISigilProjectModel ) {
+			return (ISigilProjectModel) element;
+		}
+		
+		return element.getAncestor(ISigilProjectModel.class);
+	}
+
+	@SuppressWarnings("unchecked")
+	private void addCustomUIElements() {
+		if ( !isDisplayed(LINK_LABELS) ) {
+			for ( GraphConnection c : (List<GraphConnection>) viewer.getGraphControl().getConnections() ) {
+				if ( c.getData() instanceof Link ) {
+					c.setTooltip(buildToolTip((Link) c.getData()));
+				}
+			}
+		}
+	}
+
+	private IFigure buildToolTip(Link link) {
+		Label l = new Label();
+		l.setText(getLinkText(link));
+		return l;
+	}
+
+	private void createViewer(Composite parent) {
+		parent.setLayout( new FillLayout() );
+    	viewer = new GraphViewer(parent, SWT.H_SCROLL | SWT.V_SCROLL);
+    	IContentProvider contentProvider = new BundleGraphContentProvider();
+		viewer.setContentProvider(contentProvider);
+		viewer.setLabelProvider(new BundleGraphLabelProvider(this));
+		viewer.addFilter( new BundleGraphViewFilter(this) );
+		
+		
+		int style = LayoutStyles.NO_LAYOUT_NODE_RESIZING;
+		viewer.setLayoutAlgorithm( isDisplayed(DEPENDENTS) ? new TreeLayoutAlgorithm(style) : new RadialLayoutAlgorithm(style));
+		viewer.addSelectionChangedListener( new BundleConnectionHighlighter(this) );
+		viewer.setInput(new BundleGraph());
+	}
+
+	private void createMenu() {
+		IActionBars action = getViewSite().getActionBars(); 
+		action.getMenuManager().add(new ToggleDisplayAction( LINK_LABELS, SHOW_LINK_LABELS, HIDE_LINK_LABELS ));
+		action.getMenuManager().add(new ToggleDisplayAction( LOCAL_LINKS, SHOW_LOCAL_LINKS, HIDE_LOCAL_LINKS ));
+		action.getMenuManager().add(new ToggleDisplayAction( DEPENDENTS, SHOW_DEPENDENTS, HIDE_DEPENDENTS ));
+		action.getMenuManager().add(new ToggleDisplayAction( OPTIONAL, SHOW_OPTIONAL, HIDE_OPTIONAL ));
+		action.getMenuManager().add(new ToggleDisplayAction( SATISFIED, SHOW_SATISFIED, HIDE_SATISFIED ));
+		action.getMenuManager().add(new ToggleDisplayAction( UNSATISFIED, SHOW_UNSATISFIED, HIDE_UNSATISFIED ));
+		action.updateActionBars();
+	}
+
+	private void createListeners() {
+		IPartService ps = (IPartService) getViewSite().getService(IPartService.class);
+		ps.addPartListener( new EditorViewPartListener(this) );
+		viewer.getGraphControl().addControlListener( new ControlAdapter() {
+			@Override
+			public void controlResized(ControlEvent e) {
+				Graph g = (Graph) e.getSource();
+				int x = g.getSize().x;
+				int y = g.getSize().y;
+				if ( lastX != x || lastY != y ) {
+					lastX = x;
+					lastY = y;
+					redraw();
+				}
+			}
+		} );
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/EditorViewPartListener.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/EditorViewPartListener.java
new file mode 100644
index 0000000..78bceb7
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/EditorViewPartListener.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IPartListener2;
+import org.eclipse.ui.IWorkbenchPartReference;
+
+public class EditorViewPartListener implements IPartListener2 {
+
+	private BundleResolverView bundleResolverView;
+	
+	public EditorViewPartListener(BundleResolverView bundleResolverView) {
+		this.bundleResolverView = bundleResolverView;
+	}
+
+	public void partActivated(IWorkbenchPartReference partRef) {
+		checkRef(partRef);
+	}
+
+	public void partBroughtToTop(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	public void partClosed(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	public void partDeactivated(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	public void partHidden(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	public void partInputChanged(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	public void partOpened(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	public void partVisible(IWorkbenchPartReference partRef) {
+		// no action
+	}
+
+	private void checkRef(IWorkbenchPartReference partRef) {
+		IEditorPart editor = partRef.getPage().getActiveEditor();
+		if ( editor != null ) {
+			IEditorInput input = editor.getEditorInput();
+			if ( input instanceof IFileEditorInput ) {
+				IFileEditorInput f = (IFileEditorInput) input;
+				IProject project = f.getFile().getProject();
+				try {
+					ISigilProjectModel model = SigilCore.create(project);
+					if ( model != null ) {
+						bundleResolverView.setInput(model);
+					}
+				} catch (CoreException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/Link.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/Link.java
new file mode 100644
index 0000000..35591a4
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/views/resolution/Link.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.views.resolution;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.cauldron.sigil.model.IModelElement;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.osgi.IPackageImport;
+import org.cauldron.sigil.model.osgi.IRequiredBundle;
+
+public class Link {	
+	public static class Unsatisfied {
+
+	}
+
+	private ISigilBundle source;
+	private ISigilBundle target;
+	
+	private LinkedList<IModelElement> requirements = new LinkedList<IModelElement>();
+	private static final Comparator<IModelElement> comparator = new Comparator<IModelElement>() {
+
+		public int compare(IModelElement o1, IModelElement o2) {
+			if (o1 instanceof IRequiredBundle) {
+				if ( o2 instanceof IRequiredBundle) {
+					return compareBundles( (IRequiredBundle) o1, (IRequiredBundle) o2 );
+				}
+				else {
+					return -1;
+				}
+			}
+			else {
+				if ( o2 instanceof IRequiredBundle ) {
+					return 1;
+				}
+				else {
+					return compareNonBundles( o1, o2 );
+				}
+			}
+		}
+
+		private int compareNonBundles(IModelElement o1, IModelElement o2) {
+			if (o1 instanceof IPackageImport) {
+				if ( o2 instanceof IPackageImport) {
+					return compareImports( (IPackageImport) o1, (IPackageImport) o2 );
+				}
+				else {
+					return -1;
+				}
+			}
+			else {
+				if ( o2 instanceof IPackageImport ) {
+					return 1;
+				}
+				else {
+					return 0;
+				}
+			}
+		}
+
+		private int compareImports(IPackageImport o1, IPackageImport o2) {
+			return o1.getPackageName().compareTo( o2.getPackageName() );
+		}
+
+		private int compareBundles(IRequiredBundle o1, IRequiredBundle o2) {
+			return o1.getSymbolicName().compareTo( o2.getSymbolicName() );
+		}
+		
+	};
+	
+	public Link(ISigilBundle source, ISigilBundle target) {
+		this.source = source;
+		this.target = target;
+	}
+
+	public ISigilBundle getSource() {
+		return source;
+	}
+	
+	public ISigilBundle getTarget() {
+		return target;
+	}
+	
+	public boolean isSatisfied() {
+		return target != null;
+	}
+
+	public void addRequirement(IModelElement requirement) {
+		requirements.add(requirement);
+		Collections.sort(requirements, comparator);
+	}
+	
+	public String toString() {
+		return "Link[" + source + "->" + target + "]";
+	}
+
+	public List<IModelElement> getRequirements() {
+		return requirements;
+	}
+
+	public boolean isOptional() {
+		for ( IModelElement e : requirements ) {
+			if ( e instanceof IPackageImport ) {
+				IPackageImport pi = (IPackageImport) e;
+				if ( !pi.isOptional() ) {
+					return false;
+				}
+			}
+			else if ( e instanceof IRequiredBundle ) {
+				IRequiredBundle rb = (IRequiredBundle) e;
+				if ( !rb.isOptional() ) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/SigilNewResourceWizard.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/SigilNewResourceWizard.java
new file mode 100644
index 0000000..bc4a39d
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/SigilNewResourceWizard.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.INewWizard;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;
+
+/**
+ * @author dave
+ *
+ */
+public abstract class SigilNewResourceWizard extends BasicNewResourceWizard implements INewWizard {
+
+    protected void selectRevealAndShow(IFile file) {
+        selectAndReveal(file);
+    
+        // Open editor on new file.
+        IWorkbenchWindow dw = getWorkbench().getActiveWorkbenchWindow();
+        try {
+            if (dw != null) {
+                IWorkbenchPage page = dw.getActivePage();
+                if (page != null) {
+                    IDE.openEditor(page, file, true);
+                }
+            }
+        }
+        catch (PartInitException e) {
+            MessageDialog.openError(Display.getCurrent().getActiveShell(), "Initialisation error",
+                    "Failed to open " + file);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/WorkspaceContentProvider.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/WorkspaceContentProvider.java
new file mode 100644
index 0000000..cbab33b
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/WorkspaceContentProvider.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+public class WorkspaceContentProvider implements ITreeContentProvider {
+	
+	private final boolean includeNonSigil;
+	private final boolean includeClosed;
+
+	public WorkspaceContentProvider(boolean includeNonSigil, boolean includeClosed) {
+		this.includeNonSigil = includeNonSigil;
+		this.includeClosed = includeClosed;
+	}
+
+	public Object[] getChildren(Object parentElement) {
+		Object[] result = null;
+		
+		if(parentElement instanceof IWorkspace) {
+			IProject[] projects = ((IWorkspace) parentElement).getRoot().getProjects();
+			if(includeNonSigil && includeClosed) {
+				result = projects;
+			} else {
+				List<IProject> includedProjects = new ArrayList<IProject>(projects.length);
+				for (IProject project : projects) {
+					if(!includeClosed && !project.isOpen()) {
+						continue;
+					}
+					
+					if(!includeNonSigil) {
+						try {
+							if(project.getNature(SigilCore.NATURE_ID) == null) {
+								continue;
+							}
+						} catch (CoreException e) {
+							continue;
+						}
+					}
+					
+					includedProjects.add(project);
+				}
+				result = includedProjects.toArray(new IProject[includedProjects.size()]);
+			}
+		} else if(parentElement instanceof IContainer) {
+			try {
+				IResource[] members = ((IContainer) parentElement).members();
+				List<IResource> children = new ArrayList<IResource>(members.length);
+				for (int i = 0; i < members.length; i++) {
+				    if (members[i].getType() != IResource.FILE) {
+				        children.add(members[i]);
+				    }
+				}
+				result = children.toArray(new IResource[children.size()]);
+			} catch (CoreException e) {
+				// Shouldn't happen
+			}
+		}
+		
+		return result;
+	}
+
+	public Object getParent(Object element) {
+		if(element instanceof IResource) {
+			return ((IResource) element).getParent();
+		}
+		return null;
+	}
+
+	public boolean hasChildren(Object element) {
+		return (element instanceof IContainer) && ((IContainer) element).isAccessible();
+	}
+
+	public Object[] getElements(Object inputElement) {
+		return getChildren(inputElement);
+	}
+
+	public void dispose() {
+	}
+
+	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizard.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizard.java
new file mode 100644
index 0000000..8f926bf
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizard.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard.project;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.ui.wizard.SigilNewResourceWizard;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExecutableExtension;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;
+
+/**
+ * @author dave
+ *
+ */
+public class SigilProjectWizard extends SigilNewResourceWizard implements IExecutableExtension {
+
+    private SigilProjectWizardFirstPage firstPage;
+    private SigilProjectWizardSecondPage secondPage;
+    
+    private String name;
+    
+    public static final IPath SIGIL_PROJECT_PATH = new Path( SigilCore.SIGIL_PROJECT_FILE );
+	private IConfigurationElement config;
+    
+    public void init(IWorkbench workbench, IStructuredSelection currentSelection) {
+        super.init(workbench, currentSelection);
+
+        firstPage = new SigilProjectWizardFirstPage();
+        firstPage.setInitialProjectName(name);
+        secondPage = new SigilProjectWizardSecondPage(firstPage);
+        
+        addPage(firstPage);
+        addPage(secondPage);
+    }
+    
+    private void finishPage(IProgressMonitor monitor) throws CoreException, InterruptedException {
+        secondPage.performFinish(monitor);
+        
+        IProject newProject = firstPage.getProjectHandle();
+        
+        if ( newProject != null && newProject.exists() ) {
+            IFile file = newProject.getFile( SigilProjectWizard.SIGIL_PROJECT_PATH );
+            
+            selectRevealAndShow( file ); 
+           
+            new Job("Check OSGi Install" ) {
+				@Override
+				protected IStatus run(IProgressMonitor monitor) {
+                    // prompt for osgi home if not already set.
+            		SigilCore.getInstallManager().getDefaultInstall();
+            		return Status.OK_STATUS;
+				}
+            }.schedule();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jface.wizard.Wizard#performFinish()
+     */
+    @Override
+    public boolean performFinish() {
+        IWorkspace workspace = ResourcesPlugin.getWorkspace();
+        
+        IWorkspaceRunnable op= new IWorkspaceRunnable() {
+            public void run(IProgressMonitor monitor) throws CoreException {
+                try {
+                    finishPage(monitor);
+                } catch (InterruptedException e) {
+                    throw new OperationCanceledException(e.getMessage());
+                }
+            }
+        };
+        
+        try {
+            workspace.run(op, Job.getJobManager().createProgressGroup());
+        }
+        catch (CoreException e) {
+            SigilCore.error( "Failed to complete project wizard", e);
+            return false;
+        }
+        
+        BasicNewProjectResourceWizard.updatePerspective(config);
+        return true;
+    }
+
+	public void setName(String name) {
+		this.name = name;
+	}
+	
+	public String getName() {
+		return name;
+	}
+    
+    @Override
+    public boolean performCancel() {
+    	secondPage.performCancel();
+    	return super.performCancel();
+    }
+
+	public void setInitializationData(IConfigurationElement config, String propertyName, Object data)
+			throws CoreException {
+		this.config = config;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizardFirstPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizardFirstPage.java
new file mode 100644
index 0000000..78eaf19
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizardFirstPage.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard.project;
+
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.WizardNewProjectCreationPage;
+import org.osgi.framework.Version;
+
+/**
+ * @author dave
+ *
+ */
+public class SigilProjectWizardFirstPage extends WizardNewProjectCreationPage {
+
+	private volatile String description = "";
+	private volatile Version version = new Version(1, 0, 0);
+	private volatile String vendor = "";
+	private volatile String name = "";
+	
+	private Text txtDescription;
+    private Text txtVersion;
+	private Text txtVendor;
+	private Text txtName;
+
+	public SigilProjectWizardFirstPage() {
+        super("newSigilProjectPage");
+        setTitle( "Sigil Project" );
+        setDescription( "Create a new Sigil project" );
+    }
+
+    public boolean isInWorkspace() {
+        IWorkspace workspace = ResourcesPlugin.getWorkspace();
+        
+        IPath defaultDefaultLocation = workspace.getRoot().getLocation();
+        
+        return defaultDefaultLocation.isPrefixOf( getLocationPath() );
+    }
+    
+    @Override
+    public boolean isPageComplete() {
+    	boolean result = super.isPageComplete();
+    	return result;
+    }
+    
+    @Override
+    public void createControl(Composite parent) {
+    	FieldDecoration infoDecor = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION);
+    	
+    	// Create controls
+    	super.createControl(parent);
+    	Composite control = (Composite) getControl();
+    	
+    	Group grpProjectSettings = new Group(control, SWT.NONE);
+    	grpProjectSettings.setText("Project Settings");
+    	
+    	new Label(grpProjectSettings, SWT.NONE).setText("Version:");
+    	txtVersion = new Text(grpProjectSettings, SWT.BORDER);
+    	
+    	new Label(grpProjectSettings, SWT.NONE).setText("Name:");
+    	txtName = new Text(grpProjectSettings, SWT.BORDER);
+    	
+    	ControlDecoration txtNameDecor = new ControlDecoration(txtName, SWT.LEFT | SWT.CENTER);
+    	txtNameDecor.setImage(infoDecor.getImage());
+    	txtNameDecor.setDescriptionText("Defines a human-readable name for the bundle");
+    	
+    	new Label(grpProjectSettings, SWT.NONE).setText("Description:");
+    	txtDescription = new Text(grpProjectSettings, SWT.BORDER);
+    	
+    	ControlDecoration txtDescDecor = new ControlDecoration(txtDescription, SWT.LEFT | SWT.CENTER);
+    	txtDescDecor.setImage(infoDecor.getImage());
+    	txtDescDecor.setDescriptionText("Defines a short human-readable description for the bundle");
+    	
+    	new Label(grpProjectSettings, SWT.NONE).setText("Provider:");
+    	txtVendor = new Text(grpProjectSettings, SWT.BORDER);
+    	
+    	ControlDecoration txtVendorDecor = new ControlDecoration(txtVendor, SWT.LEFT | SWT.CENTER);
+    	txtVendorDecor.setImage(infoDecor.getImage());
+    	txtVendorDecor.setDescriptionText("The name of the company, organisation or individual providing the bundle");
+    	    	
+    	// Set values
+    	txtDescription.setText(description);
+    	txtVersion.setText(version.toString());
+    	txtVendor.setText(vendor);
+    	txtName.setText(name);
+    	
+    	// Add listeners
+    	ModifyListener txtModListener = new ModifyListener() {
+			public void modifyText(ModifyEvent e) {
+				description = txtDescription.getText();
+				vendor = txtVendor.getText();
+				name = txtName.getText();
+				
+				validateSettings();
+			}
+    	};
+    	txtDescription.addModifyListener(txtModListener);
+    	txtVersion.addModifyListener(txtModListener);
+    	txtVendor.addModifyListener(txtModListener);
+    	txtName.addModifyListener(txtModListener);
+    	
+    	// Layout
+    	control.setLayout(new GridLayout());
+    	grpProjectSettings.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+    	grpProjectSettings.setLayout(new GridLayout(2, false));
+    	txtDescription.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+    	txtVersion.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+    	txtVendor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+    	txtName.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+    }
+
+    private void validateSettings() {
+    	try {
+			version = new Version(txtVersion.getText());
+		} catch (IllegalArgumentException e) {
+			version = null;
+			setErrorMessage("Invalid version");
+			setPageComplete(false);
+			return;
+		}
+		
+		setErrorMessage(null);
+		setPageComplete(true);
+	}
+
+	public Version getVersion() {
+		return version;
+	}
+
+	public String getVendor() {
+		return vendor;
+	}
+	
+	public String getDescription() {
+		return description;
+	}
+	
+	public String getName() {
+		return name;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizardSecondPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizardSecondPage.java
new file mode 100644
index 0000000..3b2592d
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/project/SigilProjectWizardSecondPage.java
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard.project;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.cauldron.sigil.SigilCore;
+import org.cauldron.sigil.model.eclipse.ISigilBundle;
+import org.cauldron.sigil.model.project.ISigilProjectModel;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.ui.PreferenceConstants;
+import org.eclipse.jdt.ui.wizards.JavaCapabilityConfigurationPage;
+import org.osgi.framework.Version;
+
+/**
+ * @author dave
+ *
+ */
+public class SigilProjectWizardSecondPage extends JavaCapabilityConfigurationPage {
+    
+    private SigilProjectWizardFirstPage firstPage;
+    private IProject currentProject;
+    private URI currentProjectLocation;
+    private boolean created;
+    
+    public SigilProjectWizardSecondPage(SigilProjectWizardFirstPage firstPage) {
+        this.firstPage = firstPage;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+    	super.setVisible(visible);
+        if (visible) {
+            changeToNewProject();
+        } else {
+            removeProject();
+        }
+    }
+    
+    @Override
+    protected boolean useNewSourcePage() {
+        return true;
+    }
+    
+    protected void performFinish(IProgressMonitor monitor) throws CoreException, InterruptedException {
+    	changeToNewProject();
+        updateProject(monitor);
+    }
+    
+    private void changeToNewProject() {
+    	if ( !created ) {
+            IWorkspace workspace = ResourcesPlugin.getWorkspace();
+            
+            IWorkspaceRunnable op= new IWorkspaceRunnable() {
+                public void run(IProgressMonitor monitor) throws CoreException {
+                    try {
+                        updateProject(monitor);
+                    } catch (InterruptedException e) {
+                        throw new OperationCanceledException(e.getMessage());
+                    }
+                }
+            };
+            
+            try {
+                workspace.run(op, Job.getJobManager().createProgressGroup());
+                setErrorMessage(null);
+                setPageComplete(true);
+                created = true;
+            }
+            catch (CoreException e) {
+            	SigilCore.error("Failed to run workspace job", e);
+            }        
+    	}
+    }
+    
+    private void removeProject() {
+        if (currentProject == null || !currentProject.exists()) {
+            return;
+        }
+        
+        IWorkspaceRunnable op= new IWorkspaceRunnable() {
+            public void run(IProgressMonitor monitor) throws CoreException {
+                doRemoveProject(monitor);
+            }
+        };
+    
+        IWorkspace workspace = ResourcesPlugin.getWorkspace();
+        
+        try {
+            workspace.run(op, Job.getJobManager().createProgressGroup());
+        }
+        catch (CoreException e) {
+        	SigilCore.error("Failed to run workspace job", e);
+        }
+        finally {
+        	created = false;
+        }
+    }
+    
+    private void updateProject(IProgressMonitor monitor) throws CoreException, InterruptedException {
+        currentProject = firstPage.getProjectHandle();
+        currentProjectLocation= getProjectLocationURI();
+        
+        String description = firstPage.getDescription();
+        Version projectVersion = firstPage.getVersion();
+        String vendor = firstPage.getVendor();
+        String name = firstPage.getName();
+        
+        createProject( currentProject, currentProjectLocation, monitor);
+
+        IPath src = createSourcePath();
+        
+        IPath output = getOutputLocation();
+        
+        if ( output.segmentCount() == 0 ) {
+        	output = new Path( currentProject.getName() ).append( "build" ).append( "classes" );
+        }
+        
+        IClasspathEntry[] entries = getProjectClassPath(src);
+        
+    	SigilCore.makeSigilProject(currentProject, monitor);
+    	
+        init(JavaCore.create(currentProject), output.makeRelative(), entries, false);
+
+        configureJavaProject(new SubProgressMonitor(monitor, 3));
+        
+        configureSigilProject( currentProject, description, projectVersion, vendor, name, src, monitor );
+    }
+    
+	private IPath createSourcePath() throws CoreException {
+        IPath projectPath = currentProject.getFullPath();
+        IPath src = new Path( "src" );
+        IFolder f = currentProject.getFolder( src );
+        if ( !f.getLocation().toFile().exists() ) {
+        	f.create(true, true, null);
+        }
+        
+        return projectPath.append(src);
+	}
+
+	final void doRemoveProject(IProgressMonitor monitor) throws CoreException {
+        final boolean noProgressMonitor= (currentProjectLocation == null); // inside workspace
+        
+        if (monitor == null || noProgressMonitor) {
+            monitor= new NullProgressMonitor();
+        }
+        monitor.beginTask("Remove project", 3); 
+        try {
+            try {
+                boolean removeContent= currentProject.isSynchronized(IResource.DEPTH_INFINITE);
+                currentProject.delete(removeContent, false, new SubProgressMonitor(monitor, 2));
+                
+            } finally {
+            }
+        } finally {
+            monitor.done();
+            currentProject= null;
+        }        
+    }
+        
+    private IClasspathEntry[] getProjectClassPath(IPath src) throws CoreException {
+        List<IClasspathEntry> cpEntries= new ArrayList<IClasspathEntry>();
+        cpEntries.add(JavaCore.newSourceEntry(src));
+        cpEntries.addAll(Arrays.asList(getDefaultClasspathEntry()));
+        cpEntries.add(JavaCore.newContainerEntry(new Path(SigilCore.CLASSPATH_CONTAINER_PATH)));
+        IClasspathEntry[] entries= cpEntries.toArray(new IClasspathEntry[cpEntries.size()]);
+        
+        return entries;
+    }
+    
+    private IClasspathEntry[] getDefaultClasspathEntry() {
+        IClasspathEntry[] defaultJRELibrary= PreferenceConstants.getDefaultJRELibrary();
+        /*String compliance= firstPage.getCompilerCompliance();
+        IPath jreContainerPath= new Path(JavaRuntime.JRE_CONTAINER);
+        if (compliance == null || defaultJRELibrary.length > 1 || !jreContainerPath.isPrefixOf(defaultJRELibrary[0].getPath())) {
+            // use default
+            return defaultJRELibrary;
+        }
+        IVMInstall inst= firstPage.getJVM();
+        if (inst != null) {
+            IPath newPath= jreContainerPath.append(inst.getVMInstallType().getId()).append(inst.getName());
+            return new IClasspathEntry[] { JavaCore.newContainerEntry(newPath) };
+        }*/
+        return defaultJRELibrary;
+    }
+    
+    
+    private void configureSigilProject( IProject project, String description, Version projectVersion, String vendorName, String bundleName, IPath src, IProgressMonitor monitor ) throws CoreException {
+        ISigilProjectModel sigil = SigilCore.create(project);
+        IClasspathEntry cp = JavaCore.newSourceEntry(src);
+        String encodedClasspath = sigil.getJavaModel().encodeClasspathEntry(cp );
+        
+        ISigilBundle bundle = sigil.getBundle();
+        bundle.addClasspathEntry(encodedClasspath);
+        
+        if(description != null) {
+        	bundle.getBundleInfo().setDescription(description);
+        }
+		if(projectVersion != null) {
+        	bundle.setVersion(projectVersion);
+        }
+		if(vendorName != null) {
+			bundle.getBundleInfo().setVendor(vendorName);
+		}
+		if(bundleName != null) {
+			bundle.getBundleInfo().setName(bundleName);
+		}
+        sigil.save(monitor);
+    }
+    
+    
+    private URI getProjectLocationURI() throws CoreException {
+        if (firstPage.isInWorkspace()) {
+            return null;
+        }
+        return firstPage.getLocationURI();
+    }
+    
+    @Override
+    public boolean isPageComplete() {
+    	boolean result = super.isPageComplete();
+    	return result;
+    }
+    
+    protected void performCancel() {
+    	if(currentProject != null) {
+    		removeProject();
+    	}
+    }
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/repository/RepositoryWizard.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/repository/RepositoryWizard.java
new file mode 100644
index 0000000..c2403e9
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/repository/RepositoryWizard.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard.repository;
+
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.Wizard;
+
+public class RepositoryWizard extends Wizard {
+	
+	private IRepositoryModel model;
+	
+	@Override
+	public boolean performFinish() {
+		for ( IWizardPage page : getPages() ) {
+			if ( page instanceof RepositoryWizardPage ) {
+				RepositoryWizardPage rwp = (RepositoryWizardPage) page;
+				rwp.storeFields();
+			}
+		}
+		return true;
+	}
+	
+	public IRepositoryModel getModel() {
+		return model;
+	}
+
+	public void init(IRepositoryModel model) {
+		this.model = model;
+	}
+}
diff --git a/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/repository/RepositoryWizardPage.java b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/repository/RepositoryWizardPage.java
new file mode 100644
index 0000000..9a22aa4
--- /dev/null
+++ b/sigil/org.cauldron.sigil.ui/src/org/cauldron/sigil/ui/wizard/repository/RepositoryWizardPage.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 org.cauldron.sigil.ui.wizard.repository;
+
+import java.util.ArrayList;
+
+import org.cauldron.sigil.model.repository.IRepositoryModel;
+import org.eclipse.jface.preference.FieldEditor;
+import org.eclipse.jface.preference.StringFieldEditor;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+public abstract class RepositoryWizardPage extends WizardPage {
+
+	private StringFieldEditor nameEditor;
+	private ArrayList<FieldEditor> editors = new ArrayList<FieldEditor>();
+	private RepositoryWizard wizard;
+	
+	protected RepositoryWizardPage(String pageName, RepositoryWizard parent) {
+		super(pageName);
+		setTitle(pageName);
+		this.wizard = parent;
+	}
+	
+	public abstract void createFieldEditors();
+
+	public void addField(FieldEditor editor) {
+		editors.add( editor );
+	}
+	
+	public void createControl(Composite parent) {
+		Composite control = new Composite(parent, SWT.NONE);
+		setControl(control);
+		
+		if ( getModel().getType().isDynamic() ) {
+			nameEditor = new StringFieldEditor("name", "Name:", control);
+			nameEditor.setStringValue(getModel().getName());
+			nameEditor.getTextControl(getFieldEditorParent()).addModifyListener( new ModifyListener() {
+				public void modifyText(ModifyEvent e) {
+					checkPageComplete();
+				}
+			});
+		}
+		
+		createFieldEditors();
+
+		int cols = nameEditor == null ? 0 : nameEditor.getNumberOfControls();
+		for ( FieldEditor e : editors ) {
+			cols = Math.max(cols, e.getNumberOfControls());
+		}
+		
+		control.setLayout( new GridLayout(cols, false) );
+		
+		if ( nameEditor != null ) {
+			nameEditor.fillIntoGrid(getFieldEditorParent(), cols);
+		}
+
+		for ( FieldEditor e : editors ) {
+			e.fillIntoGrid(getFieldEditorParent(), cols);
+			e.setPreferenceStore(getModel().getPreferences());
+			e.load();
+		}
+		
+		checkPageComplete();
+	}
+
+	protected void checkPageComplete() {
+		if ( nameEditor != null ) {
+			setPageComplete(nameEditor.getStringValue().length() > 0);
+		}
+	}
+
+	public IRepositoryModel getModel() {
+		return wizard.getModel();
+	}
+
+	protected Composite getFieldEditorParent() {
+		return (Composite) getControl();
+	}
+
+	public void storeFields() {
+		getModel().setName(nameEditor.getStringValue());
+		for ( FieldEditor e : editors ) {
+			e.store();
+		}
+	}
+	
+}