[gnome-builder] libide/editor: add plumbing for editor info bar



commit b075aa03131fa663f383927b7ed05e83b50493bc
Author: Christian Hergert <chergert redhat com>
Date:   Sun Sep 4 09:13:10 2022 -0700

    libide/editor: add plumbing for editor info bar
    
    This is just the scaffolding for the infobar, it isn't functional yet as
    we need to wire up actions correctly.
    
    Related #1769

 src/libide/editor/ide-editor-info-bar-private.h |  33 +++
 src/libide/editor/ide-editor-info-bar.c         | 328 ++++++++++++++++++++++++
 src/libide/editor/ide-editor-info-bar.ui        | 103 ++++++++
 src/libide/editor/ide-editor-page.c             |   2 +
 src/libide/editor/ide-editor-page.ui            |   7 +
 src/libide/editor/libide-editor.gresource.xml   |   1 +
 src/libide/editor/meson.build                   |   2 +
 7 files changed, 476 insertions(+)
---
diff --git a/src/libide/editor/ide-editor-info-bar-private.h b/src/libide/editor/ide-editor-info-bar-private.h
new file mode 100644
index 000000000..e9644c2c6
--- /dev/null
+++ b/src/libide/editor/ide-editor-info-bar-private.h
@@ -0,0 +1,33 @@
+/* ide-editor-info-bar-private.h
+ *
+ * Copyright 2021-2022 Christian Hergert <unknown domain org>
+ *
+ * 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-code.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_INFO_BAR (ide_editor_info_bar_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorInfoBar, ide_editor_info_bar, IDE, EDITOR_INFO_BAR, GtkWidget)
+
+GtkWidget *_ide_editor_info_bar_new (IdeBuffer *buffer);
+
+G_END_DECLS
diff --git a/src/libide/editor/ide-editor-info-bar.c b/src/libide/editor/ide-editor-info-bar.c
new file mode 100644
index 000000000..5d470eb75
--- /dev/null
+++ b/src/libide/editor/ide-editor-info-bar.c
@@ -0,0 +1,328 @@
+/* ide-editor-info-bar.c
+ *
+ * Copyright 2021-2022 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-editor-info-bar"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+
+#include "ide-editor-info-bar-private.h"
+
+struct _IdeEditorInfoBar
+{
+  GtkWidget       parent_instance;
+
+  IdeBuffer *buffer;
+
+  GtkBox         *box;
+
+  /* Discard widgetry */
+  GtkInfoBar     *discard_infobar;
+  GtkButton      *discard;
+  GtkButton      *save;
+  GtkLabel       *title;
+  GtkLabel       *subtitle;
+
+  /* Permission denied infobar */
+  GtkInfoBar     *access_infobar;
+  GtkButton      *access_subtitle;
+  GtkButton      *access_title;
+  GtkButton      *access_try_admin;
+};
+
+enum {
+  PROP_0,
+  PROP_BUFFER,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdeEditorInfoBar, ide_editor_info_bar, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_editor_info_bar_update (IdeEditorInfoBar *self)
+{
+  g_assert (IDE_IS_EDITOR_INFO_BAR (self));
+  g_assert (IDE_IS_BUFFER (self->buffer));
+
+  /* Ignore things if we're busy to avoid flapping */
+  switch (ide_buffer_get_state (self->buffer))
+    {
+    case IDE_BUFFER_STATE_READY:
+    case IDE_BUFFER_STATE_FAILED:
+      break;
+
+    case IDE_BUFFER_STATE_LOADING:
+    case IDE_BUFFER_STATE_SAVING:
+    default:
+      gtk_info_bar_set_revealed (self->discard_infobar, FALSE);
+      return;
+    }
+
+  if (ide_buffer_get_changed_on_volume (self->buffer))
+    {
+      gtk_button_set_label (self->discard, _("_Discard Changes and Reload"));
+      gtk_button_set_use_underline (self->discard, TRUE);
+      gtk_actionable_set_action_name (GTK_ACTIONABLE (self->discard), "page.discard-changes");
+      gtk_label_set_label (self->title, _("File Has Changed on Disk"));
+      gtk_label_set_label (self->subtitle, _("The file has been changed by another program."));
+      gtk_widget_show (GTK_WIDGET (self->discard));
+      gtk_widget_hide (GTK_WIDGET (self->save));
+      gtk_info_bar_set_revealed (self->discard_infobar, TRUE);
+    }
+#if 0
+  /* TODO: If we end up doing drafts like g-t-e, we might need this */
+  else if (_ide_buffer_get_was_restored (self->buffer))
+    {
+      if (ide_buffer_get_file (self->buffer) == NULL)
+        {
+          gtk_button_set_label (self->save, _("Save _As…"));
+          gtk_actionable_set_action_name (GTK_ACTIONABLE (self->save), "page.save-as");
+          gtk_label_set_label (self->title, _("Buffer Restored"));
+          gtk_label_set_label (self->subtitle, _("Unsaved buffer has been restored."));
+          gtk_widget_hide (GTK_WIDGET (self->discard));
+          gtk_widget_show (GTK_WIDGET (self->save));
+        }
+      else
+        {
+          gtk_button_set_label (self->save, _("_Save…"));
+          gtk_actionable_set_action_name (GTK_ACTIONABLE (self->save), "page.confirm-save");
+          gtk_button_set_label (self->discard, _("_Discard…"));
+          gtk_actionable_set_action_name (GTK_ACTIONABLE (self->discard), "page.confirm-discard-changes");
+          gtk_label_set_label (self->title, _("Draft Changes Restored"));
+          gtk_label_set_label (self->subtitle, _("Unsaved changes to the buffer have been restored."));
+          gtk_widget_show (GTK_WIDGET (self->discard));
+          gtk_widget_show (GTK_WIDGET (self->save));
+        }
+
+      gtk_info_bar_set_revealed (self->discard_infobar, TRUE);
+    }
+#endif
+  else
+    {
+      gtk_info_bar_set_revealed (self->discard_infobar, FALSE);
+    }
+}
+
+static void
+ide_editor_info_bar_wrap_button_label (GtkButton *button)
+{
+  GtkWidget *label;
+
+  g_assert (GTK_IS_BUTTON (button));
+
+  label = gtk_button_get_child (button);
+  g_assert (GTK_IS_LABEL (label));
+
+  gtk_label_set_wrap (GTK_LABEL (label), TRUE);
+  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+}
+
+static void
+on_notify_cb (IdeEditorInfoBar *self,
+              GParamSpec       *pspec,
+              IdeBuffer        *buffer)
+{
+  g_assert (IDE_IS_EDITOR_INFO_BAR (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  ide_editor_info_bar_update (self);
+}
+
+static void
+on_response_cb (IdeEditorInfoBar *self,
+                int               response,
+                GtkInfoBar       *infobar)
+{
+  g_assert (IDE_IS_EDITOR_INFO_BAR (self));
+  g_assert (GTK_IS_INFO_BAR (infobar));
+
+  gtk_info_bar_set_revealed (infobar, FALSE);
+}
+
+static void
+on_try_admin_cb (IdeEditorInfoBar *self,
+                 GtkButton        *button)
+{
+  g_assert (IDE_IS_EDITOR_INFO_BAR (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+#if 0
+  _ide_buffer_use_admin (self->buffer);
+#endif
+}
+
+static void
+on_try_again_cb (IdeEditorInfoBar *self,
+                 GtkButton        *button)
+{
+  IdeBufferManager *buffer_manager;
+  IdeContext *context;
+
+  g_assert (IDE_IS_EDITOR_INFO_BAR (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  context = ide_widget_get_context (GTK_WIDGET (button));
+  buffer_manager = ide_buffer_manager_from_context (context);
+
+  ide_buffer_manager_load_file_async (buffer_manager,
+                                      ide_buffer_get_file (self->buffer),
+                                      IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD,
+                                      NULL, /* TODO: Progress */
+                                      NULL, NULL, NULL);
+}
+
+static void
+ide_editor_info_bar_dispose (GObject *object)
+{
+  IdeEditorInfoBar *self = (IdeEditorInfoBar *)object;
+
+  g_clear_object (&self->buffer);
+  g_clear_pointer ((GtkWidget **)&self->box, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (ide_editor_info_bar_parent_class)->dispose (object);
+}
+
+static void
+ide_editor_info_bar_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeEditorInfoBar *self = IDE_EDITOR_INFO_BAR (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUFFER:
+      g_value_set_object (value, self->buffer);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_info_bar_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeEditorInfoBar *self = IDE_EDITOR_INFO_BAR (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUFFER:
+      if (g_set_object (&self->buffer, g_value_get_object (value)))
+        {
+#if 0
+          /* TODO: If we end up suggesting admin:// URIs */
+          g_object_bind_property (self->buffer, "suggest-admin",
+                                  self->access_try_admin, "visible",
+                                  G_BINDING_SYNC_CREATE);
+#endif
+          g_object_bind_property (self->buffer, "failed",
+                                  self->access_infobar, "revealed",
+                                  G_BINDING_SYNC_CREATE);
+          g_signal_connect_object (self->buffer,
+                                   "notify::busy",
+                                   G_CALLBACK (on_notify_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+          g_signal_connect_object (self->buffer,
+                                   "notify::changed-on-volume",
+                                   G_CALLBACK (on_notify_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+        }
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_editor_info_bar_class_init (IdeEditorInfoBarClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = ide_editor_info_bar_dispose;
+  object_class->get_property = ide_editor_info_bar_get_property;
+  object_class->set_property = ide_editor_info_bar_set_property;
+
+  properties [PROP_BUFFER] =
+    g_param_spec_object ("buffer",
+                         "Buffer",
+                         "The buffer to monitor",
+                         IDE_TYPE_BUFFER,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-editor/ide-editor-info-bar.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, access_infobar);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, access_try_admin);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, access_subtitle);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, access_title);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, box);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, discard);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, discard_infobar);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, save);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, subtitle);
+  gtk_widget_class_bind_template_child (widget_class, IdeEditorInfoBar, title);
+  gtk_widget_class_bind_template_callback (widget_class, on_try_admin_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_try_again_cb);
+}
+
+static void
+ide_editor_info_bar_init (IdeEditorInfoBar *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  /*
+   * Ensure buttons with long labels can wrap text and are
+   * center-justified, so the infobar can fit narrow screens.
+   */
+  ide_editor_info_bar_wrap_button_label (self->access_try_admin);
+  ide_editor_info_bar_wrap_button_label (self->discard);
+
+  g_signal_connect_object (self->discard_infobar,
+                           "response",
+                           G_CALLBACK (on_response_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+GtkWidget *
+_ide_editor_info_bar_new (IdeBuffer *buffer)
+{
+  g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
+
+  return g_object_new (IDE_TYPE_EDITOR_INFO_BAR,
+                       "buffer", buffer,
+                       NULL);
+}
diff --git a/src/libide/editor/ide-editor-info-bar.ui b/src/libide/editor/ide-editor-info-bar.ui
new file mode 100644
index 000000000..d75e251ad
--- /dev/null
+++ b/src/libide/editor/ide-editor-info-bar.ui
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="IdeEditorInfoBar" parent="GtkWidget">
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkInfoBar" id="access_infobar">
+            <property name="message-type">error</property>
+            <property name="show-close-button">false</property>
+            <property name="revealed">false</property>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="access_title">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Could Not Open File</property>
+                    <property name="wrap">True</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="access_subtitle">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">You do not have permission to open the 
file.</property>
+                    <property name="wrap">True</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child type="action">
+              <object class="GtkButton" id="access_try_again">
+                <property name="label" translatable="yes">_Retry</property>
+                <property name="use-underline">True</property>
+                <signal name="clicked" handler="on_try_again_cb" swapped="true"/>
+              </object>
+            </child>
+            <child type="action">
+              <object class="GtkButton" id="access_try_admin">
+                <property name="label" translatable="yes">Open As _Administrator</property>
+                <property name="use-underline">True</property>
+                <property name="visible">false</property>
+                <signal name="clicked" handler="on_try_admin_cb" swapped="true"/>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkInfoBar" id="discard_infobar">
+            <property name="message-type">warning</property>
+            <property name="show-close-button">True</property>
+            <property name="revealed">False</property>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="title">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Document Restored</property>
+                    <property name="wrap">True</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="subtitle">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Unsaved document has been restored.</property>
+                    <property name="wrap">True</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child type="action">
+              <object class="GtkButton" id="discard">
+                <property name="label" translatable="yes">_Discard…</property>
+                <property name="use-underline">True</property>
+              </object>
+            </child>
+            <child type="action">
+              <object class="GtkButton" id="save">
+                <property name="margin-start">6</property>
+                <property name="use-underline">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="discard"/>
+      <widget name="save"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/src/libide/editor/ide-editor-page.c b/src/libide/editor/ide-editor-page.c
index 9747c9a39..8bf6ebb1e 100644
--- a/src/libide/editor/ide-editor-page.c
+++ b/src/libide/editor/ide-editor-page.c
@@ -27,6 +27,7 @@
 #include <libide-code.h>
 #include <libide-threading.h>
 
+#include "ide-editor-info-bar-private.h"
 #include "ide-editor-page-addin.h"
 #include "ide-editor-page-private.h"
 #include "ide-editor-print-operation.h"
@@ -680,6 +681,7 @@ ide_editor_page_class_init (IdeEditorPageClass *klass)
   panel_widget_class_install_action (panel_widget_class, "editor.print", NULL, print_action);
   panel_widget_class_install_action (panel_widget_class, "editor.format", NULL, format_action);
 
+  g_type_ensure (IDE_TYPE_EDITOR_INFO_BAR);
   g_type_ensure (IDE_TYPE_EDITOR_SEARCH_BAR);
 }
 
diff --git a/src/libide/editor/ide-editor-page.ui b/src/libide/editor/ide-editor-page.ui
index 74efd2763..2870675ab 100644
--- a/src/libide/editor/ide-editor-page.ui
+++ b/src/libide/editor/ide-editor-page.ui
@@ -2,6 +2,13 @@
 <interface>
   <template class="IdeEditorPage" parent="IdePage">
     <property name="can-maximize">true</property>
+    <child type="content">
+      <object class="IdeEditorInfoBar" id="info_bar">
+        <binding name="buffer">
+          <lookup name="buffer">IdeEditorPage</lookup>
+        </binding>
+      </object>
+    </child>
     <child type="content">
       <object class="IdeScrubberRevealer" id="scrubber_revealer">
         <property name="hexpand">true</property>
diff --git a/src/libide/editor/libide-editor.gresource.xml b/src/libide/editor/libide-editor.gresource.xml
index d9d0869bd..d29b0f8f5 100644
--- a/src/libide/editor/libide-editor.gresource.xml
+++ b/src/libide/editor/libide-editor.gresource.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gnome/libide-editor/">
+    <file preprocess="xml-stripblanks">ide-editor-info-bar.ui</file>
     <file preprocess="xml-stripblanks">ide-editor-page.ui</file>
     <file preprocess="xml-stripblanks">ide-editor-search-bar.ui</file>
     <file preprocess="xml-stripblanks">ide-editor-workspace.ui</file>
diff --git a/src/libide/editor/meson.build b/src/libide/editor/meson.build
index d4487cfa6..c92f82269 100644
--- a/src/libide/editor/meson.build
+++ b/src/libide/editor/meson.build
@@ -20,6 +20,7 @@ libide_editor_public_headers = [
 ]
 
 libide_editor_private_headers = [
+  'ide-editor-info-bar-private.h',
   'ide-editor-page-private.h',
   'ide-editor-print-operation.h',
   'ide-editor-save-delegate.h',
@@ -42,6 +43,7 @@ libide_editor_public_sources = [
 
 libide_editor_private_sources = [
   'ide-editor-init.c',
+  'ide-editor-info-bar.c',
   'ide-editor-page-settings.c',
   'ide-editor-print-operation.c',
   'ide-editor-save-delegate.c',


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