[gnome-builder] project-selector: add New Project and Open Project dialog
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] project-selector: add New Project and Open Project dialog
- Date: Wed, 8 Apr 2015 07:26:41 +0000 (UTC)
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]