[gtksourceview/wip/chergert/gsv-gtk4] assistants: add new assistants subsystem



commit e31b204c63b8d03845851041adf19b73d5abaa66
Author: Christian Hergert <chergert redhat com>
Date:   Sat Aug 29 08:43:59 2020 -0700

    assistants: add new assistants subsystem
    
    This subsystem is meant to be a base for popup based tooling around a
    GtkSourceView. It can be used for the underlying layer of completion,
    interactive tooltips, error bubbles, snippet tooltips, and more.
    
    A new completion engine will land first using this subsystem, but will
    keep the GtkSourceAssistant type private similar to how GDK does for
    internal (but exposed) types.

 gtksourceview/gtksourceassistant-private.h      |  59 ++++
 gtksourceview/gtksourceassistant.c              | 386 ++++++++++++++++++++++++
 gtksourceview/gtksourceassistantchild-private.h |  44 +++
 gtksourceview/gtksourceassistantchild.c         | 186 ++++++++++++
 gtksourceview/gtksourceview-assistants.c        | 171 +++++++++++
 gtksourceview/gtksourceview-private.h           |  56 ++++
 gtksourceview/gtksourceview.c                   |  30 +-
 gtksourceview/meson.build                       |   3 +
 8 files changed, 934 insertions(+), 1 deletion(-)
---
diff --git a/gtksourceview/gtksourceassistant-private.h b/gtksourceview/gtksourceassistant-private.h
new file mode 100644
index 000000000..7bc6d55fc
--- /dev/null
+++ b/gtksourceview/gtksourceassistant-private.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_ASSISTANT (_gtk_source_assistant_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GtkSourceAssistant, _gtk_source_assistant, GTK_SOURCE, ASSISTANT, GtkPopover)
+
+struct _GtkSourceAssistantClass
+{
+       GtkPopoverClass parent_class;
+
+       void (*get_offset)          (GtkSourceAssistant *assistant,
+                                    int                *x_offset,
+                                    int                *y_offset);
+       void (*get_target_location) (GtkSourceAssistant *assistant,
+                                    GdkRectangle       *rect);
+};
+
+GtkSourceAssistant *_gtk_source_assistant_new        (void);
+void                _gtk_source_assistant_attach     (GtkSourceAssistant *assistant,
+                                                      GtkSourceAssistant *attached_to);
+void                _gtk_source_assistant_detach     (GtkSourceAssistant *assistant);
+void                _gtk_source_assistant_get_offset (GtkSourceAssistant *assistant,
+                                                      int                *x,
+                                                      int                *y);
+GtkTextMark        *_gtk_source_assistant_get_mark   (GtkSourceAssistant *assistant);
+void                _gtk_source_assistant_set_mark   (GtkSourceAssistant *assistant,
+                                                      GtkTextMark        *mark);
+void                _gtk_source_assistant_set_child  (GtkSourceAssistant *assistant,
+                                                      GtkWidget          *child);
+void                _gtk_source_assistant_destroy    (GtkSourceAssistant *assistant);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourceassistant.c b/gtksourceview/gtksourceassistant.c
new file mode 100644
index 000000000..9471ae42e
--- /dev/null
+++ b/gtksourceview/gtksourceassistant.c
@@ -0,0 +1,386 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceassistant-private.h"
+#include "gtksourceassistantchild-private.h"
+#include "gtksourceview-private.h"
+
+typedef struct
+{
+       GtkTextMark             *mark;
+       GtkSourceAssistantChild *child;
+} GtkSourceAssistantPrivate;
+
+static void buildable_iface_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkSourceAssistant, _gtk_source_assistant, GTK_TYPE_POPOVER,
+                         G_ADD_PRIVATE (GtkSourceAssistant)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init))
+
+static GtkSourceView *
+_gtk_source_assistant_get_view (GtkSourceAssistant *assistant)
+{
+       GtkWidget *widget;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       widget = gtk_widget_get_ancestor (GTK_WIDGET (assistant), GTK_SOURCE_TYPE_VIEW);
+
+       g_return_val_if_fail (!widget || GTK_SOURCE_IS_VIEW (widget), NULL);
+
+       return GTK_SOURCE_VIEW (widget);
+}
+
+static void
+_gtk_source_assistant_hide_action (GtkWidget   *widget,
+                                   const gchar *action_name,
+                                   GVariant    *parameter)
+{
+       g_assert (GTK_SOURCE_IS_ASSISTANT (widget));
+
+       gtk_popover_popdown (GTK_POPOVER (widget));
+}
+
+static void
+_gtk_source_assistant_real_get_target_location (GtkSourceAssistant *assistant,
+                                                GdkRectangle       *location)
+{
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (assistant);
+       GtkSourceView *view;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+       g_assert (location != NULL);
+
+       view = _gtk_source_assistant_get_view (assistant);
+
+       if (view != NULL)
+       {
+               GtkTextBuffer *buffer;
+               GtkTextMark *mark;
+               GtkTextIter iter;
+
+               buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+               mark = priv->mark ? priv->mark : gtk_text_buffer_get_insert (buffer);
+               gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+               gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), &iter, location);
+       }
+       else
+       {
+               location->x = 0;
+               location->y = 0;
+               location->width = 0;
+               location->height = 0;
+       }
+}
+
+static void
+_gtk_source_assistant_get_target_location (GtkSourceAssistant *assistant,
+                                           GdkRectangle       *location)
+{
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+       g_assert (location != NULL);
+
+       GTK_SOURCE_ASSISTANT_GET_CLASS (assistant)->get_target_location (assistant, location);
+}
+
+static void
+_gtk_source_assistant_update_position (GtkSourceAssistant *assistant)
+{
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (assistant);
+       const GList *children = NULL;
+       GtkWidget *parent;
+       GdkRectangle rect;
+       int x = 0;
+       int y = 0;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       parent = gtk_widget_get_parent (GTK_WIDGET (assistant));
+
+       if (GTK_SOURCE_IS_VIEW (parent))
+       {
+               _gtk_source_assistant_get_offset (assistant, &x, &y);
+               _gtk_source_assistant_get_target_location (assistant, &rect);
+
+               gtk_popover_set_offset (GTK_POPOVER (assistant), x, y);
+               gtk_popover_set_pointing_to (GTK_POPOVER (assistant), &rect);
+       }
+
+       if (priv->child != NULL)
+       {
+               children = _gtk_source_assistant_child_get_attached (priv->child);
+       }
+
+       for (const GList *iter = children; iter; iter = iter->next)
+       {
+               GtkSourceAssistant *child = iter->data;
+
+               _gtk_source_assistant_get_offset (child, &x, &y);
+               gtk_popover_set_offset (GTK_POPOVER (child), x, y);
+
+               if (gtk_widget_get_visible (GTK_WIDGET (child)))
+               {
+                       gtk_native_check_resize (GTK_NATIVE (child));
+               }
+       }
+}
+
+static void
+_gtk_source_assistant_show (GtkWidget *widget)
+{
+       GtkSourceAssistant *assistant = (GtkSourceAssistant *)widget;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       _gtk_source_assistant_update_position (assistant);
+
+       GTK_WIDGET_CLASS (_gtk_source_assistant_parent_class)->show (widget);
+}
+
+static void
+_gtk_source_assistant_hide (GtkWidget *widget)
+{
+       GtkSourceAssistant *assistant = (GtkSourceAssistant *)widget;
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (assistant);
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       _gtk_source_assistant_child_hide (priv->child);
+
+       GTK_WIDGET_CLASS (_gtk_source_assistant_parent_class)->hide (widget);
+}
+
+static void
+_gtk_source_assistant_real_get_offset (GtkSourceAssistant *assistant,
+                                       int                *x,
+                                       int                *y)
+{
+       GtkStyleContext *style_context;
+       GtkBorder margin;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+       g_assert (x != NULL);
+       g_assert (y != NULL);
+
+       style_context = gtk_widget_get_style_context (GTK_WIDGET (assistant));
+       gtk_style_context_get_margin (style_context, &margin);
+
+       *x = -margin.left;
+       *y = -margin.top + 1;
+}
+
+static void
+_gtk_source_assistant_dispose (GObject *object)
+{
+       GtkSourceAssistant *self = (GtkSourceAssistant *)object;
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (self);
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT (self));
+
+       _gtk_source_assistant_detach (self);
+       g_clear_object (&priv->mark);
+
+       G_OBJECT_CLASS (_gtk_source_assistant_parent_class)->dispose (object);
+}
+
+static void
+_gtk_source_assistant_class_init (GtkSourceAssistantClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = _gtk_source_assistant_dispose;
+
+       widget_class->hide = _gtk_source_assistant_hide;
+       widget_class->show = _gtk_source_assistant_show;
+
+       klass->get_offset = _gtk_source_assistant_real_get_offset;
+       klass->get_target_location = _gtk_source_assistant_real_get_target_location;
+
+       gtk_widget_class_install_action (widget_class, "assistant.hide", NULL, 
_gtk_source_assistant_hide_action);
+       gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "assistant.hide", NULL);
+       gtk_widget_class_set_css_name (widget_class, "GtkSourceAssistant");
+}
+
+static void
+_gtk_source_assistant_init (GtkSourceAssistant *self)
+{
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (self);
+
+       gtk_widget_set_halign (GTK_WIDGET (self), GTK_ALIGN_START);
+       gtk_widget_set_valign (GTK_WIDGET (self), GTK_ALIGN_START);
+       gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_BOTTOM);
+       gtk_popover_set_has_arrow (GTK_POPOVER (self), FALSE);
+       gtk_popover_set_autohide (GTK_POPOVER (self), TRUE);
+
+       priv->child = _gtk_source_assistant_child_new ();
+       gtk_popover_set_child (GTK_POPOVER (self), GTK_WIDGET (priv->child));
+}
+
+GtkSourceAssistant *
+_gtk_source_assistant_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_ASSISTANT, NULL);
+}
+
+void
+_gtk_source_assistant_set_mark (GtkSourceAssistant *assistant,
+                                GtkTextMark        *mark)
+{
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (assistant);
+
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+       g_return_if_fail (GTK_IS_TEXT_MARK (mark));
+
+       if (g_set_object (&priv->mark, mark))
+       {
+               _gtk_source_assistant_update_position (assistant);
+       }
+}
+
+GtkTextMark *
+_gtk_source_assistant_get_mark (GtkSourceAssistant *assistant)
+{
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (assistant);
+
+       g_return_val_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant), NULL);
+
+       return priv->mark;
+}
+
+void
+_gtk_source_assistant_get_offset (GtkSourceAssistant *assistant,
+                                  int                *x,
+                                  int                *y)
+{
+       int dummy_x;
+       int dummy_y;
+
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       if (x == NULL)
+               x = &dummy_x;
+
+       if (y == NULL)
+               y = &dummy_y;
+
+       *x = 0;
+       *y = 0;
+
+       GTK_SOURCE_ASSISTANT_GET_CLASS (assistant)->get_offset (assistant, x, y);
+}
+
+void
+_gtk_source_assistant_detach (GtkSourceAssistant *assistant)
+{
+       GtkWidget *parent;
+
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       parent = gtk_widget_get_parent (GTK_WIDGET (assistant));
+
+       if (GTK_SOURCE_IS_ASSISTANT_CHILD (parent))
+       {
+               _gtk_source_assistant_child_detach (GTK_SOURCE_ASSISTANT_CHILD (parent),
+                                                   assistant);
+       }
+}
+
+void
+_gtk_source_assistant_attach (GtkSourceAssistant *assistant,
+                              GtkSourceAssistant *attach_to)
+{
+       GtkSourceAssistantPrivate *priv;
+
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+       g_return_if_fail (!attach_to || GTK_SOURCE_IS_ASSISTANT (attach_to));
+
+       if (attach_to == NULL)
+       {
+               _gtk_source_assistant_detach (assistant);
+       }
+       else
+       {
+               priv = _gtk_source_assistant_get_instance_private (attach_to);
+               _gtk_source_assistant_child_attach (priv->child, assistant);
+       }
+}
+
+void
+_gtk_source_assistant_set_child (GtkSourceAssistant *assistant,
+                                 GtkWidget          *child)
+{
+       GtkSourceAssistantPrivate *priv = _gtk_source_assistant_get_instance_private (assistant);
+
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+       g_return_if_fail (!child || GTK_IS_WIDGET (child));
+
+       _gtk_source_assistant_child_set_child (priv->child, child);
+}
+
+static void
+_gtk_source_assistant_add_child (GtkBuildable *buildable,
+                                 GtkBuilder   *builder,
+                                 GObject      *child,
+                                 const char   *type)
+{
+       GtkSourceAssistant *self = (GtkSourceAssistant *)buildable;
+
+       if (GTK_IS_WIDGET (child))
+       {
+               _gtk_source_assistant_set_child (self, GTK_WIDGET (child));
+       }
+}
+
+static void
+buildable_iface_init (GtkBuildableIface *iface)
+{
+       iface->add_child = _gtk_source_assistant_add_child;
+}
+
+void
+_gtk_source_assistant_destroy (GtkSourceAssistant *self)
+{
+       GtkWidget *parent;
+
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (self));
+
+       parent = gtk_widget_get_parent (GTK_WIDGET (self));
+
+       if (parent == NULL)
+               return;
+
+       if (GTK_SOURCE_IS_VIEW (parent))
+       {
+               _gtk_source_view_remove_assistant (GTK_SOURCE_VIEW (parent), self);
+       }
+       else if (GTK_SOURCE_IS_ASSISTANT_CHILD (parent))
+       {
+               _gtk_source_assistant_child_detach (GTK_SOURCE_ASSISTANT_CHILD (parent), self);
+       }
+       else
+       {
+               g_warning ("Cannot remove assistant from type %s",
+                          G_OBJECT_TYPE_NAME (parent));
+       }
+}
diff --git a/gtksourceview/gtksourceassistantchild-private.h b/gtksourceview/gtksourceassistantchild-private.h
new file mode 100644
index 000000000..cf2718343
--- /dev/null
+++ b/gtksourceview/gtksourceassistantchild-private.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes-private.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_ASSISTANT_CHILD (_gtk_source_assistant_child_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkSourceAssistantChild, _gtk_source_assistant_child, GTK_SOURCE, ASSISTANT_CHILD, 
GtkWidget)
+
+GtkSourceAssistantChild *_gtk_source_assistant_child_new          (void);
+void                     _gtk_source_assistant_child_hide         (GtkSourceAssistantChild *self);
+void                     _gtk_source_assistant_child_set_child    (GtkSourceAssistantChild *self,
+                                                                   GtkWidget               *child);
+void                     _gtk_source_assistant_child_attach       (GtkSourceAssistantChild *self,
+                                                                   GtkSourceAssistant      *child);
+void                     _gtk_source_assistant_child_detach       (GtkSourceAssistantChild *self,
+                                                                   GtkSourceAssistant      *child);
+const GList             *_gtk_source_assistant_child_get_attached (GtkSourceAssistantChild *self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourceassistantchild.c b/gtksourceview/gtksourceassistantchild.c
new file mode 100644
index 000000000..1981d251b
--- /dev/null
+++ b/gtksourceview/gtksourceassistantchild.c
@@ -0,0 +1,186 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceassistant-private.h"
+#include "gtksourceassistantchild-private.h"
+
+struct _GtkSourceAssistantChild
+{
+       GtkWidget parent_instance;
+       GtkWidget *child;
+       GQueue attached;
+};
+
+G_DEFINE_TYPE (GtkSourceAssistantChild, _gtk_source_assistant_child, GTK_TYPE_WIDGET)
+
+static void
+_gtk_source_assistant_child_size_allocate (GtkWidget *widget,
+                                           int        width,
+                                           int        height,
+                                           int        baseline)
+{
+       GtkSourceAssistantChild *child = (GtkSourceAssistantChild *)widget;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT_CHILD (child));
+
+       GTK_WIDGET_CLASS (_gtk_source_assistant_child_parent_class)->size_allocate (widget, width, height, 
baseline);
+
+       for (const GList *iter = child->attached.head; iter; iter = iter->next)
+       {
+               GtkSourceAssistant *attached = iter->data;
+
+               g_assert (GTK_SOURCE_IS_ASSISTANT (attached));
+               g_assert (GTK_IS_NATIVE (attached));
+
+               if (gtk_widget_get_visible (GTK_WIDGET (attached)))
+               {
+                       gtk_native_check_resize (GTK_NATIVE (attached));
+               }
+       }
+}
+
+static void
+_gtk_source_assistant_child_dispose (GObject *object)
+{
+       GtkSourceAssistantChild *self = (GtkSourceAssistantChild *)object;
+
+       g_assert (GTK_SOURCE_IS_ASSISTANT_CHILD (self));
+
+       while (self->attached.head != NULL)
+       {
+               GtkSourceAssistant *attached = self->attached.head->data;
+
+               g_assert (GTK_SOURCE_IS_ASSISTANT (attached));
+
+               _gtk_source_assistant_child_detach (self, attached);
+       }
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+       G_OBJECT_CLASS (_gtk_source_assistant_child_parent_class)->dispose (object);
+}
+
+static void
+_gtk_source_assistant_child_class_init (GtkSourceAssistantChildClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = _gtk_source_assistant_child_dispose;
+
+       widget_class->size_allocate = _gtk_source_assistant_child_size_allocate;
+
+       gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static void
+_gtk_source_assistant_child_init (GtkSourceAssistantChild *self)
+{
+}
+
+GtkSourceAssistantChild *
+_gtk_source_assistant_child_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_ASSISTANT_CHILD, NULL);
+}
+
+void
+_gtk_source_assistant_child_hide (GtkSourceAssistantChild *self)
+{
+       g_assert (GTK_SOURCE_IS_ASSISTANT_CHILD (self));
+
+       for (const GList *iter = self->attached.head; iter; iter = iter->next)
+       {
+               GtkSourceAssistant *attached = iter->data;
+
+               g_assert (GTK_SOURCE_IS_ASSISTANT (attached));
+               g_assert (GTK_IS_POPOVER (attached));
+
+               gtk_popover_popdown (GTK_POPOVER (attached));
+       }
+}
+
+void
+_gtk_source_assistant_child_detach (GtkSourceAssistantChild *self,
+                                    GtkSourceAssistant      *child)
+{
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT_CHILD (self));
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (child));
+
+       if (g_queue_remove (&self->attached, child))
+       {
+               gtk_widget_unparent (GTK_WIDGET (child));
+               g_object_unref (child);
+       }
+}
+
+void
+_gtk_source_assistant_child_attach (GtkSourceAssistantChild *self,
+                                    GtkSourceAssistant      *child)
+{
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT_CHILD (self));
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (child));
+       g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (child)) == NULL);
+
+       g_queue_push_tail (&self->attached, g_object_ref_sink (child));
+       gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (self));
+
+       if (GTK_IS_NATIVE (child))
+       {
+               if (gtk_widget_get_visible (GTK_WIDGET (child)))
+               {
+                       gtk_native_check_resize (GTK_NATIVE (child));
+               }
+       }
+}
+
+void
+_gtk_source_assistant_child_set_child (GtkSourceAssistantChild *self,
+                                       GtkWidget               *child)
+{
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT_CHILD (self));
+       g_return_if_fail (GTK_IS_WIDGET (child));
+
+       if (child == self->child)
+       {
+               return;
+       }
+
+       g_clear_pointer (&self->child, gtk_widget_unparent);
+
+       if (child != NULL)
+       {
+               self->child = child;
+               gtk_widget_set_parent (child, GTK_WIDGET (self));
+       }
+
+       gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+const GList *
+_gtk_source_assistant_child_get_attached (GtkSourceAssistantChild *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_ASSISTANT_CHILD (self), NULL);
+
+       return self->attached.head;
+}
diff --git a/gtksourceview/gtksourceview-assistants.c b/gtksourceview/gtksourceview-assistants.c
new file mode 100644
index 000000000..0422edecb
--- /dev/null
+++ b/gtksourceview/gtksourceview-assistants.c
@@ -0,0 +1,171 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtksourceview-private.h"
+
+void
+_gtk_source_view_assistants_init (GtkSourceViewAssistants *assistants,
+                                  GtkSourceView           *view)
+{
+       g_assert (assistants != NULL);
+       g_assert (assistants->view == NULL);
+
+       assistants->view = view;
+       g_queue_init (&assistants->queue);
+}
+
+void
+_gtk_source_view_assistants_shutdown (GtkSourceViewAssistants *assistants)
+{
+       g_assert (assistants != NULL);
+       g_assert (assistants->view != NULL);
+
+       while (assistants->queue.length > 0)
+       {
+               GtkSourceAssistant*assistant = g_queue_peek_head (&assistants->queue);
+               _gtk_source_view_assistants_remove (assistants, assistant);
+       }
+
+       assistants->view = NULL;
+
+       g_assert (assistants->view == NULL);
+       g_assert (g_queue_is_empty (&assistants->queue));
+}
+
+void
+_gtk_source_view_assistants_add (GtkSourceViewAssistants *assistants,
+                                 GtkSourceAssistant      *assistant)
+{
+       g_assert (assistants != NULL);
+       g_assert (assistants->view != NULL);
+
+       if (gtk_widget_get_parent (GTK_WIDGET (assistant)))
+       {
+               g_warning ("Cannot add assistant, it already has a parent");
+               return;
+       }
+
+       g_queue_push_tail (&assistants->queue, g_object_ref_sink (assistant));
+       gtk_widget_set_parent (GTK_WIDGET (assistant), GTK_WIDGET (assistants->view));
+}
+
+void
+_gtk_source_view_assistants_remove (GtkSourceViewAssistants *assistants,
+                                    GtkSourceAssistant      *assistant)
+{
+       GList *link;
+
+       g_assert (assistants != NULL);
+       g_assert (assistants->view != NULL);
+       g_assert (assistants->queue.length > 0);
+
+       link = g_queue_find (&assistants->queue, assistant);
+
+       if (link != NULL)
+       {
+               g_queue_delete_link (&assistants->queue, link);
+               gtk_widget_unparent (GTK_WIDGET (assistant));
+               g_object_unref (assistant);
+       }
+}
+
+void
+_gtk_source_view_assistants_size_allocate (GtkSourceViewAssistants *assistants,
+                                           int                      width,
+                                           int                      height,
+                                           int                      baseline)
+{
+       g_assert (assistants != NULL);
+
+       for (const GList *iter = assistants->queue.head; iter; iter = iter->next)
+       {
+               GtkSourceAssistant *assistant = iter->data;
+               int assistant_width;
+               int assistant_height;
+
+               g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+               gtk_widget_measure (GTK_WIDGET (assistant),
+                                   GTK_ORIENTATION_HORIZONTAL,
+                                   -1,
+                                   NULL,
+                                   &assistant_width,
+                                   NULL,
+                                   NULL);
+               gtk_widget_measure (GTK_WIDGET (assistant),
+                                   GTK_ORIENTATION_VERTICAL,
+                                   assistant_width,
+                                   NULL,
+                                   &assistant_height,
+                                   NULL,
+                                   NULL);
+
+               gtk_widget_set_size_request (GTK_WIDGET (assistant),
+                                            assistant_width,
+                                            assistant_height);
+
+               gtk_native_check_resize (GTK_NATIVE (assistant));
+       }
+}
+
+static gboolean
+_gtk_source_view_assistants_hide_all (GtkSourceViewAssistants *assistants)
+{
+       gboolean ret = FALSE;
+
+       g_assert (assistants != NULL);
+
+       for (const GList *iter = assistants->queue.head; iter; iter = iter->next)
+       {
+               GtkSourceAssistant *assistant = iter->data;
+
+               g_assert (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+               if (gtk_widget_get_visible (GTK_WIDGET (assistant)))
+               {
+                       gtk_popover_popdown (GTK_POPOVER (assistant));
+                       ret = TRUE;
+               }
+       }
+
+       return ret;
+}
+
+gboolean
+_gtk_source_view_assistants_handle_key (GtkSourceViewAssistants *assistants,
+                                        guint                    keyval,
+                                        GdkModifierType          state)
+{
+       g_assert (assistants != NULL);
+
+       if (keyval == GDK_KEY_Escape)
+       {
+               if (_gtk_source_view_assistants_hide_all (assistants))
+               {
+                       gtk_widget_grab_focus (GTK_WIDGET (assistants->view));
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
diff --git a/gtksourceview/gtksourceview-private.h b/gtksourceview/gtksourceview-private.h
new file mode 100644
index 000000000..515e1eb34
--- /dev/null
+++ b/gtksourceview/gtksourceview-private.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include "gtksourceview.h"
+#include "gtksourceassistant-private.h"
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+       GtkSourceView *view;
+       GQueue         queue;
+} GtkSourceViewAssistants;
+
+void     _gtk_source_view_add_assistant            (GtkSourceView           *view,
+                                                    GtkSourceAssistant      *assistant);
+void     _gtk_source_view_remove_assistant         (GtkSourceView           *view,
+                                                    GtkSourceAssistant      *assistant);
+void     _gtk_source_view_assistants_init          (GtkSourceViewAssistants *assistants,
+                                                    GtkSourceView           *view);
+void     _gtk_source_view_assistants_add           (GtkSourceViewAssistants *assistants,
+                                                    GtkSourceAssistant      *assistant);
+void     _gtk_source_view_assistants_remove        (GtkSourceViewAssistants *assistants,
+                                                    GtkSourceAssistant      *assistant);
+void     _gtk_source_view_assistants_remove        (GtkSourceViewAssistants *assistants,
+                                                    GtkSourceAssistant      *assistant);
+void     _gtk_source_view_assistants_shutdown      (GtkSourceViewAssistants *assistants);
+void     _gtk_source_view_assistants_size_allocate (GtkSourceViewAssistants *assistants,
+                                                    int                      width,
+                                                    int                      height,
+                                                    int                      baseline);
+gboolean _gtk_source_view_assistants_handle_key    (GtkSourceViewAssistants *assistant,
+                                                    guint                    keyval,
+                                                    GdkModifierType          state);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourceview.c b/gtksourceview/gtksourceview.c
index 38c8c42d0..95a8b292a 100644
--- a/gtksourceview/gtksourceview.c
+++ b/gtksourceview/gtksourceview.c
@@ -22,7 +22,7 @@
 
 #include "config.h"
 
-#include "gtksourceview.h"
+#include "gtksourceview-private.h"
 
 #include <string.h>
 #include <fribidi.h>
@@ -214,6 +214,8 @@ typedef struct
        GtkSourceSmartHomeEndType smart_home_end;
        GtkSourceBackgroundPatternType background_pattern;
 
+       GtkSourceViewAssistants assistants;
+
        guint background_pattern_color_set : 1;
        guint current_line_color_set : 1;
        guint right_margin_line_color_set : 1;
@@ -1357,6 +1359,8 @@ gtk_source_view_init (GtkSourceView *view)
        gtk_style_context_add_class (context, "sourceview");
 
        gtk_source_view_populate_extra_menu (view);
+
+       _gtk_source_view_assistants_init (&priv->assistants, view);
 }
 
 static void
@@ -4919,3 +4923,27 @@ gtk_source_view_queue_draw (GtkSourceView *view)
                _gtk_source_gutter_queue_draw (priv->right_gutter);
        }
 }
+
+void
+_gtk_source_view_add_assistant (GtkSourceView      *view,
+                                GtkSourceAssistant *assistant)
+{
+       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
+
+       g_return_if_fail (GTK_SOURCE_IS_VIEW (view));
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       _gtk_source_view_assistants_add (&priv->assistants, assistant);
+}
+
+void
+_gtk_source_view_remove_assistant (GtkSourceView      *view,
+                                   GtkSourceAssistant *assistant)
+{
+       GtkSourceViewPrivate *priv = gtk_source_view_get_instance_private (view);
+
+       g_return_if_fail (GTK_SOURCE_IS_VIEW (view));
+       g_return_if_fail (GTK_SOURCE_IS_ASSISTANT (assistant));
+
+       _gtk_source_view_assistants_remove (&priv->assistants, assistant);
+}
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index 9d3d26ba4..fc3a9cbae 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -85,6 +85,8 @@ core_public_c = files([
 ])
 
 core_private_c = files([
+  'gtksourceassistant.c',
+  'gtksourceassistantchild.c',
   'gtksourcebindinggroup.c',
   'gtksourcebufferinputstream.c',
   'gtksourcebufferinternal.c',
@@ -101,6 +103,7 @@ core_private_c = files([
   'gtksourcepixbufhelper.c',
   'gtksourceregex.c',
   'gtksourcesignalgroup.c',
+  'gtksourceview-assistants.c',
 ])
 
 core_c_args = [


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