[gnome-builder] project-selector: add New Project and Open Project dialog



commit 2b3081886709debba935dbf75b147b55828e8566
Author: Christian Hergert <christian hergert me>
Date:   Wed Apr 8 00:25:17 2015 -0700

    project-selector: add New Project and Open Project dialog
    
    Note that this doesn't implement "Create New Project" from templates (that
    will come later). This implements new project from existing sources.
    
    Currently supported are autotools projects (configure.ac) as well as
    selecting a basic directory. You'll need to use the later for projects
    if you are using GNOME Builder for building something other than GNOME
    applications using automake (our charter).
    
    We'll probably need to tune the project miners for various systems once
    we see how it handles in the wild. It tries to avoid mining remote shares
    and various directories we know shouldn't matter. However, everyone has
    very unique settings and we are likely to run into something new.
    
    Clone only supports Git today (the only VCS we have implemented). If you
    care about another VCS, please offer your time to help implement it. We'll
    need to abstract the new-project-dialog, but that should be done anyway.
    
    Anyway, lots to do, and this is the first step.

 data/css/builder.Adwaita.css              |    4 +
 data/gtk/menus.ui                         |   11 +
 data/ui/gb-new-project-dialog.ui          |  464 ++++++++++++++++++++
 data/ui/gb-projects-dialog.ui             |  123 ++++++
 data/ui/gb-recent-project-row.ui          |   81 ++++
 src/app/gb-application-actions.c          |   60 +++-
 src/app/gb-application.c                  |   65 ++--
 src/app/gb-application.h                  |    2 +
 src/dialogs/gb-new-project-dialog.c       |  665 +++++++++++++++++++++++++++++
 src/dialogs/gb-new-project-dialog.h       |   32 ++
 src/dialogs/gb-projects-dialog.c          |  574 +++++++++++++++++++++++++
 src/dialogs/gb-projects-dialog.h          |   34 ++
 src/dialogs/gb-recent-project-row.c       |  280 ++++++++++++
 src/dialogs/gb-recent-project-row.h       |   47 ++
 src/gnome-builder.mk                      |    7 +
 src/resources/gnome-builder.gresource.xml |    5 +-
 16 files changed, 2415 insertions(+), 39 deletions(-)
---
diff --git a/data/css/builder.Adwaita.css b/data/css/builder.Adwaita.css
index bd22798..185ca31 100644
--- a/data/css/builder.Adwaita.css
+++ b/data/css/builder.Adwaita.css
@@ -169,3 +169,7 @@ GbViewStack:not(.focused) GtkBox.header.notebook {
 GbEditorWorkspace > GtkPaned > GtkBox > GtkBox.header.notebook {
     border-bottom: 1px solid @borders;
 }
+
+GbNewProjectDialog GtkFileChooserButton.linked-on-right .button {
+    border-radius: 3px 0 0 3px;
+}
diff --git a/data/gtk/menus.ui b/data/gtk/menus.ui
index 5e9143a..1bd72c4 100644
--- a/data/gtk/menus.ui
+++ b/data/gtk/menus.ui
@@ -3,6 +3,17 @@
   <!-- interface-requires gtk+ 3.0 -->
   <menu id="app-menu">
     <section>
+      <attribute name="id">project-section</attribute>
+      <item>
+        <attribute name="label" translatable="yes">_New Project</attribute>
+        <attribute name="action">app.new-project</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_Open Project</attribute>
+        <attribute name="action">app.open-project</attribute>
+      </item>
+    </section>
+    <section>
       <attribute name="id">preferences-section</attribute>
       <item>
         <attribute name="label" translatable="yes">_Preferences</attribute>
diff --git a/data/ui/gb-new-project-dialog.ui b/data/ui/gb-new-project-dialog.ui
new file mode 100644
index 0000000..2abacda
--- /dev/null
+++ b/data/ui/gb-new-project-dialog.ui
@@ -0,0 +1,464 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.15 -->
+  <template class="GbNewProjectDialog" parent="GtkWindow">
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="header_bar">
+        <property name="show-close-button">false</property>
+        <property name="title" translatable="yes">New Project</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkButton" id="back_button">
+            <property name="visible">true</property>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">go-previous-symbolic</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel_button">
+            <property name="label" translatable="yes">_Cancel</property>
+            <property name="use-underline">true</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="text-button"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="create_button">
+            <property name="label" translatable="yes">_Continue</property>
+            <property name="use-underline">true</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="text-button"/>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="expand">true</property>
+        <property name="homogeneous">true</property>
+        <property name="transition-type">slide-left-right</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox" id="page_open_project">
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkBox">
+                <property name="expand">true</property>
+                <property name="visible">true</property>
+                <child type="center">
+                  <object class="GbScrolledWindow">
+                    <property name="halign">center</property>
+                    <property name="hexpand">false</property>
+                    <property name="max-content-height">600</property>
+                    <property name="max-content-width">500</property>
+                    <property name="min-content-height">20</property>
+                    <property name="min-content-width">400</property>
+                    <property name="shadow-type">in</property>
+                    <property name="valign">center</property>
+                    <property name="vexpand">false</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkListBox" id="open_list_box">
+                        <property name="selection-mode">none</property>
+                        <property name="visible">true</property>
+                        <child>
+                          <object class="GtkListBoxRow" id="row_open_local">
+                            <property name="visible">true</property>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="orientation">horizontal</property>
+                                <property name="margin">12</property>
+                                <property name="spacing">12</property>
+                                <property name="visible">true</property>
+                                <child>
+                                  <object class="GtkImage">
+                                    <property name="icon-name">folder</property>
+                                    <property name="pixel-size">32</property>
+                                    <property name="visible">true</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="label" translatable="yes">From existing project on this 
computer</property>
+                                    <property name="hexpand">true</property>
+                                    <property name="visible">true</property>
+                                    <property name="xalign">0.0</property>
+                                    <style>
+                                      <class name="dim-label"/>
+                                    </style>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkListBoxRow" id="row_clone_remote">
+                            <property name="visible">true</property>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="orientation">horizontal</property>
+                                <property name="margin">12</property>
+                                <property name="spacing">12</property>
+                                <property name="visible">true</property>
+                                <child>
+                                  <object class="GtkImage">
+                                    <property name="icon-name">builder-vcs-git</property>
+                                    <property name="pixel-size">32</property>
+                                    <property name="visible">true</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="label" translatable="yes">From remote source code 
repository</property>
+                                    <property name="hexpand">true</property>
+                                    <property name="visible">true</property>
+                                    <property name="xalign">0.0</property>
+                                    <style>
+                                      <class name="dim-label"/>
+                                    </style>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox" id="page_new_project">
+            <property name="orientation">horizontal</property>
+            <property name="expand">true</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <child type="center">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <property name="halign">center</property>
+                <property name="hexpand">false</property>
+                <property name="valign">center</property>
+                <property name="vexpand">false</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkEntry" id="name_entry">
+                    <property name="hexpand">true</property>
+                    <property name="width-chars">35</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkFileChooserButton" id="location_button">
+                    <property name="action">select-folder</property>
+                    <property name="width-chars">35</property>
+                    <property name="hexpand">true</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuButton" id="version_button">
+                    <property name="hexpand">true</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuButton" id="language_button">
+                    <property name="hexpand">true</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuButton" id="pattern_button">
+                    <property name="hexpand">true</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <property name="hexpand">true</property>
+                <property name="vexpand">true</property>
+                <property name="valign">center</property>
+                <property name="halign">end</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkLabel" id="name_label">
+                    <property name="label" translatable="yes">Name</property>
+                    <property name="visible">true</property>
+                    <property name="xalign">1.0</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="location_label">
+                    <property name="label" translatable="yes">Location</property>
+                    <property name="visible">true</property>
+                    <property name="xalign">1.0</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="version_label">
+                    <property name="label" translatable="yes">GNOME Version</property>
+                    <property name="visible">true</property>
+                    <property name="xalign">1.0</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="language_label">
+                    <property name="label" translatable="yes">Language</property>
+                    <property name="visible">true</property>
+                    <property name="xalign">1.0</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="pattern_label">
+                    <property name="label" translatable="yes">Pattern</property>
+                    <property name="visible">true</property>
+                    <property name="xalign">1.0</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="pack-type">start</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkFileChooserWidget" id="file_chooser">
+            <property name="action">open</property>
+            <property name="local-only">false</property>
+            <property name="visible">true</property>
+            <property name="width-request">800</property>
+            <property name="height-request">500</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkOverlay" id="page_clone_remote">
+            <property name="visible">true</property>
+            <child type="overlay">
+              <object class="GtkProgressBar" id="clone_progress">
+                <property name="valign">start</property>
+                <property name="fraction">0.0</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="osd"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="expand">true</property>
+                <property name="spacing">12</property>
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="valign">center</property>
+                    <property name="vexpand">true</property>
+                    <property name="icon-name">builder-vcs-git</property>
+                    <property name="pixel-size">128</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child type="center">
+                  <object class="GtkBox">
+                    <property name="orientation">horizontal</property>
+                    <property name="expand">true</property>
+                    <property name="spacing">12</property>
+                    <property name="visible">true</property>
+                    <child type="center">
+                      <object class="GtkBox">
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <property name="halign">center</property>
+                        <property name="hexpand">false</property>
+                        <property name="valign">center</property>
+                        <property name="vexpand">false</property>
+                        <property name="visible">true</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">true</property>
+                            <style>
+                              <class name="linked"/>
+                            </style>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">true</property>
+                                <property name="label" translatable="yes">Git</property>
+                                <style>
+                                  <class name="button"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="clone_uri_entry">
+                                <property name="hexpand">true</property>
+                                <property name="width-chars">35</property>
+                                <property name="placeholder-text" translatable="yes">user 
host:repository.git</property>
+                                <property name="tooltip-text" translatable="yes">Enter the URL of your 
project's source code repository.</property>
+                                <property name="visible">true</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">true</property>
+                            <style>
+                              <class name="linked"/>
+                            </style>
+                            <child>
+                              <object class="GtkFileChooserButton" id="clone_location_button">
+                                <property name="action">select-folder</property>
+                                <property name="hexpand">true</property>
+                                <property name="local-only">true</property>
+                                <property name="visible">true</property>
+                                <style>
+                                  <class name="linked-on-right"/>
+                                </style>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="clone_location_entry">
+                                <property name="hexpand">true</property>
+                                <property name="placeholder-text" translatable="yes">Directory</property>
+                                <property name="visible">true</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <property name="hexpand">true</property>
+                        <property name="vexpand">true</property>
+                        <property name="valign">center</property>
+                        <property name="halign">end</property>
+                        <property name="visible">true</property>
+                        <child>
+                          <object class="GtkLabel" id="clone_uri_label">
+                            <property name="label" translatable="yes">Repository</property>
+                            <property name="visible">true</property>
+                            <property name="xalign">1.0</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="clone_location_label">
+                            <property name="label" translatable="yes">Location</property>
+                            <property name="visible">true</property>
+                            <property name="xalign">1.0</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="clone_error_label">
+                    <property name="valign">start</property>
+                    <property name="vexpand">true</property>
+                    <property name="visible">false</property>
+                    <style>
+                      <class name="error-label"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="pack-type">end</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSpinner" id="clone_spinner">
+                    <property name="vexpand">false</property>
+                    <property name="active">true</property>
+                    <property name="margin">24</property>
+                    <property name="width-request">24</property>
+                    <property name="height-request">24</property>
+                  </object>
+                  <packing>
+                    <property name="pack-type">end</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">vertical</property>
+    <widgets>
+      <widget name="name_entry"/>
+      <widget name="name_label"/>
+      <widget name="location_button"/>
+      <widget name="location_label"/>
+      <widget name="version_button"/>
+      <widget name="version_label"/>
+      <widget name="language_button"/>
+      <widget name="language_label"/>
+      <widget name="pattern_button"/>
+      <widget name="pattern_label"/>
+      <widget name="clone_location_label"/>
+      <widget name="clone_location_button"/>
+      <widget name="clone_uri_label"/>
+      <widget name="clone_uri_entry"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/data/ui/gb-projects-dialog.ui b/data/ui/gb-projects-dialog.ui
new file mode 100644
index 0000000..a7924e9
--- /dev/null
+++ b/data/ui/gb-projects-dialog.ui
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.15 -->
+  <template class="GbProjectsDialog" parent="GtkApplicationWindow">
+    <property name="icon-name">builder</property>
+    <property name="default-width">800</property>
+    <property name="default-height">600</property>
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="header_bar">
+        <property name="show-close-button">true</property>
+        <property name="title" translatable="yes">Select Project</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkButton" id="new_button">
+            <property name="label" translatable="yes">New</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="text-button"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel_button">
+            <property name="label" translatable="yes">_Cancel</property>
+            <property name="use-underline">true</property>
+            <property name="visible">false</property>
+            <style>
+              <class name="text-button"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkToggleButton" id="select_button">
+            <property name="visible">true</property>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">object-select-symbolic</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkToggleButton" id="search_button">
+            <property name="visible">true</property>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">system-search-symbolic</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="vexpand">true</property>
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkSearchBar" id="search_bar">
+            <property name="search-mode-enabled">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkSearchEntry" id="search_entry">
+                <property name="width-chars">40</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GbScrolledWindow">
+            <property name="expand">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkListBox" id="listbox">
+                <property name="visible">true</property>
+                <property name="selection-mode">none</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkActionBar" id="action_bar">
+            <child>
+              <object class="GtkButton" id="delete_button">
+                <property name="label" translatable="yes">Delete</property>
+                <property name="sensitive">false</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="destructive-action"/>
+                </style>
+              </object>
+              <packing>
+                <property name="pack-type">start</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/gb-recent-project-row.ui b/data/ui/gb-recent-project-row.ui
new file mode 100644
index 0000000..9402dc0
--- /dev/null
+++ b/data/ui/gb-recent-project-row.ui
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.15 -->
+  <template class="GbRecentProjectRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkBox">
+        <property name="margin">12</property>
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkRevealer" id="revealer">
+            <property name="reveal-child">false</property>
+            <property name="transition-type">slide-right</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkCheckButton" id="check_button">
+                <property name="valign">center</property>
+                <property name="vexpand">false</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="icon-name">folder</property>
+            <property name="margin-end">12</property>
+            <property name="margin-start">12</property>
+            <property name="pixel-size">64</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="valign">center</property>
+            <property name="vexpand">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="name_label">
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+                <property name="xalign">0.0</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="location_label">
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+                <property name="xalign">0.0</property>
+                <attributes>
+                  <attribute name="scale" value="0.833333"/>
+                </attributes>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="date_label">
+            <property name="margin">12</property>
+            <property name="valign">center</property>
+            <property name="visible">true</property>
+            <property name="xalign">1.0f</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkArrow">
+            <property name="arrow-type">right</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/app/gb-application-actions.c b/src/app/gb-application-actions.c
index 476a460..a4b2428 100644
--- a/src/app/gb-application-actions.c
+++ b/src/app/gb-application-actions.c
@@ -27,6 +27,7 @@
 #include "gb-application-actions.h"
 #include "gb-application-credits.h"
 #include "gb-application-private.h"
+#include "gb-new-project-dialog.h"
 #include "gb-support.h"
 #include "gb-workbench.h"
 
@@ -171,11 +172,62 @@ gb_application_actions_about (GSimpleAction *action,
   gtk_window_present (GTK_WINDOW (dialog));
 }
 
+static void
+gb_application_actions_open_project (GSimpleAction *action,
+                                     GVariant      *variant,
+                                     gpointer       user_data)
+{
+  GbApplication *self = user_data;
+
+  g_assert (GB_IS_APPLICATION (self));
+
+  gb_application_show_projects_window (self);
+}
+
+static void
+gb_application_actions__window_open_project (GbApplication      *self,
+                                             GFile              *project_file,
+                                             GbNewProjectDialog *window)
+{
+  g_assert (GB_IS_APPLICATION (self));
+  g_assert (G_IS_FILE (project_file));
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (window));
+
+  gb_application_open_project (self, project_file, NULL);
+  gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+static void
+gb_application_actions_new_project (GSimpleAction *action,
+                                    GVariant      *variant,
+                                    gpointer       user_data)
+{
+  GbApplication *self = user_data;
+  GtkWindow *window;
+
+  g_assert (GB_IS_APPLICATION (self));
+
+  window = g_object_new (GB_TYPE_NEW_PROJECT_DIALOG,
+                         "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG,
+                         "window-position", GTK_WIN_POS_CENTER,
+                         NULL);
+
+  g_signal_connect_object (window,
+                           "open-project",
+                           G_CALLBACK (gb_application_actions__window_open_project),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_window_present (window);
+}
+
 static const GActionEntry GbApplicationActions[] = {
-  { "about",       gb_application_actions_about },
-  { "preferences", gb_application_actions_preferences },
-  { "quit",        gb_application_actions_quit },
-  { "support",     gb_application_actions_support },
+  { "about",        gb_application_actions_about },
+  { "open-project", gb_application_actions_open_project },
+  { "new-project",  gb_application_actions_new_project },
+  { "preferences",  gb_application_actions_preferences },
+  { "quit",         gb_application_actions_quit },
+  { "support",      gb_application_actions_support },
 };
 
 void
diff --git a/src/app/gb-application.c b/src/app/gb-application.c
index 4aa2847..59c459e 100644
--- a/src/app/gb-application.c
+++ b/src/app/gb-application.c
@@ -32,6 +32,7 @@
 #include "gb-editor-document.h"
 #include "gb-editor-workspace.h"
 #include "gb-glib.h"
+#include "gb-projects-dialog.h"
 #include "gb-resources.h"
 #include "gb-workbench.h"
 
@@ -480,49 +481,45 @@ gb_application_open (GApplication   *application,
   IDE_EXIT;
 }
 
-static void
-gb_application_activate (GApplication *application)
+void
+gb_application_show_projects_window (GbApplication *self)
 {
-  GbApplication *self = (GbApplication *)application;
-  const gchar *home_dir;
-  g_autofree gchar *current_dir = NULL;
-  g_autofree gchar *target_dir = NULL;
-  g_autoptr(GFile) directory = NULL;
-  g_autoptr(GTask) task = NULL;
-
-  g_return_if_fail (GB_IS_APPLICATION (self));
-
-  /*
-   * FIXME:
-   *
-   * This is a stop gap until we get the project selection window in place.
-   * That will allow us to select previous projects and such.
-   */
+  GbProjectsDialog *window;
+  GtkRequisition req;
+  GList *windows;
 
-  home_dir = g_get_home_dir ();
-  current_dir = g_get_current_dir ();
+  g_assert (GB_IS_APPLICATION (self));
 
-  if (g_str_equal (home_dir, current_dir))
-    target_dir = g_build_filename (home_dir, "Projects", NULL);
-  else
-    target_dir = g_strdup (current_dir);
+  windows = gtk_application_get_windows (GTK_APPLICATION (self));
 
-  if (!g_file_test (target_dir, G_FILE_TEST_EXISTS))
+  for (; windows; windows = windows->next)
     {
-      g_free (target_dir);
-      target_dir = g_strdup (home_dir);
+      if (GB_IS_PROJECTS_DIALOG (windows->data))
+        {
+          gtk_window_present (windows->data);
+          return;
+        }
     }
 
-  directory = g_file_new_for_path (target_dir);
+  get_default_size (&req);
 
-  task = g_task_new (self, NULL, NULL, NULL);
-  g_task_set_task_data (task, g_ptr_array_new (), (GDestroyNotify)g_ptr_array_unref);
+  window = g_object_new (GB_TYPE_PROJECTS_DIALOG,
+                         "application", self,
+                         "default-width", req.width,
+                         "default-height", req.height,
+                         NULL);
+  gtk_window_maximize (GTK_WINDOW (window));
+  gtk_window_present (GTK_WINDOW (window));
+}
 
-  ide_context_new_async (directory,
-                         NULL,
-                         gb_application__context_new_cb,
-                         g_object_ref (task));
-  g_application_hold (application);
+static void
+gb_application_activate (GApplication *application)
+{
+  GbApplication *self = (GbApplication *)application;
+
+  g_assert (GB_IS_APPLICATION (self));
+
+  gb_application_show_projects_window (self);
 }
 
 static void
diff --git a/src/app/gb-application.h b/src/app/gb-application.h
index d66df1b..757541b 100644
--- a/src/app/gb-application.h
+++ b/src/app/gb-application.h
@@ -30,6 +30,8 @@ G_DECLARE_FINAL_TYPE (GbApplication, gb_application, GB, APPLICATION, GtkApplica
 void gb_application_open_project         (GbApplication *self,
                                           GFile         *file,
                                           GPtrArray     *additional_files);
+void gb_application_show_projects_window (GbApplication *self);
+
 G_END_DECLS
 
 #endif /* GB_APPLICATION_H */
diff --git a/src/dialogs/gb-new-project-dialog.c b/src/dialogs/gb-new-project-dialog.c
new file mode 100644
index 0000000..f2731c4
--- /dev/null
+++ b/src/dialogs/gb-new-project-dialog.c
@@ -0,0 +1,665 @@
+/* gb-new-project-dialog.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "gb-new-project-dialog"
+
+#include <glib/gi18n.h>
+#include <libgit2-glib/ggit.h>
+
+#include "gb-new-project-dialog.h"
+#include "gb-string.h"
+#include "gb-widget.h"
+
+#define ANIMATION_DURATION_MSEC 250
+
+struct _GbNewProjectDialog
+{
+  GtkDialog             parent_instance;
+
+  gdouble               progress_fraction;
+
+  GtkButton            *back_button;
+  GtkButton            *cancel_button;
+  GtkLabel             *clone_error_label;
+  GtkFileChooserWidget *clone_location_button;
+  GtkEntry             *clone_location_entry;
+  GtkProgressBar       *clone_progress;
+  GtkSpinner           *clone_spinner;
+  GtkEntry             *clone_uri_entry;
+  GtkButton            *create_button;
+  GtkFileChooserWidget *file_chooser;
+  GtkHeaderBar         *header_bar;
+  GtkListBox           *open_list_box;
+  GtkListBoxRow        *row_clone_remote;
+  GtkListBoxRow        *row_open_local;
+  GtkBox               *page_clone_remote;
+  GtkBox               *page_open_project;
+  GtkStack             *stack;
+};
+
+typedef struct
+{
+  gchar *uri;
+  GFile *location;
+} CloneRequest;
+
+G_DEFINE_TYPE (GbNewProjectDialog, gb_new_project_dialog, GTK_TYPE_WINDOW)
+
+enum {
+  PROP_0,
+  LAST_PROP
+};
+
+enum {
+  BACK,
+  CLOSE,
+  OPEN_PROJECT,
+  LAST_SIGNAL
+};
+
+static guint gSignals [LAST_SIGNAL];
+
+static void
+clone_request_free (gpointer data)
+{
+  CloneRequest *req = data;
+
+  if (req)
+    {
+      g_free (req->uri);
+      g_clear_object (&req->location);
+      g_free (req);
+    }
+}
+
+static CloneRequest *
+clone_request_new (const gchar *uri,
+                   GFile       *location)
+{
+  CloneRequest *req;
+
+  g_assert (uri);
+  g_assert (location);
+
+  req = g_new0 (CloneRequest, 1);
+  req->uri = g_strdup (uri);
+  req->location = g_object_ref (location);
+
+  return req;
+}
+
+static void
+gb_new_project_dialog_back (GbNewProjectDialog *self)
+{
+  GtkWidget *child;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  child = gtk_stack_get_visible_child (self->stack);
+
+  if (child == GTK_WIDGET (self->page_open_project))
+    g_signal_emit_by_name (self, "close");
+
+  if (gtk_widget_get_sensitive (GTK_WIDGET (self->back_button)))
+    gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->page_open_project));
+}
+
+static gboolean
+open_after_timeout (gpointer user_data)
+{
+  GbNewProjectDialog *self;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  self = g_task_get_source_object (task);
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->back_button), TRUE);
+  gtk_widget_hide (GTK_WIDGET (self->clone_spinner));
+
+  file = g_task_propagate_pointer (task, &error);
+
+  if (error)
+    {
+      g_warning ("%s", error->message);
+      gtk_label_set_label (self->clone_error_label, error->message);
+      gtk_widget_show (GTK_WIDGET (self->clone_error_label));
+    }
+  else
+    {
+      g_signal_emit (self, gSignals [OPEN_PROJECT], 0, file);
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gb_new_project_dialog__clone_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  GbNewProjectDialog *self = (GbNewProjectDialog *)object;
+  GTask *task = (GTask *)result;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (G_IS_TASK (task));
+
+  ide_object_animate_full (self->clone_progress,
+                           IDE_ANIMATION_EASE_IN_OUT_QUAD,
+                           ANIMATION_DURATION_MSEC,
+                           NULL,
+                           (GDestroyNotify)gb_widget_fade_hide,
+                           self->clone_progress,
+                           "fraction", 1.0,
+                           NULL);
+
+  /*
+   * Wait for a second so animations can complete before opening
+   * the project. Otherwise, it's pretty jarring to the user.
+   */
+  g_timeout_add (ANIMATION_DURATION_MSEC, open_after_timeout, g_object_ref (task));
+}
+
+static void
+gb_new_project_dialog__clone_worker (GTask        *task,
+                                     gpointer      source_object,
+                                     gpointer      task_data,
+                                     GCancellable *cancellable)
+{
+  GbNewProjectDialog *self = source_object;
+  GgitRepository *repository;
+  g_autoptr(GFile) workdir = NULL;
+  CloneRequest *req = task_data;
+  GgitCloneOptions *clone_options;
+  GgitRemoteCallbacks *callbacks;
+  IdeProgress *progress;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (source_object));
+  g_assert (req != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  clone_options = ggit_clone_options_new ();
+  ggit_clone_options_set_is_bare (clone_options, FALSE);
+  ggit_clone_options_set_checkout_branch (clone_options, "master");
+
+  callbacks = g_object_new (IDE_TYPE_GIT_REMOTE_CALLBACKS, NULL);
+  progress = ide_git_remote_callbacks_get_progress (IDE_GIT_REMOTE_CALLBACKS (callbacks));
+  g_object_bind_property (progress, "fraction",
+                          self->clone_progress, "fraction",
+                          G_BINDING_SYNC_CREATE);
+  ggit_clone_options_set_remote_callbacks (clone_options, callbacks);
+
+  repository = ggit_repository_clone (req->uri, req->location, clone_options, &error);
+
+  g_object_unref (callbacks);
+  ggit_clone_options_free (clone_options);
+
+  if (repository == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  workdir = ggit_repository_get_workdir (repository);
+  g_task_return_pointer (task, g_object_ref (workdir), g_object_unref);
+
+  g_object_unref (repository);
+}
+
+static void
+gb_new_project_dialog_begin_clone (GbNewProjectDialog *self)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GFile) location = NULL;
+  g_autoptr(GFile) child = NULL;
+  CloneRequest *req;
+  const gchar *uri;
+  const gchar *child_name;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->back_button), FALSE);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), FALSE);
+  gtk_widget_hide (GTK_WIDGET (self->clone_error_label));
+  gtk_widget_show (GTK_WIDGET (self->clone_spinner));
+
+  uri = gtk_entry_get_text (self->clone_uri_entry);
+  child_name = gtk_entry_get_text (self->clone_location_entry);
+  location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (self->clone_location_button));
+
+  if (child_name != NULL)
+    {
+      child = g_file_get_child (location, child_name);
+      req = clone_request_new (uri, child);
+    }
+  else
+    {
+      req = clone_request_new (uri, location);
+    }
+
+  task = g_task_new (self, NULL, gb_new_project_dialog__clone_cb, self);
+  g_task_set_task_data (task, req, clone_request_free);
+  g_task_run_in_thread (task, gb_new_project_dialog__clone_worker);
+}
+
+static void
+gb_new_project_dialog__back_button_clicked (GbNewProjectDialog *self,
+                                            GtkButton          *back_button)
+{
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_BUTTON (back_button));
+
+  gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->page_open_project));
+}
+
+static void
+gb_new_project_dialog__cancel_button_clicked (GbNewProjectDialog *self,
+                                              GtkButton          *cancel_button)
+{
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_BUTTON (cancel_button));
+
+  gtk_window_close (GTK_WINDOW (self));
+}
+
+static void
+gb_new_project_dialog__create_button_clicked (GbNewProjectDialog *self,
+                                              GtkButton          *cancel_button)
+{
+  GtkWidget *visible_child;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_BUTTON (cancel_button));
+
+  visible_child = gtk_stack_get_visible_child (self->stack);
+
+  if (visible_child == GTK_WIDGET (self->file_chooser))
+    {
+      g_autoptr(GFile) file = NULL;
+
+      file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (self->file_chooser));
+      if (file != NULL)
+        g_signal_emit (self, gSignals [OPEN_PROJECT], 0, file);
+    }
+  else if (visible_child == GTK_WIDGET (self->page_clone_remote))
+    {
+      gb_new_project_dialog_begin_clone (self);
+    }
+}
+
+static void
+gb_new_project_dialog__open_list_box_row_activated (GbNewProjectDialog *self,
+                                                    GtkListBoxRow      *row,
+                                                    GtkListBox         *list_box)
+{
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  if (row == self->row_open_local)
+    gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->file_chooser));
+  else if (row == self->row_clone_remote)
+    gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->page_clone_remote));
+}
+
+static void
+gb_new_project_dialog__stack_notify_visible_child (GbNewProjectDialog *self,
+                                                   GParamSpec         *pspec,
+                                                   GtkStack           *stack)
+{
+  GtkWidget *visible_child;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_STACK (stack));
+
+  visible_child = gtk_stack_get_visible_child (stack);
+
+  if (visible_child == GTK_WIDGET (self->file_chooser))
+    {
+      gtk_widget_hide (GTK_WIDGET (self->cancel_button));
+      gtk_widget_show (GTK_WIDGET (self->back_button));
+      gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), FALSE);
+      gtk_header_bar_set_title (self->header_bar, _("Select Project File"));
+    }
+  else if (visible_child == GTK_WIDGET (self->page_open_project))
+    {
+      gtk_widget_hide (GTK_WIDGET (self->back_button));
+      gtk_widget_show (GTK_WIDGET (self->cancel_button));
+      gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), FALSE);
+      gtk_header_bar_set_title (self->header_bar, _("New Project"));
+    }
+  else if (visible_child == GTK_WIDGET (self->page_clone_remote))
+    {
+      g_autofree gchar *text= NULL;
+      GtkClipboard *clipboard;
+
+      clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self), GDK_SELECTION_CLIPBOARD);
+      text = gtk_clipboard_wait_for_text (clipboard);
+      if (!ide_str_empty0 (text) &&
+          (strstr (text, "://") || strchr (text, '@')) &&
+          ide_vcs_uri_is_valid (text))
+        gtk_entry_set_text (self->clone_uri_entry, text);
+
+      gtk_widget_hide (GTK_WIDGET (self->cancel_button));
+      gtk_widget_show (GTK_WIDGET (self->back_button));
+      gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), FALSE);
+      gtk_header_bar_set_title (self->header_bar, _("Clone Repository"));
+
+      g_signal_emit_by_name (G_OBJECT (self->clone_uri_entry), "changed");
+    }
+}
+
+static GList *
+gb_new_project_dialog_create_filters (GbNewProjectDialog *self)
+{
+  GtkFileFilter *filter;
+  GList *list = NULL;
+
+  /*
+   * TODO: These should come from extension points in libide.
+   */
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  /* autotools filter (IdeAutotoolsBuildSystem) */
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Autotools Project (configure.ac)"));
+  gtk_file_filter_add_pattern (filter, "configure.ac");
+  list = g_list_append (list, filter);
+
+  /* any directory filter (IdeDirectoryBuildSystem) */
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Any Directory"));
+  gtk_file_filter_add_pattern (filter, "*");
+  list = g_list_append (list, filter);
+
+  return list;
+}
+
+static void
+gb_new_project_dialog__file_chooser_selection_changed (GbNewProjectDialog *self,
+                                                       GtkFileChooser     *file_chooser)
+{
+  g_autoptr(GFile) file = NULL;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_FILE_CHOOSER (file_chooser));
+
+  file = gtk_file_chooser_get_file (file_chooser);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), !!file);
+}
+
+static void
+gb_new_project_dialog__file_chooser_file_activated (GbNewProjectDialog *self,
+                                                    GtkFileChooser     *file_chooser)
+{
+  g_autoptr(GFile) file = NULL;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_FILE_CHOOSER (file_chooser));
+
+  file = gtk_file_chooser_get_file (file_chooser);
+  if (file != NULL)
+    g_signal_emit (self, gSignals [OPEN_PROJECT], 0, file);
+}
+
+static void
+gb_new_project_dialog_close (GbNewProjectDialog *self)
+{
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  gtk_window_close (GTK_WINDOW (self));
+}
+
+static void
+gb_new_project_dialog__open_list_box_header_func (GtkListBoxRow *row,
+                                                  GtkListBoxRow *before,
+                                                  gpointer       user_data)
+{
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (!before || GTK_IS_LIST_BOX_ROW (before));
+
+  if (before != NULL)
+    {
+      GtkWidget *header;
+
+      header = g_object_new (GTK_TYPE_SEPARATOR,
+                             "orientation", GTK_ORIENTATION_HORIZONTAL,
+                             "visible", TRUE,
+                             NULL);
+      gtk_list_box_row_set_header (row, header);
+    }
+}
+
+static void
+clone_adjust_sensitivity (GbNewProjectDialog *self)
+{
+  const gchar *uristr;
+  gboolean is_valid;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  uristr = gtk_entry_get_text (self->clone_uri_entry);
+  is_valid = ide_vcs_uri_is_valid (uristr);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), is_valid);
+}
+
+static void
+gb_new_project_dialog__clone_location_entry_changed (GbNewProjectDialog *self,
+                                                     GtkEntry           *entry)
+{
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  clone_adjust_sensitivity (self);
+}
+
+static void
+gb_new_project_dialog__clone_uri_entry_changed (GbNewProjectDialog *self,
+                                                GtkEntry           *entry)
+{
+  g_autoptr(IdeVcsUri) uri = NULL;
+  const gchar *text;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  clone_adjust_sensitivity (self);
+
+  text = gtk_entry_get_text (entry);
+  uri = ide_vcs_uri_new (text);
+
+  if (uri != NULL)
+    {
+      const gchar *path;
+      gchar *name = NULL;
+
+      g_object_set (self->clone_uri_entry,
+                    "secondary-icon-name", NULL,
+                    "secondary-icon-tooltip-text", NULL,
+                    NULL);
+
+      path = ide_vcs_uri_get_path (uri);
+
+      if (path != NULL)
+        {
+          name = g_path_get_basename (path);
+          if (g_str_has_suffix (name, ".git"))
+            *(strrchr (name, '.')) = '\0';
+          if (!g_str_equal (name, "/"))
+            gtk_entry_set_text (self->clone_location_entry, name);
+          g_free (name);
+        }
+    }
+  else
+    {
+      g_object_set (self->clone_uri_entry,
+                    "secondary-icon-name", "dialog-warning-symbolic",
+                    "secondary-icon-tooltip-text", _("A valid Git URL is required"),
+                    NULL);
+    }
+}
+
+static void
+gb_new_project_dialog_class_init (GbNewProjectDialogClass *klass)
+{
+  GtkBindingSet *binding_set;
+
+  gSignals [BACK] =
+    g_signal_new_class_handler ("back",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                G_CALLBACK (gb_new_project_dialog_back),
+                                NULL, NULL,
+                                g_cclosure_marshal_VOID__VOID,
+                                G_TYPE_NONE,
+                                0);
+
+  gSignals [CLOSE] =
+    g_signal_new_class_handler ("close",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                G_CALLBACK (gb_new_project_dialog_close),
+                                NULL, NULL,
+                                g_cclosure_marshal_VOID__VOID,
+                                G_TYPE_NONE,
+                                0);
+
+  gSignals [OPEN_PROJECT] =
+    g_signal_new ("open-project",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_FILE);
+
+  binding_set = gtk_binding_set_by_class (klass);
+
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_Escape,
+                                0,
+                                "back",
+                                0);
+
+  GB_WIDGET_CLASS_TEMPLATE (klass, "gb-new-project-dialog.ui");
+
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, back_button);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, cancel_button);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_error_label);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_location_button);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_location_entry);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_progress);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_spinner);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_uri_entry);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, create_button);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, file_chooser);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, header_bar);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, open_list_box);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, page_clone_remote);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, page_open_project);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, row_open_local);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, row_clone_remote);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, stack);
+}
+
+static void
+gb_new_project_dialog_init (GbNewProjectDialog *self)
+{
+  g_autofree gchar *path = NULL;
+  GList *iter;
+  GList *filters;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  filters = gb_new_project_dialog_create_filters (self);
+  for (iter = filters; iter; iter = iter->next)
+    gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (self->file_chooser), iter->data);
+  g_list_free (filters);
+
+  g_signal_connect_object (self->stack,
+                           "notify::visible-child",
+                           G_CALLBACK (gb_new_project_dialog__stack_notify_visible_child),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->back_button,
+                           "clicked",
+                           G_CALLBACK (gb_new_project_dialog__back_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->cancel_button,
+                           "clicked",
+                           G_CALLBACK (gb_new_project_dialog__cancel_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->clone_uri_entry,
+                           "changed",
+                           G_CALLBACK (gb_new_project_dialog__clone_uri_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->clone_location_entry,
+                           "changed",
+                           G_CALLBACK (gb_new_project_dialog__clone_location_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->create_button,
+                           "clicked",
+                           G_CALLBACK (gb_new_project_dialog__create_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->open_list_box,
+                           "row-activated",
+                           G_CALLBACK (gb_new_project_dialog__open_list_box_row_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->file_chooser,
+                           "selection-changed",
+                           G_CALLBACK (gb_new_project_dialog__file_chooser_selection_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->file_chooser,
+                           "file-activated",
+                           G_CALLBACK (gb_new_project_dialog__file_chooser_file_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_list_box_set_header_func (self->open_list_box,
+                                gb_new_project_dialog__open_list_box_header_func,
+                                NULL, NULL);
+
+  path = g_build_filename (g_get_home_dir (), Q_("Directory|Projects"), NULL);
+  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (self->file_chooser), path);
+  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (self->clone_location_button), path);
+
+  g_object_notify (G_OBJECT (self->stack), "visible-child");
+}
diff --git a/src/dialogs/gb-new-project-dialog.h b/src/dialogs/gb-new-project-dialog.h
new file mode 100644
index 0000000..b46ed5d
--- /dev/null
+++ b/src/dialogs/gb-new-project-dialog.h
@@ -0,0 +1,32 @@
+/* gb-new-project-dialog.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_NEW_PROJECT_DIALOG_H
+#define GB_NEW_PROJECT_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_NEW_PROJECT_DIALOG (gb_new_project_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GbNewProjectDialog, gb_new_project_dialog, GB, NEW_PROJECT_DIALOG, GtkWindow)
+
+G_END_DECLS
+
+#endif /* GB_NEW_PROJECT_DIALOG_H */
diff --git a/src/dialogs/gb-projects-dialog.c b/src/dialogs/gb-projects-dialog.c
new file mode 100644
index 0000000..92db43e
--- /dev/null
+++ b/src/dialogs/gb-projects-dialog.c
@@ -0,0 +1,574 @@
+/* gb-projects-dialog.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#define G_LOG_DOMAIN "gb-projects-dialog"
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "gb-application.h"
+#include "gb-editor-document.h"
+#include "gb-glib.h"
+#include "gb-new-project-dialog.h"
+#include "gb-projects-dialog.h"
+#include "gb-recent-project-row.h"
+#include "gb-scrolled-window.h"
+#include "gb-string.h"
+#include "gb-widget.h"
+#include "gb-workbench.h"
+
+struct _GbProjectsDialog
+{
+  GtkApplicationWindow parent_instance;
+
+  IdeRecentProjects *recent_projects;
+  IdePatternSpec    *search_pattern;
+
+  GtkActionBar      *action_bar;
+  GtkButton         *cancel_button;
+  GtkButton         *delete_button;
+  GtkHeaderBar      *header_bar;
+  GtkListBox        *listbox;
+  GtkButton         *new_button;
+  GtkSearchBar      *search_bar;
+  GtkToggleButton   *search_button;
+  GtkSearchEntry    *search_entry;
+  GtkToggleButton   *select_button;
+};
+
+G_DEFINE_TYPE (GbProjectsDialog, gb_projects_dialog, GTK_TYPE_APPLICATION_WINDOW)
+
+static void
+gb_projects_dialog_update_delete_sensitivity (GbProjectsDialog *self)
+{
+  gboolean selected = FALSE;
+  GList *children;
+  GList *iter;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+
+  children = gtk_container_get_children (GTK_CONTAINER (self->listbox));
+
+  for (iter = children; !selected && iter; iter = iter->next)
+    {
+      if (gb_recent_project_row_get_selected (iter->data))
+        selected = TRUE;
+    }
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->delete_button), selected);
+
+  g_list_free (children);
+}
+
+static void
+gb_projects_dialog__listbox_row_activated_cb (GbProjectsDialog *self,
+                                              GtkListBoxRow    *row,
+                                              GtkListBox       *listbox)
+{
+  IdeProjectInfo *project_info;
+  GApplication *app;
+  GFile *file;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GB_IS_RECENT_PROJECT_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (listbox));
+
+  /*
+   * If we are in selection mode, just select the row instead.
+   */
+  if (gtk_toggle_button_get_active (self->select_button))
+    {
+      gboolean selected;
+
+      selected = !gb_recent_project_row_get_selected (GB_RECENT_PROJECT_ROW (row));
+      gb_recent_project_row_set_selected (GB_RECENT_PROJECT_ROW (row), selected);
+      return;
+    }
+
+  project_info = gb_recent_project_row_get_project_info (GB_RECENT_PROJECT_ROW (row));
+  g_assert (IDE_IS_PROJECT_INFO (project_info));
+
+  app = g_application_get_default ();
+  file = ide_project_info_get_file (project_info);
+
+  gb_application_open_project (GB_APPLICATION (app), file, NULL);
+
+  gtk_widget_destroy (GTK_WIDGET (self));
+}
+
+static void
+gb_projects_dialog__recent_projects_added (GbProjectsDialog  *self,
+                                           IdeProjectInfo    *project_info,
+                                           IdeRecentProjects *recent_projects)
+{
+  GtkWidget *row;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (IDE_IS_PROJECT_INFO (project_info));
+  g_assert (IDE_IS_RECENT_PROJECTS (recent_projects));
+
+  row = g_object_new (GB_TYPE_RECENT_PROJECT_ROW,
+                      "project-info", project_info,
+                      "visible", TRUE,
+                      NULL);
+  g_signal_connect_object (row,
+                           "notify::selected",
+                           G_CALLBACK (gb_projects_dialog_update_delete_sensitivity),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_object_bind_property (self->select_button, "active", row, "selection-mode",
+                          G_BINDING_SYNC_CREATE);
+  gtk_container_add (GTK_CONTAINER (self->listbox), row);
+}
+
+static void
+gb_projects_dialog__recent_projects_discover_cb (GObject      *object,
+                                                 GAsyncResult *result,
+                                                 gpointer      user_data)
+{
+  IdeRecentProjects *recent_projects = (IdeRecentProjects *)object;
+  g_autoptr(GbProjectsDialog) self = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RECENT_PROJECTS (recent_projects));
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+
+  if (!ide_recent_projects_discover_finish (recent_projects, result, &error))
+    {
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+}
+
+static void
+gb_projects_dialog__listbox_header_cb (GtkListBoxRow *row,
+                                       GtkListBoxRow *before,
+                                       gpointer       user_data)
+{
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (!before || GTK_IS_LIST_BOX_ROW (before));
+
+  if (before != NULL)
+    {
+      GtkWidget *header;
+
+      header =  g_object_new (GTK_TYPE_SEPARATOR,
+                              "orientation", GTK_ORIENTATION_HORIZONTAL,
+                              "visible", TRUE,
+                              NULL);
+      gtk_list_box_row_set_header (row, header);
+    }
+}
+
+static gint
+gb_projects_dialog__listbox_sort_cb (GtkListBoxRow *row1,
+                                     GtkListBoxRow *row2,
+                                     gpointer       user_data)
+{
+  IdeProjectInfo *info1;
+  IdeProjectInfo *info2;
+  const gchar *name1;
+  const gchar *name2;
+  GDateTime *dt1;
+  GDateTime *dt2;
+  gint ret;
+
+  g_assert (GB_IS_RECENT_PROJECT_ROW (row1));
+  g_assert (GB_IS_RECENT_PROJECT_ROW (row2));
+
+  info1 = gb_recent_project_row_get_project_info (GB_RECENT_PROJECT_ROW (row1));
+  info2 = gb_recent_project_row_get_project_info (GB_RECENT_PROJECT_ROW (row2));
+
+  g_assert (IDE_IS_PROJECT_INFO (info1));
+  g_assert (IDE_IS_PROJECT_INFO (info2));
+
+  dt1 = ide_project_info_get_last_modified_at (info1);
+  dt2 = ide_project_info_get_last_modified_at (info2);
+
+  ret = g_date_time_compare (dt2, dt1);
+
+  if (ret != 0)
+    return ret;
+
+  name1 = ide_project_info_get_name (info1);
+  name2 = ide_project_info_get_name (info2);
+
+  if (name1 == NULL)
+    return 1;
+  else if (name2 == NULL)
+    return -1;
+  else
+    return strcasecmp (name1, name2);
+}
+
+static gboolean
+gb_projects_dialog__listbox_filter_cb (GtkListBoxRow *row,
+                                       gpointer       user_data)
+{
+  GbProjectsDialog *self = user_data;
+  IdeProjectInfo *info;
+  const gchar *name;
+
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+
+  info = gb_recent_project_row_get_project_info (GB_RECENT_PROJECT_ROW (row));
+  g_assert (IDE_IS_PROJECT_INFO (info));
+
+  if (self->search_pattern == NULL)
+    return TRUE;
+
+  name = ide_project_info_get_name (info);
+
+  return ide_pattern_spec_match (self->search_pattern, name);
+}
+
+static void
+gb_projects_dialog__select_button_notify_active (GbProjectsDialog *self,
+                                                 GParamSpec       *pspec,
+                                                 GtkToggleButton  *select_button)
+{
+  GtkStyleContext *style_context;
+  gboolean active;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GTK_IS_TOGGLE_BUTTON (select_button));
+
+  active = gtk_toggle_button_get_active (select_button);
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (self->header_bar));
+
+  if (active)
+    {
+      gtk_widget_set_visible (GTK_WIDGET (self->action_bar), TRUE);
+      gtk_widget_set_visible (GTK_WIDGET (self->new_button), FALSE);
+      gtk_widget_set_visible (GTK_WIDGET (self->select_button), FALSE);
+      gtk_widget_set_visible (GTK_WIDGET (self->cancel_button), TRUE);
+      gtk_header_bar_set_show_close_button (self->header_bar, FALSE);
+      gtk_header_bar_set_title (self->header_bar, _("(Click on items to select them)"));
+      gtk_style_context_add_class (style_context, "selection-mode");
+    }
+  else
+    {
+      gtk_style_context_remove_class (style_context, "selection-mode");
+      gtk_widget_set_visible (GTK_WIDGET (self->action_bar), FALSE);
+      gtk_widget_set_visible (GTK_WIDGET (self->new_button), TRUE);
+      gtk_widget_set_visible (GTK_WIDGET (self->select_button), TRUE);
+      gtk_widget_set_visible (GTK_WIDGET (self->cancel_button), FALSE);
+      gtk_header_bar_set_show_close_button (self->header_bar, TRUE);
+      gtk_header_bar_set_title (self->header_bar, _("Select Project"));
+    }
+}
+
+static void
+gb_projects_dialog__cancel_button_clicked (GbProjectsDialog *self,
+                                           GtkButton        *cancel_button)
+{
+  GList *rows;
+  GList *iter;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GTK_IS_BUTTON (cancel_button));
+
+  /* reset back to normal mode */
+  gtk_toggle_button_set_active (self->select_button, FALSE);
+
+  /* uncheck rows */
+  rows = gtk_container_get_children (GTK_CONTAINER (self->listbox));
+  for (iter = rows; iter; iter = iter->next)
+    gb_recent_project_row_set_selected (iter->data, FALSE);
+  g_list_free (rows);
+}
+
+static void
+gb_projects_dialog__search_entry_activate (GbProjectsDialog *self,
+                                           GtkEntry         *entry)
+{
+  GtkListBoxRow *row;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  /* FIXME: We use 1 because 0 doesn't work and we have no API to get
+   *        the first row taking the sort/filter into account.
+   */
+  row = gtk_list_box_get_row_at_y (self->listbox, 1);
+  if (row != NULL)
+    g_signal_emit_by_name (row, "activate");
+}
+
+static void
+gb_projects_dialog__search_entry_changed (GbProjectsDialog *self,
+                                          GtkEntry         *entry)
+{
+  const gchar *text;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  g_clear_pointer (&self->search_pattern, (GDestroyNotify)ide_pattern_spec_unref);
+
+  text = gtk_entry_get_text (entry);
+  if (!gb_str_empty0 (text))
+    self->search_pattern = ide_pattern_spec_new (text);
+
+  gtk_list_box_invalidate_filter (self->listbox);
+}
+
+static void
+gb_projects_dialog__window_open_project (GbProjectsDialog   *self,
+                                         GFile              *project_file,
+                                         GbNewProjectDialog *dialog)
+{
+  GApplication *app = g_application_get_default ();
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (G_IS_FILE (project_file));
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (dialog));
+  g_assert (GB_IS_APPLICATION (app));
+
+  gb_application_open_project (GB_APPLICATION (app), project_file, NULL);
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+  gtk_widget_destroy (GTK_WIDGET (self));
+}
+
+static void
+gb_projects_dialog__new_button_clicked (GbProjectsDialog *self,
+                                        GtkButton        *new_button)
+{
+  GtkWindow *window;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GTK_IS_BUTTON (new_button));
+
+  window = g_object_new (GB_TYPE_NEW_PROJECT_DIALOG,
+                         "destroy-with-parent", TRUE,
+                         "transient-for", self,
+                         "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG,
+                         "visible", TRUE,
+                         NULL);
+
+  g_signal_connect_object (window,
+                           "open-project",
+                           G_CALLBACK (gb_projects_dialog__window_open_project),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_window_present (window);
+}
+
+static void
+gb_projects_dialog__delete_button_clicked (GbProjectsDialog *self,
+                                           GtkButton        *delete_button)
+{
+  GList *list;
+  GList *iter;
+
+  g_assert (GB_IS_PROJECTS_DIALOG (self));
+  g_assert (GTK_IS_BUTTON (delete_button));
+
+  list = gtk_container_get_children (GTK_CONTAINER (self->listbox));
+
+  /*
+   * TODO: track infos to delete.
+   *       confirm with user.
+   *       walk directory tree and remove.
+   */
+
+  for (iter = list; iter; iter = iter->next)
+    {
+
+      if (gb_recent_project_row_get_selected (iter->data))
+        {
+          gtk_container_remove (GTK_CONTAINER (self->listbox), iter->data);
+        }
+    }
+
+  g_list_free (list);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->select_button), FALSE);
+}
+
+static void
+gb_projects_dialog_constructed (GObject *object)
+{
+  GbProjectsDialog *self = (GbProjectsDialog *)object;
+
+  G_OBJECT_CLASS (gb_projects_dialog_parent_class)->constructed (object);
+
+  g_signal_connect_object (self->listbox,
+                           "row-activated",
+                           G_CALLBACK (gb_projects_dialog__listbox_row_activated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->delete_button,
+                           "clicked",
+                           G_CALLBACK (gb_projects_dialog__delete_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->recent_projects,
+                           "added",
+                           G_CALLBACK (gb_projects_dialog__recent_projects_added),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_object_bind_property (self->search_button, "active",
+                          self->search_bar, "search-mode-enabled",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  g_signal_connect_object (self->search_entry,
+                           "activate",
+                           G_CALLBACK (gb_projects_dialog__search_entry_activate),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->search_entry,
+                           "changed",
+                           G_CALLBACK (gb_projects_dialog__search_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->select_button,
+                           "notify::active",
+                           G_CALLBACK (gb_projects_dialog__select_button_notify_active),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->cancel_button,
+                           "clicked",
+                           G_CALLBACK (gb_projects_dialog__cancel_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->new_button,
+                           "clicked",
+                           G_CALLBACK (gb_projects_dialog__new_button_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_list_box_set_header_func (self->listbox,
+                                gb_projects_dialog__listbox_header_cb,
+                                NULL, NULL);
+
+  gtk_list_box_set_sort_func (self->listbox,
+                              gb_projects_dialog__listbox_sort_cb,
+                              NULL, NULL);
+
+  gtk_list_box_set_filter_func (self->listbox,
+                                gb_projects_dialog__listbox_filter_cb,
+                                self, NULL);
+
+  ide_recent_projects_discover_async (self->recent_projects,
+                                      NULL, /* TODO: cancellable */
+                                      gb_projects_dialog__recent_projects_discover_cb,
+                                      g_object_ref (self));
+}
+
+static gboolean
+gb_projects_dialog_key_press_event (GtkWidget   *widget,
+                                   GdkEventKey *event)
+{
+  GbProjectsDialog *self = (GbProjectsDialog *)widget;
+  gboolean ret;
+
+  ret = GTK_WIDGET_CLASS (gb_projects_dialog_parent_class)->key_press_event (widget, event);
+
+  if (!ret)
+    {
+      switch (event->keyval)
+        {
+        case GDK_KEY_Escape:
+          if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->select_button)))
+            {
+              gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->select_button), FALSE);
+              ret = TRUE;
+            }
+          if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_button)))
+            {
+              gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->search_button), FALSE);
+              ret = TRUE;
+            }
+          break;
+
+        case GDK_KEY_f:
+          if ((event->state & GDK_CONTROL_MASK) != 0)
+            {
+              gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->search_button), TRUE);
+              ret = TRUE;
+            }
+          break;
+
+        default:
+          break;
+        }
+    }
+
+
+  return ret;
+}
+
+static void
+gb_projects_dialog_finalize (GObject *object)
+{
+  GbProjectsDialog *self = (GbProjectsDialog *)object;
+
+  g_clear_object (&self->recent_projects);
+  g_clear_pointer (&self->search_pattern, (GDestroyNotify)ide_pattern_spec_unref);
+
+  G_OBJECT_CLASS (gb_projects_dialog_parent_class)->finalize (object);
+}
+
+static void
+gb_projects_dialog_class_init (GbProjectsDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = gb_projects_dialog_constructed;
+  object_class->finalize = gb_projects_dialog_finalize;
+
+  widget_class->key_press_event = gb_projects_dialog_key_press_event;
+
+  GB_WIDGET_CLASS_TEMPLATE (klass, "gb-projects-dialog.ui");
+
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, action_bar);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, cancel_button);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, delete_button);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, header_bar);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, new_button);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, listbox);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, search_bar);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, search_button);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, search_entry);
+  GB_WIDGET_CLASS_BIND (klass, GbProjectsDialog, select_button);
+
+  g_type_ensure (GB_TYPE_SCROLLED_WINDOW);
+}
+
+static void
+gb_projects_dialog_init (GbProjectsDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->recent_projects = ide_recent_projects_new ();
+}
diff --git a/src/dialogs/gb-projects-dialog.h b/src/dialogs/gb-projects-dialog.h
new file mode 100644
index 0000000..1989969
--- /dev/null
+++ b/src/dialogs/gb-projects-dialog.h
@@ -0,0 +1,34 @@
+/* gb-projects-dialog.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_PROJECTS_DIALOG_H
+#define GB_PROJECTS_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_PROJECTS_DIALOG (gb_projects_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GbProjectsDialog, gb_projects_dialog, GB, PROJECTS_DIALOG, GtkApplicationWindow)
+
+GbProjectsDialog *gb_projects_dialog_new (void);
+
+G_END_DECLS
+
+#endif /* GB_PROJECTS_DIALOG_H */
diff --git a/src/dialogs/gb-recent-project-row.c b/src/dialogs/gb-recent-project-row.c
new file mode 100644
index 0000000..baf176c
--- /dev/null
+++ b/src/dialogs/gb-recent-project-row.c
@@ -0,0 +1,280 @@
+/* gb-recent-project-row.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-glib.h"
+#include "gb-recent-project-row.h"
+#include "gb-widget.h"
+
+struct _GbRecentProjectRow
+{
+  GtkListBoxRow   parent_instance;
+
+  IdeProjectInfo *project_info;
+
+  GtkCheckButton *check_button;
+  GtkImage       *image;
+  GtkLabel       *date_label;
+  GtkLabel       *location_label;
+  GtkLabel       *name_label;
+  GtkRevealer    *revealer;
+};
+
+struct _GbRecentProjectRowClass
+{
+  GtkListBoxRowClass parent_class;
+};
+
+enum
+{
+  PROP_0,
+  PROP_PROJECT_INFO,
+  PROP_SELECTED,
+  PROP_SELECTION_MODE,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (GbRecentProjectRow, gb_recent_project_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+gb_recent_project_row_set_selection_mode (GbRecentProjectRow *self,
+                                          gboolean            selection_mode)
+{
+  g_return_if_fail (GB_IS_RECENT_PROJECT_ROW (self));
+
+  if (selection_mode != gtk_revealer_get_reveal_child (self->revealer))
+    gtk_revealer_set_reveal_child (self->revealer, selection_mode);
+}
+
+static gboolean
+gb_recent_project_row_get_selection_mode (GbRecentProjectRow *self)
+{
+  g_return_val_if_fail (GB_IS_RECENT_PROJECT_ROW (self), FALSE);
+
+  return gtk_revealer_get_reveal_child (self->revealer);
+}
+
+static void
+gb_recent_project_row_set_project_info (GbRecentProjectRow *self,
+                                        IdeProjectInfo     *project_info)
+{
+  g_assert (GB_IS_RECENT_PROJECT_ROW (self));
+  g_assert (IDE_IS_PROJECT_INFO (project_info));
+
+  if (g_set_object (&self->project_info, project_info))
+    {
+      if (project_info != NULL)
+        {
+          g_autofree gchar *relpath = NULL;
+          g_autofree gchar *datestr = NULL;
+          g_autoptr(GFile) home = NULL;
+          GDateTime *last_modified_at;
+          const gchar *name;
+          GFile *directory;
+
+          name = ide_project_info_get_name (project_info);
+          directory = ide_project_info_get_directory (project_info);
+          last_modified_at = ide_project_info_get_last_modified_at (project_info);
+
+          home = g_file_new_for_path (g_get_home_dir ());
+          relpath = g_file_get_relative_path (home, directory);
+          if (relpath == NULL)
+            relpath = g_file_get_path (directory);
+
+          if (!g_file_is_native (directory))
+            {
+              gtk_image_set_from_icon_name (self->image, "folder-remote", GTK_ICON_SIZE_DIALOG);
+              gtk_image_set_pixel_size (self->image, 64);
+            }
+
+          datestr = gb_date_time_format_for_display (last_modified_at);
+
+          gtk_label_set_label (self->name_label, name);
+          gtk_label_set_label (self->location_label, relpath);
+          gtk_label_set_label (self->date_label, datestr);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_PROJECT_INFO]);
+    }
+}
+
+static void
+gb_recent_project_row__check_button_toggled (GbRecentProjectRow *self,
+                                             GtkToggleButton    *toggle_button)
+{
+  g_assert (GB_IS_RECENT_PROJECT_ROW (self));
+  g_assert (GTK_IS_TOGGLE_BUTTON (toggle_button));
+
+  g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_SELECTED]);
+}
+
+static void
+gb_recent_project_row_finalize (GObject *object)
+{
+  GbRecentProjectRow *self = (GbRecentProjectRow *)object;
+
+  g_clear_object (&self->project_info);
+
+  G_OBJECT_CLASS (gb_recent_project_row_parent_class)->finalize (object);
+}
+
+static void
+gb_recent_project_row_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GbRecentProjectRow *self = GB_RECENT_PROJECT_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROJECT_INFO:
+      g_value_set_object (value, gb_recent_project_row_get_project_info (self));
+      break;
+
+    case PROP_SELECTED:
+      g_value_set_boolean (value, gb_recent_project_row_get_selected (self));
+      break;
+
+    case PROP_SELECTION_MODE:
+      g_value_set_boolean (value, gb_recent_project_row_get_selection_mode (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_recent_project_row_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GbRecentProjectRow *self = GB_RECENT_PROJECT_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROJECT_INFO:
+      gb_recent_project_row_set_project_info (self, g_value_get_object (value));
+      break;
+
+    case PROP_SELECTED:
+      gb_recent_project_row_set_selected (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_SELECTION_MODE:
+      gb_recent_project_row_set_selection_mode (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gb_recent_project_row_class_init (GbRecentProjectRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gb_recent_project_row_finalize;
+  object_class->get_property = gb_recent_project_row_get_property;
+  object_class->set_property = gb_recent_project_row_set_property;
+
+  GB_WIDGET_CLASS_TEMPLATE (klass, "gb-recent-project-row.ui");
+  GB_WIDGET_CLASS_BIND (klass, GbRecentProjectRow, check_button);
+  GB_WIDGET_CLASS_BIND (klass, GbRecentProjectRow, date_label);
+  GB_WIDGET_CLASS_BIND (klass, GbRecentProjectRow, image);
+  GB_WIDGET_CLASS_BIND (klass, GbRecentProjectRow, location_label);
+  GB_WIDGET_CLASS_BIND (klass, GbRecentProjectRow, name_label);
+  GB_WIDGET_CLASS_BIND (klass, GbRecentProjectRow, revealer);
+
+  gParamSpecs [PROP_PROJECT_INFO] =
+    g_param_spec_object ("project-info",
+                         _("Project Info"),
+                         _("The project info for the row."),
+                         IDE_TYPE_PROJECT_INFO,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_PROJECT_INFO,
+                                   gParamSpecs [PROP_PROJECT_INFO]);
+
+  gParamSpecs [PROP_SELECTED] =
+    g_param_spec_boolean ("selected",
+                          _("Selected"),
+                          _("Selected"),
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_SELECTED, gParamSpecs [PROP_SELECTED]);
+
+  gParamSpecs [PROP_SELECTION_MODE] =
+    g_param_spec_boolean ("selection-mode",
+                          _("Selection Mode"),
+                          _("Selection Mode"),
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_SELECTION_MODE,
+                                   gParamSpecs [PROP_SELECTION_MODE]);
+}
+
+static void
+gb_recent_project_row_init (GbRecentProjectRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->check_button,
+                           "toggled",
+                           G_CALLBACK (gb_recent_project_row__check_button_toggled),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+IdeProjectInfo *
+gb_recent_project_row_get_project_info (GbRecentProjectRow *self)
+{
+  g_return_val_if_fail (GB_IS_RECENT_PROJECT_ROW (self), NULL);
+
+  return self->project_info;
+}
+
+GtkWidget *
+gb_recent_project_row_new (IdeProjectInfo *project_info)
+{
+  return g_object_new (IDE_TYPE_PROJECT_INFO,
+                       "project-info", project_info,
+                       NULL);
+}
+
+gboolean
+gb_recent_project_row_get_selected (GbRecentProjectRow *self)
+{
+  g_return_val_if_fail (GB_IS_RECENT_PROJECT_ROW (self), FALSE);
+
+  return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->check_button));
+}
+
+void
+gb_recent_project_row_set_selected (GbRecentProjectRow *self,
+                                    gboolean            selected)
+{
+  g_return_if_fail (GB_IS_RECENT_PROJECT_ROW (self));
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->check_button), selected);
+}
diff --git a/src/dialogs/gb-recent-project-row.h b/src/dialogs/gb-recent-project-row.h
new file mode 100644
index 0000000..9ad84d4
--- /dev/null
+++ b/src/dialogs/gb-recent-project-row.h
@@ -0,0 +1,47 @@
+/* gb-recent-project-row.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_RECENT_PROJECT_ROW_H
+#define GB_RECENT_PROJECT_ROW_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_RECENT_PROJECT_ROW            (gb_recent_project_row_get_type())
+#define GB_RECENT_PROJECT_ROW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_RECENT_PROJECT_ROW, GbRecentProjectRow))
+#define GB_RECENT_PROJECT_ROW_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_RECENT_PROJECT_ROW, GbRecentProjectRow const))
+#define GB_RECENT_PROJECT_ROW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
GB_TYPE_RECENT_PROJECT_ROW, GbRecentProjectRowClass))
+#define GB_IS_RECENT_PROJECT_ROW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GB_TYPE_RECENT_PROJECT_ROW))
+#define GB_IS_RECENT_PROJECT_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
GB_TYPE_RECENT_PROJECT_ROW))
+#define GB_RECENT_PROJECT_ROW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
GB_TYPE_RECENT_PROJECT_ROW, GbRecentProjectRowClass))
+
+typedef struct _GbRecentProjectRow      GbRecentProjectRow;
+typedef struct _GbRecentProjectRowClass GbRecentProjectRowClass;
+
+GType           gb_recent_project_row_get_type         (void);
+GtkWidget      *gb_recent_project_row_new              (IdeProjectInfo     *project_info);
+IdeProjectInfo *gb_recent_project_row_get_project_info (GbRecentProjectRow *self);
+gboolean        gb_recent_project_row_get_selected     (GbRecentProjectRow *self);
+void            gb_recent_project_row_set_selected     (GbRecentProjectRow *self,
+                                                        gboolean            selected);
+
+G_END_DECLS
+
+#endif /* GB_RECENT_PROJECT_ROW_H */
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index e188338..395f131 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -35,6 +35,12 @@ libgnome_builder_la_SOURCES = \
        src/devhelp/gb-devhelp-document.h \
        src/devhelp/gb-devhelp-view.c \
        src/devhelp/gb-devhelp-view.h \
+       src/dialogs/gb-new-project-dialog.c \
+       src/dialogs/gb-new-project-dialog.h \
+       src/dialogs/gb-projects-dialog.c \
+       src/dialogs/gb-projects-dialog.h \
+       src/dialogs/gb-recent-project-row.c \
+       src/dialogs/gb-recent-project-row.h \
        src/documents/gb-document.c \
        src/documents/gb-document.h \
        src/editor/gb-editor-document.c \
@@ -168,6 +174,7 @@ libgnome_builder_la_CFLAGS = \
        -I$(top_srcdir)/src/app \
        -I$(top_srcdir)/src/commands \
        -I$(top_srcdir)/src/devhelp \
+       -I$(top_srcdir)/src/dialogs \
        -I$(top_srcdir)/src/documents \
        -I$(top_srcdir)/src/editor \
        -I$(top_srcdir)/src/gd \
diff --git a/src/resources/gnome-builder.gresource.xml b/src/resources/gnome-builder.gresource.xml
index e619206..7b2f90f 100644
--- a/src/resources/gnome-builder.gresource.xml
+++ b/src/resources/gnome-builder.gresource.xml
@@ -14,7 +14,7 @@
     <file 
alias="icons/scalable/actions/builder-split-tab-left-symbolic.svg">../../data/icons/hicolor/scalable/actions/builder-split-tab-left-symbolic.svg</file>
     <file 
alias="icons/scalable/actions/builder-split-tab-right-symbolic.svg">../../data/icons/hicolor/scalable/actions/builder-split-tab-right-symbolic.svg</file>
     <file 
alias="icons/scalable/actions/builder-split-tab-symbolic.svg">../../data/icons/hicolor/scalable/actions/builder-split-tab-symbolic.svg</file>
-    
+
     <file 
alias="icons/scalable/actions/lang-class-symbolic.svg">../../data/icons/hicolor/scalable/autocomplete/lang-class-symbolic.svg</file>
     <file 
alias="icons/scalable/actions/lang-enum-symbolic.svg">../../data/icons/hicolor/scalable/autocomplete/lang-enum-symbolic.svg</file>
     <file 
alias="icons/scalable/actions/lang-function-symbolic.svg">../../data/icons/hicolor/scalable/autocomplete/lang-function-symbolic.svg</file>
@@ -41,12 +41,15 @@
     <file alias="ui/gb-editor-view.ui">../../data/ui/gb-editor-view.ui</file>
     <file alias="ui/gb-editor-workspace.ui">../../data/ui/gb-editor-workspace.ui</file>
     <file alias="ui/gb-html-view.ui">../../data/ui/gb-html-view.ui</file>
+    <file alias="ui/gb-new-project-dialog.ui">../../data/ui/gb-new-project-dialog.ui</file>
     <file alias="ui/gb-preferences-page-editor.ui">../../data/ui/gb-preferences-page-editor.ui</file>
     <file 
alias="ui/gb-preferences-page-experimental.ui">../../data/ui/gb-preferences-page-experimental.ui</file>
     <file alias="ui/gb-preferences-page-git.ui">../../data/ui/gb-preferences-page-git.ui</file>
     <file 
alias="ui/gb-preferences-page-keybindings.ui">../../data/ui/gb-preferences-page-keybindings.ui</file>
     <file alias="ui/gb-preferences-page-language.ui">../../data/ui/gb-preferences-page-language.ui</file>
     <file alias="ui/gb-preferences-window.ui">../../data/ui/gb-preferences-window.ui</file>
+    <file alias="ui/gb-projects-dialog.ui">../../data/ui/gb-projects-dialog.ui</file>
+    <file alias="ui/gb-recent-project-row.ui">../../data/ui/gb-recent-project-row.ui</file>
     <file alias="ui/gb-search-box.ui">../../data/ui/gb-search-box.ui</file>
     <file alias="ui/gb-search-display-group.ui">../../data/ui/gb-search-display-group.ui</file>
     <file alias="ui/gb-search-display-row.ui">../../data/ui/gb-search-display-row.ui</file>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]