[gnome-builder: 47/139] libide-greeter: add new libide-greeter static library
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder: 47/139] libide-greeter: add new libide-greeter static library
- Date: Thu, 10 Jan 2019 04:21:16 +0000 (UTC)
commit fb47eb9ae435fcad8f8af530686700273f4246f9
Author: Christian Hergert <chergert redhat com>
Date: Wed Jan 9 16:38:03 2019 -0800
libide-greeter: add new libide-greeter static library
This creates a new static library that contains the greeter. It breaks
the greeter into a separate window so that we can vastly simplify the
code in Builder with regards to workspaces.
Some code is moved to a plugin so that it can hook into command line
options and provide the new -g option to jump to the greeter.
src/libide/genesis/ide-genesis-addin.c | 150 ---
src/libide/genesis/ide-genesis-addin.h | 82 --
src/libide/genesis/meson.build | 12 -
src/libide/greeter/ide-clone-surface.c | 564 ++++++++
src/libide/greeter/ide-clone-surface.h | 42 +
src/libide/greeter/ide-clone-surface.ui | 383 ++++++
src/libide/greeter/ide-greeter-perspective.c | 1385 --------------------
src/libide/greeter/ide-greeter-perspective.ui | 427 ------
...greeter-perspective.h => ide-greeter-private.h} | 17 +-
src/libide/greeter/ide-greeter-section.c | 2 +-
src/libide/greeter/ide-greeter-section.h | 6 +-
src/libide/greeter/ide-greeter-workspace-actions.c | 223 ++++
.../greeter/ide-greeter-workspace-shortcuts.c | 44 +
src/libide/greeter/ide-greeter-workspace.c | 808 ++++++++++++
src/libide/greeter/ide-greeter-workspace.h | 61 +
src/libide/greeter/ide-greeter-workspace.ui | 177 +++
src/libide/greeter/libide-greeter.gresource.xml | 7 +
src/libide/greeter/libide-greeter.h | 34 +
src/libide/greeter/meson.build | 88 +-
.../greeter/gbp-greeter-application-addin.c | 229 ++++
.../greeter/gbp-greeter-application-addin.h | 31 +
src/plugins/greeter/greeter-plugin.c | 36 +
src/plugins/greeter/greeter.gresource.xml | 7 +
src/plugins/greeter/greeter.plugin | 13 +
src/plugins/greeter/gtk/menus.ui | 97 ++
src/plugins/greeter/meson.build | 12 +
26 files changed, 2856 insertions(+), 2081 deletions(-)
---
diff --git a/src/libide/greeter/ide-clone-surface.c b/src/libide/greeter/ide-clone-surface.c
new file mode 100644
index 000000000..aa038842c
--- /dev/null
+++ b/src/libide/greeter/ide-clone-surface.c
@@ -0,0 +1,564 @@
+/* ide-clone-surface.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-clone-surface"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+#include <libide-vcs.h>
+
+#include "ide-clone-surface.h"
+#include "ide-greeter-private.h"
+#include "ide-greeter-workspace.h"
+
+struct _IdeCloneSurface
+{
+ IdeSurface parent_instance;
+
+ /* This extension set contains IdeVcsCloner implementations which we
+ * use to validate URIs, as well as provide some toggles for how the
+ * user wants to perform the clone operation. Currently, we have a
+ * very limited set of cloning (basically just git), but that could be
+ * expanded in the future based on demand.
+ */
+ PeasExtensionSet *addins;
+ guint n_addins;
+
+ /* We calculate the file to the target folder based on the vcs uri and
+ * the destination file chooser. It's cached here so that we don't have
+ * to recaclulate it in multiple code paths.
+ */
+ GFile *destination;
+
+ /* Template Widgets */
+ DzlFileChooserEntry *destination_chooser;
+ GtkLabel *destination_label;
+ DzlRadioBox *kind_radio;
+ GtkLabel *kind_label;
+ GtkLabel *status_message;
+ GtkEntry *uri_entry;
+ GtkEntry *author_entry;
+ GtkEntry *email_entry;
+ GtkEntry *branch_entry;
+ GtkButton *clone_button;
+ GtkButton *cancel_button;
+ GtkStack *button_stack;
+};
+
+G_DEFINE_TYPE (IdeCloneSurface, ide_clone_surface, IDE_TYPE_SURFACE)
+
+enum {
+ PROP_0,
+ PROP_URI,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * ide_clone_surface_new:
+ *
+ * Create a new #IdeCloneSurface.
+ *
+ * Returns: (transfer full): a newly created #IdeCloneSurface
+ *
+ * Since: 3.32
+ */
+IdeCloneSurface *
+ide_clone_surface_new (void)
+{
+ return g_object_new (IDE_TYPE_CLONE_SURFACE, NULL);
+}
+
+static void
+ide_clone_surface_addin_added_cb (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeVcsCloner *cloner = (IdeVcsCloner *)exten;
+ IdeCloneSurface *self = user_data;
+ g_autofree gchar *title = NULL;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_VCS_CLONER (cloner));
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+
+ self->n_addins++;
+
+ title = ide_vcs_cloner_get_title (cloner);
+
+ dzl_radio_box_add_item (self->kind_radio,
+ peas_plugin_info_get_module_name (plugin_info),
+ title);
+
+ if (self->n_addins > 1)
+ {
+ gtk_widget_show (GTK_WIDGET (self->kind_label));
+ gtk_widget_show (GTK_WIDGET (self->kind_radio));
+ }
+}
+
+static void
+ide_clone_surface_addin_removed_cb (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeVcsCloner *cloner = (IdeVcsCloner *)exten;
+ IdeCloneSurface *self = user_data;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_VCS_CLONER (cloner));
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+
+ self->n_addins--;
+
+ dzl_radio_box_remove_item (self->kind_radio,
+ peas_plugin_info_get_module_name (plugin_info));
+
+ if (self->n_addins < 2)
+ {
+ gtk_widget_hide (GTK_WIDGET (self->kind_label));
+ gtk_widget_hide (GTK_WIDGET (self->kind_radio));
+ }
+}
+
+static void
+ide_clone_surface_validate_cb (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeVcsCloner *cloner = (IdeVcsCloner *)exten;
+ struct {
+ const gchar *text;
+ gchar *errmsg;
+ gboolean valid;
+ } *validate = user_data;
+ g_autofree gchar *errmsg = NULL;
+
+ g_assert (IDE_IS_VCS_CLONER (cloner));
+
+ if (validate->valid)
+ return;
+
+ validate->valid = ide_vcs_cloner_validate_uri (cloner, validate->text, &errmsg);
+
+ if (!validate->errmsg)
+ validate->errmsg = g_steal_pointer (&errmsg);
+}
+
+static void
+ide_clone_surface_validate (IdeCloneSurface *self)
+{
+ struct {
+ const gchar *text;
+ gchar *errmsg;
+ gboolean valid;
+ } validate;
+
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+
+ validate.text = gtk_entry_get_text (self->uri_entry);
+ validate.errmsg = NULL;
+ validate.valid = FALSE;
+
+ if (self->addins != NULL)
+ peas_extension_set_foreach (self->addins,
+ ide_clone_surface_validate_cb,
+ &validate);
+
+ if (validate.valid)
+ dzl_gtk_widget_remove_style_class (GTK_WIDGET (self->uri_entry), "error");
+ else
+ dzl_gtk_widget_add_style_class (GTK_WIDGET (self->uri_entry), "error");
+
+ if (validate.errmsg)
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->uri_entry), validate.errmsg);
+ else
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self->uri_entry), NULL);
+
+ g_free (validate.errmsg);
+}
+
+static void
+ide_clone_surface_update (IdeCloneSurface *self)
+{
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFile) child_file = NULL;
+ g_autoptr(IdeVcsUri) uri = NULL;
+ g_autofree gchar *child = NULL;
+ g_autofree gchar *collapsed = NULL;
+ g_autofree gchar *formatted = NULL;
+ const gchar *text;
+ GtkEntry *entry;
+
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+
+ ide_clone_surface_validate (self);
+
+ file = dzl_file_chooser_entry_get_file (self->destination_chooser);
+ text = gtk_entry_get_text (self->uri_entry);
+ uri = ide_vcs_uri_new (text);
+
+ if (uri != NULL)
+ child = ide_vcs_uri_get_clone_name (uri);
+
+ if (child)
+ child_file = g_file_get_child (file, child);
+ else
+ child_file = g_object_ref (file);
+
+ g_set_object (&self->destination, child_file);
+
+ collapsed = ide_path_collapse (g_file_peek_path (child_file));
+
+ entry = dzl_file_chooser_entry_get_entry (self->destination_chooser);
+
+ if (g_file_query_exists (child_file, NULL))
+ {
+ /* translators: %s is replaced with the path to the project */
+ formatted = g_strdup_printf (_("The directory “%s” already exists. Please choose another directory."),
+ collapsed);
+ dzl_gtk_widget_add_style_class (GTK_WIDGET (entry), "error");
+ }
+ else
+ {
+ /* translators: %s is replaced with the path to the project */
+ formatted = g_strdup_printf (_("Your project will be created at %s"), collapsed);
+ dzl_gtk_widget_remove_style_class (GTK_WIDGET (entry), "error");
+ }
+
+ gtk_label_set_label (self->destination_label, formatted);
+}
+
+static void
+ide_clone_surface_uri_entry_changed (IdeCloneSurface *self,
+ GtkEntry *entry)
+{
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ ide_clone_surface_update (self);
+}
+
+static void
+ide_clone_surface_destination_changed (IdeCloneSurface *self,
+ GParamSpec *pspec,
+ DzlFileChooserEntry *chooser)
+{
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+ g_assert (DZL_IS_FILE_CHOOSER_ENTRY (chooser));
+
+ ide_clone_surface_update (self);
+}
+
+static void
+ide_clone_surface_grab_focus (GtkWidget *widget)
+{
+ gtk_widget_grab_focus (GTK_WIDGET (IDE_CLONE_SURFACE (widget)->uri_entry));
+}
+
+static void
+ide_clone_surface_destroy (GtkWidget *widget)
+{
+ IdeCloneSurface *self = (IdeCloneSurface *)widget;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+
+ g_clear_object (&self->addins);
+ g_clear_object (&self->destination);
+
+ GTK_WIDGET_CLASS (ide_clone_surface_parent_class)->destroy (widget);
+}
+
+static void
+ide_clone_surface_constructed (GObject *object)
+{
+ IdeCloneSurface *self = (IdeCloneSurface *)object;
+ g_autoptr(GFile) file = NULL;
+
+ G_OBJECT_CLASS (ide_clone_surface_parent_class)->constructed (object);
+
+ gtk_entry_set_text (self->author_entry, g_get_real_name ());
+
+ file = g_file_new_for_path (ide_get_projects_dir ());
+ dzl_file_chooser_entry_set_file (self->destination_chooser, file);
+
+ self->addins = peas_extension_set_new (peas_engine_get_default (),
+ IDE_TYPE_VCS_CLONER,
+ NULL);
+
+ g_signal_connect (self->addins,
+ "extension-added",
+ G_CALLBACK (ide_clone_surface_addin_added_cb),
+ self);
+
+ g_signal_connect (self->addins,
+ "extension-removed",
+ G_CALLBACK (ide_clone_surface_addin_removed_cb),
+ self);
+
+ peas_extension_set_foreach (self->addins,
+ ide_clone_surface_addin_added_cb,
+ self);
+
+ ide_clone_surface_update (self);
+}
+
+static void
+ide_clone_surface_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeCloneSurface *self = IDE_CLONE_SURFACE (object);
+
+ switch (prop_id)
+ {
+ case PROP_URI:
+ g_value_set_string (value, ide_clone_surface_get_uri (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_clone_surface_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeCloneSurface *self = IDE_CLONE_SURFACE (object);
+
+ switch (prop_id)
+ {
+ case PROP_URI:
+ ide_clone_surface_set_uri (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_clone_surface_class_init (IdeCloneSurfaceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = ide_clone_surface_constructed;
+ object_class->get_property = ide_clone_surface_get_property;
+ object_class->set_property = ide_clone_surface_set_property;
+
+ widget_class->destroy = ide_clone_surface_destroy;
+ widget_class->grab_focus = ide_clone_surface_grab_focus;
+
+ /**
+ * IdeCloneSurface:uri:
+ *
+ * The "uri" property is the URI of the version control repository to
+ * be cloned. Usually, this is something like
+ *
+ * "https://gitlab.gnome.org/GNOME/gnome-builder.git"
+ *
+ * Since: 3.32
+ */
+ properties [PROP_URI] =
+ g_param_spec_string ("uri",
+ "Uri",
+ "The URI of the repository to clone.",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-clone-surface.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, author_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, branch_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, button_stack);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, clone_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, destination_chooser);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, destination_label);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, email_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, kind_label);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, kind_radio);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, status_message);
+ gtk_widget_class_bind_template_child (widget_class, IdeCloneSurface, uri_entry);
+ gtk_widget_class_bind_template_callback (widget_class, ide_clone_surface_clone);
+ gtk_widget_class_bind_template_callback (widget_class, ide_clone_surface_destination_changed);
+ gtk_widget_class_bind_template_callback (widget_class, ide_clone_surface_uri_entry_changed);
+}
+
+static void
+ide_clone_surface_init (IdeCloneSurface *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+const gchar *
+ide_clone_surface_get_uri (IdeCloneSurface *self)
+{
+ g_return_val_if_fail (IDE_IS_CLONE_SURFACE (self), NULL);
+
+ return gtk_entry_get_text (self->uri_entry);
+}
+
+void
+ide_clone_surface_set_uri (IdeCloneSurface *self,
+ const gchar *uri)
+{
+ g_return_if_fail (IDE_IS_CLONE_SURFACE (self));
+
+ gtk_entry_set_text (self->uri_entry, uri);
+}
+
+static void
+ide_clone_surface_clone_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeVcsCloner *cloner = (IdeVcsCloner *)object;
+ g_autoptr(IdeCloneSurface) self = user_data;
+ g_autoptr(IdeProjectInfo) project_info = NULL;
+ g_autoptr(GError) error = NULL;
+ GtkWidget *workspace;
+
+ g_assert (IDE_IS_VCS_CLONER (cloner));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_CLONE_SURFACE (self));
+
+ workspace = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_GREETER_WORKSPACE);
+ ide_greeter_workspace_end (IDE_GREETER_WORKSPACE (workspace));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->uri_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->destination_chooser), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->clone_button), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->author_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->email_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->branch_entry), TRUE);
+ gtk_stack_set_visible_child (self->button_stack, GTK_WIDGET (self->clone_button));
+ gtk_label_set_label (self->status_message, "");
+
+ if (!ide_vcs_cloner_clone_finish (cloner, result, &error))
+ {
+ g_warning ("Failed to clone repository: %s", error->message);
+ gtk_label_set_label (self->status_message, error->message);
+ gtk_entry_set_progress_fraction (self->uri_entry, 0);
+ return;
+ }
+
+ project_info = ide_project_info_new ();
+ ide_project_info_set_vcs_uri (project_info, gtk_entry_get_text (self->uri_entry));
+ ide_project_info_set_file (project_info, self->destination);
+ ide_project_info_set_directory (project_info, self->destination);
+
+ ide_greeter_workspace_open_project (IDE_GREETER_WORKSPACE (workspace), project_info);
+}
+
+void
+ide_clone_surface_clone (IdeCloneSurface *self)
+{
+ PeasEngine *engine = peas_engine_get_default ();
+ g_autoptr(IdeNotification) notif = NULL;
+ g_autoptr(GCancellable) cancellable = NULL;
+ PeasPluginInfo *plugin_info;
+ IdeVcsCloner *addin;
+ GVariantDict dict;
+ const gchar *uri;
+ const gchar *path;
+ const gchar *module_name;
+ const gchar *author;
+ const gchar *email;
+ GtkWidget *workspace;
+
+ g_return_if_fail (IDE_IS_CLONE_SURFACE (self));
+
+ if (!(module_name = dzl_radio_box_get_active_id (self->kind_radio)) ||
+ !(plugin_info = peas_engine_get_plugin_info (engine, module_name)) ||
+ !(addin = (IdeVcsCloner *)peas_extension_set_get_extension (self->addins, plugin_info)))
+ {
+ g_warning ("Failed to locate module to use for cloning");
+ return;
+ }
+
+ g_variant_dict_init (&dict, NULL);
+
+ uri = gtk_entry_get_text (self->uri_entry);
+ author = gtk_entry_get_text (self->author_entry);
+ email = gtk_entry_get_text (self->email_entry);
+ path = g_file_peek_path (self->destination);
+
+ if (!ide_str_empty0 (author) && !g_str_equal (g_get_real_name (), author))
+ g_variant_dict_insert (&dict, "author-name", "s", author);
+
+ if (!ide_str_empty0 (email))
+ g_variant_dict_insert (&dict, "author-email", "s", email);
+
+ g_debug ("Cloning repository using addin: %s", module_name);
+
+ workspace = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_GREETER_WORKSPACE);
+ ide_greeter_workspace_begin (IDE_GREETER_WORKSPACE (workspace));
+
+ cancellable = g_cancellable_new ();
+
+ g_signal_connect_object (self->cancel_button,
+ "clicked",
+ G_CALLBACK (g_cancellable_cancel),
+ cancellable,
+ G_CONNECT_SWAPPED);
+
+ ide_vcs_cloner_clone_async (addin,
+ uri,
+ path,
+ &dict,
+ cancellable,
+ ¬if,
+ ide_clone_surface_clone_cb,
+ g_object_ref (self));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->uri_entry), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->destination_chooser), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->clone_button), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->author_entry), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->email_entry), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->branch_entry), FALSE);
+ gtk_stack_set_visible_child (self->button_stack, GTK_WIDGET (self->cancel_button));
+
+ if (notif != NULL)
+ {
+ g_object_bind_property (notif, "progress", self->uri_entry, "progress-fraction",
G_BINDING_SYNC_CREATE);
+ g_object_bind_property (notif, "body", self->status_message, "label", G_BINDING_SYNC_CREATE);
+ }
+
+ g_variant_dict_clear (&dict);
+}
diff --git a/src/libide/greeter/ide-clone-surface.h b/src/libide/greeter/ide-clone-surface.h
new file mode 100644
index 000000000..c9d48c87e
--- /dev/null
+++ b/src/libide/greeter/ide-clone-surface.h
@@ -0,0 +1,42 @@
+/* ide-clone-surface.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CLONE_SURFACE (ide_clone_surface_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeCloneSurface, ide_clone_surface, IDE, CLONE_SURFACE, IdeSurface)
+
+IDE_AVAILABLE_IN_3_32
+IdeCloneSurface *ide_clone_surface_new (void);
+IDE_AVAILABLE_IN_3_32
+const gchar *ide_clone_surface_get_uri (IdeCloneSurface *self);
+IDE_AVAILABLE_IN_3_32
+void ide_clone_surface_set_uri (IdeCloneSurface *self,
+ const gchar *uri);
+IDE_AVAILABLE_IN_3_32
+void ide_clone_surface_clone (IdeCloneSurface *self);
+
+G_END_DECLS
diff --git a/src/libide/greeter/ide-clone-surface.ui b/src/libide/greeter/ide-clone-surface.ui
new file mode 100644
index 000000000..716a2f752
--- /dev/null
+++ b/src/libide/greeter/ide-clone-surface.ui
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.24 -->
+ <template class="IdeCloneSurface" parent="IdeSurface">
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="propagate-natural-width">true</property>
+ <property name="propagate-natural-height">true</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin">32</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">start</property>
+ <property name="vexpand">true</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="DzlThreeGrid" id="grid">
+ <property name="column-spacing">12</property>
+ <!-- can't use row-spacing because we have to animate in
+ the revealer which messes up the margins. -->
+ <property name="row-spacing">0</property>
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkImage" id="splash">
+ <property name="valign">end</property>
+ <property name="vexpand">true</property>
+ <property name="icon-name">document-save-symbolic</property>
+ <property name="pixel-size">128</property>
+ <property name="visible">true</property>
+ <property name="margin">24</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">0</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="repo_label">
+ <property name="label" translatable="yes">Repository URL</property>
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ <property name="xalign">1.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">1</property>
+ <property name="column">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="uri_entry_help">
+ <property name="label" translatable="yes">Enter the repository of the project you
would like to clone. The URL should look similar to
"https://gitlab.gnome.org/GNOME/gnome-builder.git".</property>
+ <property name="margin-top">3</property>
+ <property name="width-chars">40</property>
+ <property name="max-width-chars">60</property>
+ <property name="visible">true</property>
+ <property name="wrap">true</property>
+ <property name="xalign">0.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.833333"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="row">2</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="uri_entry">
+ <property name="placeholder-text"
translatable="yes">user@host:repository.git</property>
+ <property name="width-chars">40</property>
+ <property name="max-width-chars">50</property>
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ <signal name="changed" handler="ide_clone_surface_uri_entry_changed" swapped="true"
object="IdeCloneSurface"/>
+ </object>
+ <packing>
+ <property name="row">1</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="more_button">
+ <property name="halign">start</property>
+ <property name="hexpand">false</property>
+ <property name="visible">true</property>
+ <property name="has-tooltip">true</property>
+ <property name="tooltip-text" translatable="yes">Select branch and other
options.</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="icon-name">view-more-symbolic</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="row">1</property>
+ <property name="column">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="more_revealer">
+ <property name="reveal-child" bind-source="more_button" bind-property="active"/>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-top">12</property>
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="DzlThreeGrid">
+ <property name="column-spacing">12</property>
+ <property name="row-spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="kind_label">
+ <property name="label" translatable="yes">Repository Kind</property>
+ <property name="visible">false</property>
+ <property name="xalign">1.0</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">0</property>
+ <property name="column">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="DzlRadioBox" id="kind_radio">
+ <property name="visible">false</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="row">0</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="branch_label">
+ <property name="label" translatable="yes">Branch</property>
+ <property name="visible">true</property>
+ <property name="xalign">1.0</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">1</property>
+ <property name="column">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="branch_entry">
+ <property name="visible">true</property>
+ <property name="valign">center</property>
+ <property name="text">master</property>
+ </object>
+ <packing>
+ <property name="row">1</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="author_label">
+ <property name="label" translatable="yes">Author Name</property>
+ <property name="visible">true</property>
+ <property name="xalign">1.0</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">2</property>
+ <property name="column">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="author_entry">
+ <property name="visible">true</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="row">2</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="email_label">
+ <property name="label" translatable="yes">Author Email</property>
+ <property name="visible">true</property>
+ <property name="xalign">1.0</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">3</property>
+ <property name="column">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="email_entry">
+ <property name="visible">true</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="row">3</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="DzlThreeGrid">
+ <property name="margin-top">12</property>
+ <property name="column-spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkLabel" id="dest_label">
+ <property name="label" translatable="yes">Project Destination</property>
+ <property name="visible">true</property>
+ <property name="xalign">1.0</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">0</property>
+ <property name="column">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="DzlFileChooserEntry" id="destination_chooser">
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ <signal name="notify::file" handler="ide_clone_surface_destination_changed"
object="IdeCloneSurface" swapped="true"/>
+ </object>
+ <packing>
+ <property name="row">0</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="destination_label">
+ <property name="visible">true</property>
+ <property name="xalign">0.0</property>
+ <property name="valign">center</property>
+ <property name="margin-top">3</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="0.83333"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="row">2</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="margin-top">32</property>
+ <property name="visible">true</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkStack" id="button_stack">
+ <property name="visible">true</property>
+ <property name="homogeneous">true</property>
+ <property name="halign">end</property>
+ <child>
+ <object class="GtkButton" id="clone_button">
+ <property name="label" translatable="yes">Clone Project</property>
+ <property name="visible">true</property>
+ <signal name="clicked" handler="ide_clone_surface_clone"
object="IdeCloneSurface" swapped="true"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="row">3</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="status_message">
+ <property name="margin-top">24</property>
+ <property name="valign">center</property>
+ <property name="visible">true</property>
+ <property name="width-chars">50</property>
+ <property name="max-width-chars">50</property>
+ <property name="wrap">true</property>
+ <property name="xalign">0.0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="row">4</property>
+ <property name="column">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="splash"/>
+ <widget name="branch_entry"/>
+ <widget name="author_entry"/>
+ <widget name="email_entry"/>
+ <widget name="uri_entry"/>
+ <widget name="uri_entry_help"/>
+ <widget name="destination_chooser"/>
+ <widget name="kind_radio"/>
+ </widgets>
+ </object>
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="repo_label"/>
+ <widget name="dest_label"/>
+ <widget name="author_label"/>
+ <widget name="email_label"/>
+ <widget name="branch_label"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/src/libide/greeter/ide-greeter-perspective.h b/src/libide/greeter/ide-greeter-private.h
similarity index 56%
rename from src/libide/greeter/ide-greeter-perspective.h
rename to src/libide/greeter/ide-greeter-private.h
index f1d3edf0c..1b41ee4d6 100644
--- a/src/libide/greeter/ide-greeter-perspective.h
+++ b/src/libide/greeter/ide-greeter-private.h
@@ -1,6 +1,6 @@
-/* ide-greeter-perspective.h
+/* ide-greeter-private.h
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
*
* 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
@@ -20,18 +20,13 @@
#pragma once
-#include <gtk/gtk.h>
+#include <libide-projects.h>
-#include "projects/ide-recent-projects.h"
+#include "ide-greeter-workspace.h"
G_BEGIN_DECLS
-#define IDE_TYPE_GREETER_PERSPECTIVE (ide_greeter_perspective_get_type())
-
-G_DECLARE_FINAL_TYPE (IdeGreeterPerspective, ide_greeter_perspective, IDE, GREETER_PERSPECTIVE, GtkBin)
-
-void ide_greeter_perspective_show_genesis_view (IdeGreeterPerspective *self,
- const gchar *genesis_addin_name,
- const gchar *manifest);
+void _ide_greeter_workspace_init_actions (IdeGreeterWorkspace *self);
+void _ide_greeter_workspace_init_shortcuts (IdeGreeterWorkspace *self);
G_END_DECLS
diff --git a/src/libide/greeter/ide-greeter-section.c b/src/libide/greeter/ide-greeter-section.c
index b8900937c..6794135d8 100644
--- a/src/libide/greeter/ide-greeter-section.c
+++ b/src/libide/greeter/ide-greeter-section.c
@@ -22,7 +22,7 @@
#include "config.h"
-#include "greeter/ide-greeter-section.h"
+#include "ide-greeter-section.h"
G_DEFINE_INTERFACE (IdeGreeterSection, ide_greeter_section, GTK_TYPE_WIDGET)
diff --git a/src/libide/greeter/ide-greeter-section.h b/src/libide/greeter/ide-greeter-section.h
index cfd4acf9a..8dc0f939a 100644
--- a/src/libide/greeter/ide-greeter-section.h
+++ b/src/libide/greeter/ide-greeter-section.h
@@ -21,10 +21,8 @@
#pragma once
#include <dazzle.h>
-
-#include "ide-version-macros.h"
-
-#include "projects/ide-project-info.h"
+#include <libide-core.h>
+#include <libide-projects.h>
G_BEGIN_DECLS
diff --git a/src/libide/greeter/ide-greeter-workspace-actions.c
b/src/libide/greeter/ide-greeter-workspace-actions.c
new file mode 100644
index 000000000..344a8ca10
--- /dev/null
+++ b/src/libide/greeter/ide-greeter-workspace-actions.c
@@ -0,0 +1,223 @@
+/* ide-greeter-workspace-actions.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-greeter-workspace-actions"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-greeter-private.h"
+#include "ide-greeter-workspace.h"
+
+static void
+ide_greeter_workspace_dialog_response (IdeGreeterWorkspace *self,
+ gint response_id,
+ GtkFileChooserDialog *dialog)
+{
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (GTK_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ if (response_id == GTK_RESPONSE_OK)
+ {
+ g_autoptr(IdeProjectInfo) project_info = NULL;
+ g_autoptr(GFile) project_file = NULL;
+
+ project_file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+
+ project_info = ide_project_info_new ();
+ ide_project_info_set_file (project_info, project_file);
+
+ ide_greeter_workspace_open_project (self, project_info);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+ide_greeter_workspace_dialog_notify_filter (IdeGreeterWorkspace *self,
+ GParamSpec *pspec,
+ GtkFileChooserDialog *dialog)
+{
+ GtkFileFilter *filter;
+ GtkFileChooserAction action;
+
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (pspec != NULL);
+ g_assert (GTK_IS_FILE_CHOOSER_DIALOG (dialog));
+
+ filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (dialog));
+
+ if (filter && g_object_get_data (G_OBJECT (filter), "IS_DIRECTORY"))
+ action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+ else
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+
+ gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), action);
+}
+
+static void
+ide_greeter_workspace_actions_open (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeGreeterWorkspace *self = user_data;
+ GtkFileChooserDialog *dialog;
+ GtkFileFilter *all_filter;
+ const GList *list;
+ gint64 last_priority = G_MAXINT64;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (param == NULL);
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ list = peas_engine_get_plugin_list (peas_engine_get_default ());
+
+ dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "action", GTK_FILE_CHOOSER_ACTION_OPEN,
+ "transient-for", self,
+ "modal", TRUE,
+ "title", _("Open Project"),
+ "visible", TRUE,
+ NULL);
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Open"), GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ g_signal_connect_object (dialog,
+ "notify::filter",
+ G_CALLBACK (ide_greeter_workspace_dialog_notify_filter),
+ self,
+ G_CONNECT_SWAPPED);
+
+ all_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (all_filter, _("All Project Types"));
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), all_filter);
+
+ /* For testing with no plugins */
+ if (list == NULL)
+ gtk_file_filter_add_pattern (all_filter, "*");
+
+ for (; list != NULL; list = list->next)
+ {
+ PeasPluginInfo *plugin_info = list->data;
+ GtkFileFilter *filter;
+ const gchar *pattern;
+ const gchar *content_type;
+ const gchar *name;
+ const gchar *priority;
+ gchar **patterns;
+ gchar **content_types;
+ gint i;
+
+ if (!peas_plugin_info_is_loaded (plugin_info))
+ continue;
+
+ name = peas_plugin_info_get_external_data (plugin_info, "X-Project-File-Filter-Name");
+ if (name == NULL)
+ continue;
+
+ pattern = peas_plugin_info_get_external_data (plugin_info, "X-Project-File-Filter-Pattern");
+ content_type = peas_plugin_info_get_external_data (plugin_info, "X-Project-File-Filter-Content-Type");
+ priority = peas_plugin_info_get_external_data (plugin_info, "X-Project-File-Filter-Priority");
+
+ if (pattern == NULL && content_type == NULL)
+ continue;
+
+ patterns = g_strsplit (pattern ?: "", ",", 0);
+ content_types = g_strsplit (content_type ?: "", ",", 0);
+
+ filter = gtk_file_filter_new ();
+
+ gtk_file_filter_set_name (filter, name);
+
+ for (i = 0; patterns [i] != NULL; i++)
+ {
+ if (*patterns [i])
+ {
+ gtk_file_filter_add_pattern (filter, patterns [i]);
+ gtk_file_filter_add_pattern (all_filter, patterns [i]);
+ }
+ }
+
+ for (i = 0; content_types [i] != NULL; i++)
+ {
+ if (*content_types [i])
+ {
+ gtk_file_filter_add_mime_type (filter, content_types [i]);
+ gtk_file_filter_add_mime_type (all_filter, content_types [i]);
+
+ /* Helper so we can change the file chooser action to OPEN_DIRECTORY,
+ * otherwise the user won't be able to choose a directory, it will
+ * instead dive into the directory.
+ */
+ if (g_strcmp0 (content_types [i], "inode/directory") == 0)
+ g_object_set_data (G_OBJECT (filter), "IS_DIRECTORY", GINT_TO_POINTER (1));
+ }
+ }
+
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ /* Look at the priority to set the default file filter. */
+ if (priority != NULL)
+ {
+ gint64 pval = g_ascii_strtoll (priority, NULL, 10);
+
+ if (pval < last_priority)
+ {
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+ last_priority = pval;
+ }
+ }
+
+ g_strfreev (patterns);
+ g_strfreev (content_types);
+ }
+
+ g_signal_connect_object (dialog,
+ "response",
+ G_CALLBACK (ide_greeter_workspace_dialog_response),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /* If unset, set the default filter */
+ if (last_priority == G_MAXINT64)
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), all_filter);
+
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
+ ide_get_projects_dir ());
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static const GActionEntry actions[] = {
+ { "open", ide_greeter_workspace_actions_open },
+};
+
+void
+_ide_greeter_workspace_init_actions (IdeGreeterWorkspace *self)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+
+ g_action_map_add_action_entries (G_ACTION_MAP (self), actions, G_N_ELEMENTS (actions), self);
+}
diff --git a/src/libide/greeter/ide-greeter-workspace-shortcuts.c
b/src/libide/greeter/ide-greeter-workspace-shortcuts.c
new file mode 100644
index 000000000..18a1a9472
--- /dev/null
+++ b/src/libide/greeter/ide-greeter-workspace-shortcuts.c
@@ -0,0 +1,44 @@
+/* ide-greeter-workspace-shortcuts.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-greeter-workspace-shortcuts"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "ide-greeter-private.h"
+#include "ide-greeter-workspace.h"
+
+#define I_(s) (g_intern_static_string(s))
+
+void
+_ide_greeter_workspace_init_shortcuts (IdeGreeterWorkspace *self)
+{
+ DzlShortcutController *controller;
+
+ controller = dzl_shortcut_controller_find (GTK_WIDGET (self));
+
+ dzl_shortcut_controller_add_command_action (controller,
+ I_("org.gnome.builder.greeter.close"),
+ "<Primary>w",
+ DZL_SHORTCUT_PHASE_DISPATCH,
+ I_("win.close"));
+}
diff --git a/src/libide/greeter/ide-greeter-workspace.c b/src/libide/greeter/ide-greeter-workspace.c
new file mode 100644
index 000000000..2a31ce4a7
--- /dev/null
+++ b/src/libide/greeter/ide-greeter-workspace.c
@@ -0,0 +1,808 @@
+/* ide-greeter-workspace.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-greeter-workspace"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-clone-surface.h"
+#include "ide-greeter-private.h"
+#include "ide-greeter-workspace.h"
+
+/**
+ * SECTION:ide-greeter-workspace
+ * @title: IdeGreeterWorkspace
+ * @short_description: The greeter upon starting Builder
+ *
+ * Use the #IdeWorkspace APIs to add surfaces for user guides such
+ * as the git workflow or project creation wizard.
+ *
+ * You can add buttons to the headerbar and use actions to change
+ * surfaces such as "win.surface::'surface-name'".
+ *
+ * Since: 3.32
+ */
+
+struct _IdeGreeterWorkspace
+{
+ IdeWorkspace parent_instance;
+
+ PeasExtensionSet *addins;
+ DzlPatternSpec *pattern_spec;
+ GSimpleAction *delete_action;
+ GSimpleAction *purge_action;
+
+ /* Template Widgets */
+ IdeCloneSurface *clone_surface;
+ IdeHeaderBar *header_bar;
+ DzlPriorityBox *sections;
+ DzlPriorityBox *left_box;
+ GtkStack *surfaces;
+ IdeSurface *sections_surface;
+ GtkSearchEntry *search_entry;
+ GtkButton *back_button;
+ GtkButton *select_button;
+ GtkActionBar *action_bar;
+
+ guint selection_mode : 1;
+};
+
+G_DEFINE_TYPE (IdeGreeterWorkspace, ide_greeter_workspace, IDE_TYPE_WORKSPACE)
+
+enum {
+ PROP_0,
+ PROP_SELECTION_MODE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_greeter_workspace_filter_sections (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeGreeterWorkspace *self = user_data;
+ IdeGreeterSection *section = (IdeGreeterSection *)exten;
+ gboolean has_child;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_GREETER_SECTION (section));
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ has_child = ide_greeter_section_filter (section, self->pattern_spec);
+
+ gtk_widget_set_visible (GTK_WIDGET (section), has_child);
+}
+
+static void
+ide_greeter_workspace_apply_filter_all (IdeGreeterWorkspace *self)
+{
+ const gchar *text;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ g_clear_pointer (&self->pattern_spec, dzl_pattern_spec_unref);
+
+ if (NULL != (text = gtk_entry_get_text (GTK_ENTRY (self->search_entry))))
+ self->pattern_spec = dzl_pattern_spec_new (text);
+
+ if (self->addins != NULL)
+ peas_extension_set_foreach (self->addins,
+ ide_greeter_workspace_filter_sections,
+ self);
+}
+
+static void
+ide_greeter_workspace_activate_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ gboolean *handled = user_data;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (handled != NULL);
+
+ if (!IDE_IS_GREETER_SECTION (widget))
+ return;
+
+ if (!*handled)
+ *handled = ide_greeter_section_activate_first (IDE_GREETER_SECTION (widget));
+}
+
+static void
+ide_greeter_workspace_search_entry_activate (IdeGreeterWorkspace *self,
+ GtkSearchEntry *search_entry)
+{
+ gboolean handled = FALSE;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (GTK_IS_SEARCH_ENTRY (search_entry));
+
+ gtk_container_foreach (GTK_CONTAINER (self->sections),
+ ide_greeter_workspace_activate_cb,
+ &handled);
+
+ if (!handled)
+ gdk_window_beep (gtk_widget_get_window (GTK_WIDGET (search_entry)));
+}
+
+static void
+ide_greeter_workspace_search_entry_changed (IdeGreeterWorkspace *self,
+ GtkSearchEntry *search_entry)
+{
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (GTK_IS_SEARCH_ENTRY (search_entry));
+
+ ide_greeter_workspace_apply_filter_all (self);
+}
+
+static void
+stack_notify_visible_child_cb (IdeGreeterWorkspace *self,
+ GParamSpec *pspec,
+ GtkStack *stack)
+{
+ g_autofree gchar *title = NULL;
+ GtkWidget *visible_child;
+ gboolean sections;
+
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (GTK_IS_STACK (stack));
+
+ visible_child = gtk_stack_get_visible_child (stack);
+
+ if (DZL_IS_DOCK_ITEM (visible_child))
+ title = dzl_dock_item_get_title (DZL_DOCK_ITEM (visible_child));
+
+ gtk_header_bar_set_title (GTK_HEADER_BAR (self->header_bar), title);
+
+ sections = ide_str_equal0 ("sections", gtk_stack_get_visible_child_name (stack));
+
+ gtk_widget_set_visible (GTK_WIDGET (self->left_box), sections);
+ gtk_widget_set_visible (GTK_WIDGET (self->back_button), !sections);
+ gtk_widget_set_visible (GTK_WIDGET (self->select_button), sections);
+}
+
+static void
+ide_greeter_workspace_addin_added_cb (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeGreeterSection *section = (IdeGreeterSection *)exten;
+ IdeGreeterWorkspace *self = user_data;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_GREETER_SECTION (section));
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ /* Don't allow floating, to work with extension set*/
+ if (g_object_is_floating (G_OBJECT (section)))
+ g_object_ref_sink (section);
+
+ gtk_widget_show (GTK_WIDGET (section));
+
+ ide_greeter_workspace_add_section (self, section);
+}
+
+static void
+ide_greeter_workspace_addin_removed_cb (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeGreeterSection *section = (IdeGreeterSection *)exten;
+ IdeGreeterWorkspace *self = user_data;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_GREETER_SECTION (section));
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ gtk_widget_destroy (GTK_WIDGET (section));
+}
+
+static void
+ide_greeter_workspace_constructed (GObject *object)
+{
+ IdeGreeterWorkspace *self = (IdeGreeterWorkspace *)object;
+
+ G_OBJECT_CLASS (ide_greeter_workspace_parent_class)->constructed (object);
+
+ dzl_gtk_widget_add_style_class (GTK_WIDGET (self), "greeter");
+
+ self->addins = peas_extension_set_new (peas_engine_get_default (),
+ IDE_TYPE_GREETER_SECTION,
+ NULL);
+
+ g_signal_connect (self->addins,
+ "extension-added",
+ G_CALLBACK (ide_greeter_workspace_addin_added_cb),
+ self);
+
+ g_signal_connect (self->addins,
+ "extension-removed",
+ G_CALLBACK (ide_greeter_workspace_addin_removed_cb),
+ self);
+
+ peas_extension_set_foreach (self->addins,
+ ide_greeter_workspace_addin_added_cb,
+ self);
+
+ /* Ensure that no plugin changed our page */
+ ide_workspace_set_visible_surface_name (IDE_WORKSPACE (self), "sections");
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
+}
+
+static void
+ide_greeter_workspace_open_project_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeWorkbench *workbench = (IdeWorkbench *)object;
+ g_autoptr(IdeGreeterWorkspace) self = (IdeGreeterWorkspace *)user_data;
+ g_autoptr(GError) error = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_WORKBENCH (workbench));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ if (!ide_workbench_load_project_finish (workbench, result, &error))
+ {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (workbench),
+ GTK_DIALOG_USE_HEADER_BAR,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to load the project"));
+
+ g_object_set (dialog,
+ "modal", TRUE,
+ "secondary-text", error->message,
+ NULL);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+ g_signal_connect_swapped (dialog,
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ workbench);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ ide_greeter_workspace_end (self);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (self));
+
+ IDE_EXIT;
+}
+
+/**
+ * ide_greeter_workspace_open_project:
+ * @self: an #IdeGreeterWorkspace
+ * @project_info: an #IdeProjectInfo
+ *
+ * Opens the project described by @project_info.
+ *
+ * This is useful by greeter workspace extensions that add new surfaces
+ * which may not have other means to activate a project.
+ *
+ * Since: 3.32
+ */
+void
+ide_greeter_workspace_open_project (IdeGreeterWorkspace *self,
+ IdeProjectInfo *project_info)
+{
+ IdeWorkbench *workbench;
+ const gchar *vcs_uri = NULL;
+ GFile *file;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+ g_return_if_fail (IDE_IS_PROJECT_INFO (project_info));
+
+ /* If there is a VCS Uri and no project file/directory, then we want
+ * to switch to the clone dialog. However, we can use the VCS Uri to
+ * determine what the check-out directory would be, and if so, we can
+ * just open that directory.
+ */
+ if (!ide_project_info_get_file (project_info) &&
+ !ide_project_info_get_directory (project_info) &&
+ (vcs_uri = ide_project_info_get_vcs_uri (project_info)))
+ {
+ g_autoptr(IdeVcsUri) uri = ide_vcs_uri_new (vcs_uri);
+ g_autofree gchar *suggested = NULL;
+ g_autofree gchar *checkout = NULL;
+
+ if (uri != NULL &&
+ (suggested = ide_vcs_uri_get_clone_name (uri)) &&
+ (checkout = g_build_filename (ide_get_projects_dir (), suggested, NULL)) &&
+ g_file_test (checkout, G_FILE_TEST_IS_DIR))
+ {
+ g_autoptr(GFile) directory = g_file_new_for_path (checkout);
+ ide_project_info_set_directory (project_info, directory);
+ }
+ else
+ {
+ ide_clone_surface_set_uri (self->clone_surface, vcs_uri);
+ ide_workspace_set_visible_surface_name (IDE_WORKSPACE (self), "clone");
+ return;
+ }
+ }
+
+ workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+
+ ide_greeter_workspace_begin (self);
+
+ if (ide_project_info_get_directory (project_info) == NULL)
+ {
+ if ((file = ide_project_info_get_file (project_info)))
+ {
+ g_autoptr(GFile) parent = g_file_get_parent (file);
+
+ /* If it's a directory, set that too, otherwise use the parent */
+ if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY)
+ ide_project_info_set_directory (project_info, file);
+ else
+ ide_project_info_set_directory (project_info, parent);
+ }
+ }
+
+ ide_workbench_load_project_async (workbench,
+ project_info,
+ IDE_TYPE_PRIMARY_WORKSPACE,
+ ide_workspace_get_cancellable (IDE_WORKSPACE (self)),
+ ide_greeter_workspace_open_project_cb,
+ g_object_ref (self));
+
+ IDE_EXIT;
+}
+
+static void
+ide_greeter_workspace_project_activated_cb (IdeGreeterWorkspace *self,
+ IdeProjectInfo *project_info,
+ IdeGreeterSection *section)
+{
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (IDE_IS_PROJECT_INFO (project_info));
+ g_assert (IDE_IS_GREETER_SECTION (section));
+
+ ide_greeter_workspace_open_project (self, project_info);
+}
+
+static void
+ide_greeter_workspace_delete_selected_rows_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ if (IDE_IS_GREETER_SECTION (widget))
+ ide_greeter_section_delete_selected (IDE_GREETER_SECTION (widget));
+}
+
+static void
+ide_greeter_workspace_delete_selected_rows (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeGreeterWorkspace *self = user_data;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (param == NULL);
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ gtk_container_foreach (GTK_CONTAINER (self->sections),
+ ide_greeter_workspace_delete_selected_rows_cb,
+ NULL);
+ ide_greeter_workspace_apply_filter_all (self);
+ ide_greeter_workspace_set_selection_mode (self, FALSE);
+}
+
+static void
+ide_greeter_workspace_purge_selected_rows_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ if (IDE_IS_GREETER_SECTION (widget))
+ ide_greeter_section_purge_selected (IDE_GREETER_SECTION (widget));
+}
+
+static void
+purge_selected_rows_response (IdeGreeterWorkspace *self,
+ gint response,
+ GtkDialog *dialog)
+{
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+ g_assert (GTK_IS_DIALOG (dialog));
+
+ if (response == GTK_RESPONSE_OK)
+ {
+ gtk_container_foreach (GTK_CONTAINER (self->sections),
+ ide_greeter_workspace_purge_selected_rows_cb,
+ NULL);
+ ide_greeter_workspace_apply_filter_all (self);
+ ide_greeter_workspace_set_selection_mode (self, FALSE);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+ide_greeter_workspace_purge_selected_rows (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeGreeterWorkspace *self = user_data;
+ GtkWidget *parent;
+ GtkWidget *button;
+ GtkDialog *dialog;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (param == NULL);
+ g_assert (IDE_IS_GREETER_WORKSPACE (self));
+
+ parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW);
+ dialog = g_object_new (GTK_TYPE_MESSAGE_DIALOG,
+ "modal", TRUE,
+ "transient-for", parent,
+ "attached-to", parent,
+ "text", _("Removing project sources will delete them from your computer and cannot
be undone."),
+ NULL);
+ gtk_dialog_add_buttons (dialog,
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Delete Project Sources"), GTK_RESPONSE_OK,
+ NULL);
+ button = gtk_dialog_get_widget_for_response (dialog, GTK_RESPONSE_OK);
+ dzl_gtk_widget_add_style_class (button, "destructive-action");
+ g_signal_connect_data (dialog,
+ "response",
+ G_CALLBACK (purge_selected_rows_response),
+ g_object_ref (self),
+ (GClosureNotify)g_object_unref,
+ G_CONNECT_SWAPPED);
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+ide_greeter_workspace_destroy (GtkWidget *widget)
+{
+ IdeGreeterWorkspace *self = (IdeGreeterWorkspace *)widget;
+
+ g_clear_object (&self->addins);
+ g_clear_object (&self->delete_action);
+ g_clear_object (&self->purge_action);
+ g_clear_pointer (&self->pattern_spec, dzl_pattern_spec_unref);
+
+ GTK_WIDGET_CLASS (ide_greeter_workspace_parent_class)->destroy (widget);
+}
+
+static void
+ide_greeter_workspace_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeGreeterWorkspace *self = IDE_GREETER_WORKSPACE (object);
+
+ switch (prop_id)
+ {
+ case PROP_SELECTION_MODE:
+ g_value_set_boolean (value, ide_greeter_workspace_get_selection_mode (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_greeter_workspace_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeGreeterWorkspace *self = IDE_GREETER_WORKSPACE (object);
+
+ switch (prop_id)
+ {
+ case PROP_SELECTION_MODE:
+ ide_greeter_workspace_set_selection_mode (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_greeter_workspace_class_init (IdeGreeterWorkspaceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ IdeWorkspaceClass *workspace_class = IDE_WORKSPACE_CLASS (klass);
+
+ object_class->constructed = ide_greeter_workspace_constructed;
+ object_class->get_property = ide_greeter_workspace_get_property;
+ object_class->set_property = ide_greeter_workspace_set_property;
+
+ widget_class->destroy = ide_greeter_workspace_destroy;
+
+ /**
+ * IdeGreeterWorkspace:selection-mode:
+ *
+ * The "selection-mode" property indicates if the workspace allows
+ * selecting existing projects and removing them, including source files
+ * and cached data.
+ *
+ * This is usually used by the checkmark button to toggle selections.
+ *
+ * Since: 3.32
+ */
+ properties [PROP_SELECTION_MODE] =
+ g_param_spec_boolean ("selection-mode",
+ "Selection Mode",
+ "If the workspace is in selection mode",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ ide_workspace_class_set_kind (workspace_class, "greeter");
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/ui/ide-greeter-workspace.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, action_bar);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, back_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, clone_surface);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, header_bar);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, left_box);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, select_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, surfaces);
+ gtk_widget_class_bind_template_child (widget_class, IdeGreeterWorkspace, sections);
+ gtk_widget_class_bind_template_callback (widget_class, stack_notify_visible_child_cb);
+
+ g_type_ensure (IDE_TYPE_CLONE_SURFACE);
+}
+
+static void
+ide_greeter_workspace_init (IdeGreeterWorkspace *self)
+{
+ g_autoptr(GPropertyAction) selection_action = NULL;
+ static const GActionEntry actions[] = {
+ { "purge-selected-rows", ide_greeter_workspace_purge_selected_rows },
+ { "delete-selected-rows", ide_greeter_workspace_delete_selected_rows },
+ };
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ selection_action = g_property_action_new ("selection-mode", G_OBJECT (self), "selection-mode");
+ g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (selection_action));
+ g_action_map_add_action_entries (G_ACTION_MAP (self), actions, G_N_ELEMENTS (actions), self);
+
+ g_signal_connect_object (self->search_entry,
+ "activate",
+ G_CALLBACK (ide_greeter_workspace_search_entry_activate),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->search_entry,
+ "changed",
+ G_CALLBACK (ide_greeter_workspace_search_entry_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ stack_notify_visible_child_cb (self, NULL, self->surfaces);
+
+ _ide_greeter_workspace_init_actions (self);
+ _ide_greeter_workspace_init_shortcuts (self);
+}
+
+IdeGreeterWorkspace *
+ide_greeter_workspace_new (IdeApplication *app)
+{
+ return g_object_new (IDE_TYPE_GREETER_WORKSPACE,
+ "application", app,
+ "default-width", 1000,
+ "default-height", 600,
+ NULL);
+}
+
+/**
+ * ide_greeter_workspace_add_section:
+ * @self: a #IdeGreeterWorkspace
+ * @section: an #IdeGreeterSection based #GtkWidget
+ *
+ * Adds the #IdeGreeterSection to the display.
+ *
+ * Since: 3.32
+ */
+void
+ide_greeter_workspace_add_section (IdeGreeterWorkspace *self,
+ IdeGreeterSection *section)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+ g_return_if_fail (IDE_IS_GREETER_SECTION (section));
+
+ g_signal_connect_object (section,
+ "project-activated",
+ G_CALLBACK (ide_greeter_workspace_project_activated_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ gtk_container_add_with_properties (GTK_CONTAINER (self->sections), GTK_WIDGET (section),
+ "priority", ide_greeter_section_get_priority (section),
+ NULL);
+}
+
+/**
+ * ide_greeter_workspace_remove_section:
+ * @self: a #IdeGreeterWorkspace
+ * @section: an #IdeGreeterSection based #GtkWidget
+ *
+ * Remvoes the #IdeGreeterSection from the display. This should be a section
+ * that was previously added with ide_greeter_workspace_add_section().
+ *
+ * Plugins should clean up after themselves when they are unloaded, which may
+ * include calling this function.
+ *
+ * Since: 3.32
+ */
+void
+ide_greeter_workspace_remove_section (IdeGreeterWorkspace *self,
+ IdeGreeterSection *section)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+ g_return_if_fail (IDE_IS_GREETER_SECTION (section));
+
+ gtk_container_remove (GTK_CONTAINER (self->sections), GTK_WIDGET (section));
+}
+
+void
+ide_greeter_workspace_add_button (IdeGreeterWorkspace *self,
+ GtkWidget *button,
+ gint priority)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+ g_return_if_fail (GTK_IS_WIDGET (button));
+
+ gtk_container_add_with_properties (GTK_CONTAINER (self->left_box), button,
+ "priority", priority,
+ NULL);
+}
+
+/**
+ * ide_greeter_workspace_begin:
+ * @self: a #IdeGreeterWorkspace
+ *
+ * This function will disable various actions and should be called before
+ * an #IdeGreeterAddin begins doing work that cannot be undone except to
+ * cancel the operation.
+ *
+ * Actions such as switching guides will be disabled during this process.
+ *
+ * See ide_greeter_workspace_end() to restore actions.
+ *
+ * Since: 3.32
+ */
+void
+ide_greeter_workspace_begin (IdeGreeterWorkspace *self)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+
+ dzl_gtk_widget_action_set (GTK_WIDGET (self), "win", "open",
+ "enabled", FALSE,
+ NULL);
+ dzl_gtk_widget_action_set (GTK_WIDGET (self), "win", "surface",
+ "enabled", FALSE,
+ NULL);
+}
+
+/**
+ * ide_greeter_workspace_end:
+ * @self: a #IdeGreeterWorkspace
+ *
+ * Restores actions after a call to ide_greeter_workspace_begin().
+ *
+ * Since: 3.32
+ */
+void
+ide_greeter_workspace_end (IdeGreeterWorkspace *self)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+
+ dzl_gtk_widget_action_set (GTK_WIDGET (self), "win", "open",
+ "enabled", TRUE,
+ NULL);
+ dzl_gtk_widget_action_set (GTK_WIDGET (self), "win", "surface",
+ "enabled", TRUE,
+ NULL);
+}
+
+/**
+ * ide_greeter_workspace_get_selection_mode:
+ * @self: a #IdeGreeterWorkspace
+ *
+ * Gets if the greeter is in selection mode, which means that the workspace
+ * allows selecting projects for removal.
+ *
+ * Returns: %TRUE if in selection mode, otherwise %FALSE
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_greeter_workspace_get_selection_mode (IdeGreeterWorkspace *self)
+{
+ g_return_val_if_fail (IDE_IS_GREETER_WORKSPACE (self), FALSE);
+
+ return self->selection_mode;
+}
+
+static void
+ide_greeter_workspace_set_selection_mode_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ if (IDE_IS_GREETER_SECTION (widget))
+ ide_greeter_section_set_selection_mode (IDE_GREETER_SECTION (widget),
+ GPOINTER_TO_INT (user_data));
+}
+
+/**
+ * ide_greeter_workspace_set_selection_mode:
+ * @self: a #IdeGreeterWorkspace
+ * @selection_mode: if the workspace should be in selection mode
+ *
+ * Sets the workspace in selection mode.
+ *
+ * Since: 3.32
+ */
+void
+ide_greeter_workspace_set_selection_mode (IdeGreeterWorkspace *self,
+ gboolean selection_mode)
+{
+ g_return_if_fail (IDE_IS_GREETER_WORKSPACE (self));
+
+ selection_mode = !!selection_mode;
+
+ if (selection_mode != self->selection_mode)
+ {
+ self->selection_mode = selection_mode;
+ gtk_container_foreach (GTK_CONTAINER (self->sections),
+ ide_greeter_workspace_set_selection_mode_cb,
+ GINT_TO_POINTER (selection_mode));
+ gtk_widget_set_visible (GTK_WIDGET (self->action_bar), selection_mode);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTION_MODE]);
+ }
+}
diff --git a/src/libide/greeter/ide-greeter-workspace.h b/src/libide/greeter/ide-greeter-workspace.h
new file mode 100644
index 000000000..d3b0dd749
--- /dev/null
+++ b/src/libide/greeter/ide-greeter-workspace.h
@@ -0,0 +1,61 @@
+/* ide-greeter-workspace.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-projects.h>
+#include <libide-gui.h>
+
+#include "ide-greeter-section.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_GREETER_WORKSPACE (ide_greeter_workspace_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeGreeterWorkspace, ide_greeter_workspace, IDE, GREETER_WORKSPACE, IdeWorkspace)
+
+IDE_AVAILABLE_IN_3_32
+IdeGreeterWorkspace *ide_greeter_workspace_new (IdeApplication *app);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_add_section (IdeGreeterWorkspace *self,
+ IdeGreeterSection *section);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_remove_section (IdeGreeterWorkspace *self,
+ IdeGreeterSection *section);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_add_button (IdeGreeterWorkspace *self,
+ GtkWidget *button,
+ gint priority);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_begin (IdeGreeterWorkspace *self);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_end (IdeGreeterWorkspace *self);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_greeter_workspace_get_selection_mode (IdeGreeterWorkspace *self);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_set_selection_mode (IdeGreeterWorkspace *self,
+ gboolean selection_mode);
+IDE_AVAILABLE_IN_3_32
+void ide_greeter_workspace_open_project (IdeGreeterWorkspace *self,
+ IdeProjectInfo *project_info);
+
+
+G_END_DECLS
diff --git a/src/libide/greeter/ide-greeter-workspace.ui b/src/libide/greeter/ide-greeter-workspace.ui
new file mode 100644
index 000000000..905e14441
--- /dev/null
+++ b/src/libide/greeter/ide-greeter-workspace.ui
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.24 -->
+ <template class="IdeGreeterWorkspace" parent="IdeWorkspace">
+ <child type="titlebar">
+ <object class="IdeHeaderBar" id="header_bar">
+ <property name="menu-id">ide-greeter-workspace-menu</property>
+ <property name="show-fullscreen-button">false</property>
+ <property name="show-close-button">true</property>
+ <property name="visible">true</property>
+ <child type="left">
+ <object class="GtkButton" id="back_button">
+ <property name="action-name">win.surface</property>
+ <property name="action-target">'sections'</property>
+ <property name="has-tooltip">true</property>
+ <property name="tooltip-text" translatable="yes">Go back</property>
+ <property name="margin-end">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="icon-name">pan-start-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="left">
+ <object class="DzlPriorityBox" id="left_box">
+ <property name="spacing">10</property>
+ <property name="hexpand">false</property>
+ <property name="homogeneous">true</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkButton">
+ <property name="action-name">win.open</property>
+ <property name="label" translatable="yes">_Open…</property>
+ <property name="use-underline">true</property>
+ <property name="visible">true</property>
+ </object>
+ <packing>
+ <property name="priority">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="action-name">win.surface</property>
+ <property name="action-target">'clone'</property>
+ <property name="label" translatable="yes">_Clone…</property>
+ <property name="use-underline">true</property>
+ <property name="visible">true</property>
+ </object>
+ <packing>
+ <property name="priority">10</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child type="right">
+ <object class="GtkToggleButton" id="select_button">
+ <property name="action-name">win.selection-mode</property>
+ <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>
+ </object>
+ </child>
+ <child internal-child="surfaces">
+ <object class="GtkStack" id="surfaces">
+ <property name="transition-type">crossfade</property>
+ <signal name="notify::visible-child" handler="stack_notify_visible_child_cb"
object="IdeGreeterWorkspace" swapped="true"/>
+ <child>
+ <object class="IdeSurface" id="sections_surface">
+ <property name="title" translatable="yes">Select a Project</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="sectionssurface"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="expand">true</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkBox">
+ <property name="margin">32</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">24</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="halign">center</property>
+ <property name="visible">true</property>
+ <property name="width-chars">45</property>
+ </object>
+ </child>
+ <child>
+ <object class="DzlPriorityBox" id="sections">
+ <property name="orientation">vertical</property>
+ <property name="spacing">32</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkActionBar" id="action_bar">
+ <child>
+ <object class="GtkButton" id="remove_button">
+ <property name="action-name">win.delete-selected-rows</property>
+ <property name="label" translatable="yes">_Remove Projects</property>
+ <property name="use-underline">true</property>
+ <property name="visible">true</property>
+ <property name="sensitive">false</property>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="purge_button">
+ <property name="action-name">win.purge-selected-rows</property>
+ <property name="label" translatable="yes">Remove Projects and Sources…</property>
+ <property name="use-underline">true</property>
+ <property name="visible">true</property>
+ <property name="sensitive">false</property>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">sections</property>
+ </packing>
+ </child>
+ <child>
+ <object class="IdeCloneSurface" id="clone_surface">
+ <property name="title" translatable="yes">Clone Project</property>
+ <property name="visible">true</property>
+ </object>
+ <packing>
+ <property name="name">clone</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/greeter/libide-greeter.gresource.xml b/src/libide/greeter/libide-greeter.gresource.xml
new file mode 100644
index 000000000..002f2abb2
--- /dev/null
+++ b/src/libide/greeter/libide-greeter.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/builder/ui">
+ <file preprocess="xml-stripblanks">ide-clone-surface.ui</file>
+ <file preprocess="xml-stripblanks">ide-greeter-workspace.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/libide/greeter/libide-greeter.h b/src/libide/greeter/libide-greeter.h
new file mode 100644
index 000000000..bce96a180
--- /dev/null
+++ b/src/libide/greeter/libide-greeter.h
@@ -0,0 +1,34 @@
+/* ide-greeter.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+#include <libide-gui.h>
+#include <libide-projects.h>
+#include <libide-threading.h>
+
+#define IDE_GREETER_INSIDE
+
+#include "ide-clone-surface.h"
+#include "ide-greeter-section.h"
+#include "ide-greeter-workspace.h"
+
+#undef IDE_GREETER_INSIDE
diff --git a/src/libide/greeter/meson.build b/src/libide/greeter/meson.build
index d74c836fd..68e932c0d 100644
--- a/src/libide/greeter/meson.build
+++ b/src/libide/greeter/meson.build
@@ -1,18 +1,88 @@
-greeter_headers = [
+libide_greeter_header_subdir = join_paths(libide_header_subdir, 'greeter')
+libide_include_directories += include_directories('.')
+
+libide_greeter_generated_headers = []
+
+#
+# Public API Headers
+#
+
+libide_greeter_public_headers = [
+ 'ide-clone-surface.h',
'ide-greeter-section.h',
+ 'ide-greeter-workspace.h',
+ 'libide-greeter.h',
+]
+
+libide_greeter_private_headers = [
+ 'ide-greeter-private.h',
]
-greeter_sources = [
+install_headers(libide_greeter_public_headers, subdir: libide_greeter_header_subdir)
+
+#
+# Sources
+#
+
+libide_greeter_public_sources = [
+ 'ide-clone-surface.c',
'ide-greeter-section.c',
+ 'ide-greeter-workspace.c',
+]
+
+libide_greeter_private_sources = [
+ 'ide-greeter-workspace-actions.c',
+ 'ide-greeter-workspace-shortcuts.c',
]
-greeter_private_sources = [
- 'ide-greeter-perspective.c',
- 'ide-greeter-perspective.h',
+#
+# Generated Resource Files
+#
+
+libide_greeter_resources = gnome.compile_resources(
+ 'ide-greeter-resources',
+ 'libide-greeter.gresource.xml',
+ c_name: 'ide_greeter',
+)
+libide_greeter_generated_headers += [libide_greeter_resources[1]]
+libide_greeter_private_sources += libide_greeter_resources[0]
+
+
+#
+# Dependencies
+#
+
+libide_greeter_deps = [
+ libgio_dep,
+ libgtk_dep,
+ libdazzle_dep,
+
+ libide_core_dep,
+ libide_gui_dep,
+ libide_io_dep,
+ libide_threading_dep,
+ libide_vcs_dep,
]
-libide_public_headers += files(greeter_headers)
-libide_public_sources += files(greeter_sources)
-libide_private_sources += files(greeter_private_sources)
+#
+# Library Definitions
+#
+
+libide_greeter = static_library('ide-greeter-' + libide_api_version,
+ libide_greeter_public_sources + libide_greeter_private_sources,
+ dependencies: libide_greeter_deps,
+ c_args: libide_args + release_args + ['-DIDE_GREETER_COMPILATION'],
+)
+
+libide_greeter_dep = declare_dependency(
+ sources: libide_greeter_private_headers + libide_greeter_generated_headers,
+ dependencies: libide_greeter_deps,
+ link_whole: libide_greeter,
+ include_directories: include_directories('.'),
+)
-install_headers(greeter_headers, subdir: join_paths(libide_header_subdir, 'greeter'))
+gnome_builder_public_sources += files(libide_greeter_public_sources)
+gnome_builder_public_headers += files(libide_greeter_public_headers)
+gnome_builder_generated_headers += libide_greeter_generated_headers
+gnome_builder_include_subdirs += libide_greeter_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-greeter.h', '-DIDE_GREETER_COMPILATION']
diff --git a/src/plugins/greeter/gbp-greeter-application-addin.c
b/src/plugins/greeter/gbp-greeter-application-addin.c
new file mode 100644
index 000000000..7fd0a2d93
--- /dev/null
+++ b/src/plugins/greeter/gbp-greeter-application-addin.c
@@ -0,0 +1,229 @@
+/* gbp-greeter-application-addin.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-greeter-application-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-greeter.h>
+#include <libide-gui.h>
+
+#include "gbp-greeter-application-addin.h"
+
+struct _GbpGreeterApplicationAddin
+{
+ GObject parent_instance;
+ IdeApplication *application;
+};
+
+static void
+present_greeter_with_surface (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ GbpGreeterApplicationAddin *self = user_data;
+ g_autoptr(IdeWorkbench) workbench = NULL;
+ IdeGreeterWorkspace *workspace;
+ const gchar *name;
+
+ g_assert (!action || G_IS_SIMPLE_ACTION (action));
+ g_assert (!param || g_variant_is_of_type (param, G_VARIANT_TYPE_STRING));
+ g_assert (GBP_IS_GREETER_APPLICATION_ADDIN (self));
+ g_assert (IDE_IS_APPLICATION (self->application));
+
+ workbench = ide_workbench_new ();
+ ide_application_add_workbench (self->application, workbench);
+
+ workspace = ide_greeter_workspace_new (self->application);
+ ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+ if (param != NULL && (name = g_variant_get_string (param, NULL)))
+ ide_workspace_set_visible_surface_name (IDE_WORKSPACE (workspace), name);
+
+ ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+}
+
+static void
+open_project (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ GbpGreeterApplicationAddin *self = user_data;
+ g_autoptr(IdeWorkbench) workbench = NULL;
+ IdeGreeterWorkspace *workspace;
+
+ g_assert (!action || G_IS_SIMPLE_ACTION (action));
+ g_assert (!param || g_variant_is_of_type (param, G_VARIANT_TYPE_STRING));
+ g_assert (GBP_IS_GREETER_APPLICATION_ADDIN (self));
+ g_assert (IDE_IS_APPLICATION (self->application));
+
+ workbench = ide_workbench_new ();
+ ide_application_add_workbench (self->application, workbench);
+
+ workspace = ide_greeter_workspace_new (self->application);
+ ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+ ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+
+ dzl_gtk_widget_action (GTK_WIDGET (workspace), "win", "open", NULL);
+}
+
+static const GActionEntry actions[] = {
+ { "present-greeter-with-surface", present_greeter_with_surface, "s" },
+ { "open-project", open_project },
+};
+
+static void
+gbp_greeter_application_addin_add_option_entries (IdeApplicationAddin *addin,
+ IdeApplication *app)
+{
+ g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+ g_assert (G_IS_APPLICATION (app));
+
+ g_application_add_main_option (G_APPLICATION (app),
+ "greeter",
+ 'g',
+ G_OPTION_FLAG_IN_MAIN,
+ G_OPTION_ARG_NONE,
+ _("Display a new greeter window"),
+ NULL);
+
+ g_application_add_main_option (G_APPLICATION (app),
+ "clone",
+ 0,
+ G_OPTION_FLAG_IN_MAIN,
+ G_OPTION_ARG_STRING,
+ _("Begin cloning project from URI"),
+ "URI");
+}
+
+static void
+gbp_greeter_application_addin_handle_command_line (IdeApplicationAddin *addin,
+ IdeApplication *application,
+ GApplicationCommandLine *cmdline)
+{
+ GbpGreeterApplicationAddin *self = (GbpGreeterApplicationAddin *)addin;
+ g_auto(GStrv) argv = NULL;
+ GVariantDict *dict;
+ const gchar *clone_uri = NULL;
+ gint argc;
+
+ g_assert (GBP_IS_GREETER_APPLICATION_ADDIN (self));
+ g_assert (IDE_IS_APPLICATION (application));
+ g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+ dict = g_application_command_line_get_options_dict (cmdline);
+ argv = ide_application_get_argv (IDE_APPLICATION (application), cmdline);
+ argc = g_strv_length (argv);
+
+ /*
+ * If we are processing the arguments for the startup of the primary
+ * instance, then we want to show the greeter if no arguments are
+ * provided. (That means argc == 1, the programe executable).
+ *
+ * Also, if they provided --greeter or -g we'll show a new greeter.
+ */
+ if ((!g_application_command_line_get_is_remote (cmdline) && argc == 1) ||
+ g_variant_dict_contains (dict, "greeter"))
+ {
+ present_greeter_with_surface (NULL, NULL, addin);
+ return;
+ }
+
+ /*
+ * If the --clone=URI option was provided, switch the greeter to the
+ * clone surface and begin cloning.
+ */
+ if (dict != NULL && g_variant_dict_lookup (dict, "clone", "&s", &clone_uri))
+ {
+ IdeGreeterWorkspace *workspace;
+ IdeWorkbench *workbench;
+ IdeSurface *surface;
+
+ workbench = ide_workbench_new ();
+ ide_application_add_workbench (self->application, workbench);
+
+ workspace = ide_greeter_workspace_new (self->application);
+ ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+ surface = ide_workspace_get_surface_by_name (IDE_WORKSPACE (workspace), "clone");
+ ide_workspace_set_visible_surface (IDE_WORKSPACE (workspace), surface);
+
+ if (IDE_IS_CLONE_SURFACE (surface))
+ ide_clone_surface_set_uri (IDE_CLONE_SURFACE (surface), clone_uri);
+
+ ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+ }
+}
+
+static void
+gbp_greeter_application_addin_load (IdeApplicationAddin *addin,
+ IdeApplication *application)
+{
+ GbpGreeterApplicationAddin *self = (GbpGreeterApplicationAddin *)addin;
+
+ g_assert (GBP_IS_GREETER_APPLICATION_ADDIN (self));
+ g_assert (IDE_IS_APPLICATION (application));
+
+ self->application = application;
+
+ g_action_map_add_action_entries (G_ACTION_MAP (application),
+ actions,
+ G_N_ELEMENTS (actions),
+ self);
+}
+
+static void
+gbp_greeter_application_addin_unload (IdeApplicationAddin *addin,
+ IdeApplication *application)
+{
+ GbpGreeterApplicationAddin *self = (GbpGreeterApplicationAddin *)addin;
+
+ g_assert (GBP_IS_GREETER_APPLICATION_ADDIN (self));
+ g_assert (IDE_IS_APPLICATION (application));
+
+ for (guint i = 0; i < G_N_ELEMENTS (actions); i++)
+ g_action_map_remove_action (G_ACTION_MAP (application), actions[i].name);
+
+ self->application = NULL;
+}
+
+static void
+application_addin_iface_init (IdeApplicationAddinInterface *iface)
+{
+ iface->load = gbp_greeter_application_addin_load;
+ iface->unload = gbp_greeter_application_addin_unload;
+ iface->add_option_entries = gbp_greeter_application_addin_add_option_entries;
+ iface->handle_command_line = gbp_greeter_application_addin_handle_command_line;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpGreeterApplicationAddin, gbp_greeter_application_addin, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_APPLICATION_ADDIN, application_addin_iface_init))
+
+static void
+gbp_greeter_application_addin_class_init (GbpGreeterApplicationAddinClass *klass)
+{
+}
+
+static void
+gbp_greeter_application_addin_init (GbpGreeterApplicationAddin *self)
+{
+}
diff --git a/src/plugins/greeter/gbp-greeter-application-addin.h
b/src/plugins/greeter/gbp-greeter-application-addin.h
new file mode 100644
index 000000000..eb1f26b86
--- /dev/null
+++ b/src/plugins/greeter/gbp-greeter-application-addin.h
@@ -0,0 +1,31 @@
+/* gbp-greeter-application-addin.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GREETER_APPLICATION_ADDIN (gbp_greeter_application_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGreeterApplicationAddin, gbp_greeter_application_addin, GBP,
GREETER_APPLICATION_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/greeter/greeter-plugin.c b/src/plugins/greeter/greeter-plugin.c
new file mode 100644
index 000000000..5d9c55d57
--- /dev/null
+++ b/src/plugins/greeter/greeter-plugin.c
@@ -0,0 +1,36 @@
+/* greeter-plugin.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "greeter-plugin"
+
+#include "config.h"
+
+#include <libpeas/peas.h>
+#include <libide-greeter.h>
+
+#include "gbp-greeter-application-addin.h"
+
+_IDE_EXTERN void
+_gbp_greeter_register_types (PeasObjectModule *module)
+{
+ peas_object_module_register_extension_type (module,
+ IDE_TYPE_APPLICATION_ADDIN,
+ GBP_TYPE_GREETER_APPLICATION_ADDIN);
+}
diff --git a/src/plugins/greeter/greeter.gresource.xml b/src/plugins/greeter/greeter.gresource.xml
new file mode 100644
index 000000000..13594ee06
--- /dev/null
+++ b/src/plugins/greeter/greeter.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/plugins/greeter">
+ <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+ <file>greeter.plugin</file>
+ </gresource>
+</gresources>
diff --git a/src/plugins/greeter/greeter.plugin b/src/plugins/greeter/greeter.plugin
new file mode 100644
index 000000000..c0114abc4
--- /dev/null
+++ b/src/plugins/greeter/greeter.plugin
@@ -0,0 +1,13 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2014-2018 Christian Hergert
+Description=Builder's greeter window
+Embedded=_gbp_greeter_register_types
+Hidden=true
+Module=greeter
+Name=Greeter
+X-At-Startup=true
+X-Project-File-Filter-Content-Type=inode/directory
+X-Project-File-Filter-Name=Directory
+X-Project-File-Filter-Priority=-100
diff --git a/src/plugins/greeter/gtk/menus.ui b/src/plugins/greeter/gtk/menus.ui
new file mode 100644
index 000000000..ff260345b
--- /dev/null
+++ b/src/plugins/greeter/gtk/menus.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <menu id="ide-greeter-workspace-menu">
+ <section id="ide-greeter-workspace-menu-projects">
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-open</attribute>
+ <attribute name="label" translatable="yes">_Open Project</attribute>
+ <attribute name="action">win.open</attribute>
+ </item>
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-clone</attribute>
+ <attribute name="label" translatable="yes">_Clone Repository</attribute>
+ <attribute name="action">win.surface</attribute>
+ <attribute name="target" type="s">'clone'</attribute>
+ </item>
+ </section>
+ <section id="ide-greeter-workspace-menu-close">
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-close</attribute>
+ <attribute name="label" translatable="yes">Close</attribute>
+ <attribute name="action">win.close</attribute>
+ </item>
+ </section>
+ <section id="ide-greeter-workspace-menu-app">
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-preferences</attribute>
+ <attribute name="label" translatable="yes">Preferences</attribute>
+ <attribute name="action">app.preferences</attribute>
+ <attribute name="accel"><primary>comma</attribute>
+ </item>
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-shortcuts</attribute>
+ <attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
+ <attribute name="action">app.shortcuts</attribute>
+ <attribute name="accel"><primary>question</attribute>
+ </item>
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-help</attribute>
+ <attribute name="label" translatable="yes">Help</attribute>
+ <attribute name="action">app.help</attribute>
+ <attribute name="accel">F1</attribute>
+ </item>
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-about</attribute>
+ <attribute name="label" translatable="yes">About Builder</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </section>
+ <section id="ide-greeter-workspace-menu-quit-section">
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-quit</attribute>
+ <attribute name="label" translatable="yes">Quit</attribute>
+ <attribute name="action">app.quit</attribute>
+ </item>
+ </section>
+ <!--
+ <section id="ide-greeter-workspace-menu-debug-section">
+ <attribute name="label" translatable="yes">Debugging</attribute>
+ <item>
+ <attribute name="id">ide-greeter-workspace-menu-stats</attribute>
+ <attribute name="label" translatable="yes">Type Statistics</attribute>
+ <attribute name="action">app.about:types</attribute>
+ </item>
+ </section>
+ -->
+ </menu>
+ <menu id="ide-primary-workspace-menu">
+ <section id="ide-primary-workspace-menu-projects-section">
+ <item>
+ <attribute name="id">ide-primary-workspace-menu-open</attribute>
+ <attribute name="label" translatable="yes">_Open Project</attribute>
+ <attribute name="action">app.open-project</attribute>
+ </item>
+ <item>
+ <attribute name="id">ide-primary-workspace-menu-clone</attribute>
+ <attribute name="label" translatable="yes">_Clone Repository</attribute>
+ <attribute name="action">app.present-greeter-with-surface</attribute>
+ <attribute name="target" type="s">'clone'</attribute>
+ </item>
+ </section>
+ </menu>
+ <menu id="ide-editor-workspace-menu">
+ <section id="ide-editor-workspace-menu-projects-section">
+ <item>
+ <attribute name="id">ide-editor-workspace-menu-open</attribute>
+ <attribute name="label" translatable="yes">_Open Project</attribute>
+ <attribute name="action">app.open-project</attribute>
+ </item>
+ <item>
+ <attribute name="id">ide-editor-workspace-menu-clone</attribute>
+ <attribute name="label" translatable="yes">_Clone Repository</attribute>
+ <attribute name="action">app.present-greeter-with-surface</attribute>
+ <attribute name="target" type="s">'clone'</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/plugins/greeter/meson.build b/src/plugins/greeter/meson.build
new file mode 100644
index 000000000..736f31520
--- /dev/null
+++ b/src/plugins/greeter/meson.build
@@ -0,0 +1,12 @@
+plugins_sources += files([
+ 'greeter-plugin.c',
+ 'gbp-greeter-application-addin.c',
+])
+
+plugin_greeter_resources = gnome.compile_resources(
+ 'gbp-greeter-resources',
+ 'greeter.gresource.xml',
+ c_name: 'gbp_greeter',
+)
+
+plugins_sources += plugin_greeter_resources[0]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]