[gnome-builder/wip/gtk4-port: 1478/1774] plugins/shellcmd: more work on editing shell commands




commit b9877e865dde26d2c2e1f861d0ac93d2eb7c981b
Author: Christian Hergert <chergert redhat com>
Date:   Sat Jun 11 00:53:04 2022 -0700

    plugins/shellcmd: more work on editing shell commands
    
    This is just a lot of UI scaffolding as things are not wired up yet.

 src/plugins/shellcmd/gbp-shellcmd-command-dialog.c | 219 +++++++++++++++++++++
 src/plugins/shellcmd/gbp-shellcmd-command-dialog.h |  37 ++++
 .../shellcmd/gbp-shellcmd-command-dialog.ui        | 172 ++++++++++++++++
 .../shellcmd/gbp-shellcmd-preferences-addin.c      |  53 +++--
 src/plugins/shellcmd/meson.build                   |   1 +
 src/plugins/shellcmd/shellcmd.gresource.xml        |   1 +
 6 files changed, 452 insertions(+), 31 deletions(-)
---
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-dialog.c 
b/src/plugins/shellcmd/gbp-shellcmd-command-dialog.c
new file mode 100644
index 000000000..13d26b8fd
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-dialog.c
@@ -0,0 +1,219 @@
+/* gbp-shellcmd-command-dialog.c
+ *
+ * Copyright 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 "gbp-shellcmd-command-dialog"
+
+#include "config.h"
+
+#include <libide-gtk.h>
+
+#include "gbp-shellcmd-command-dialog.h"
+
+struct _GbpShellcmdCommandDialog
+{
+  AdwWindow              parent_instance;
+
+  GbpShellcmdRunCommand *command;
+
+  GtkStringList         *envvars;
+  GtkListBox            *envvars_list_box;
+};
+
+enum {
+  PROP_0,
+  PROP_COMMAND,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GbpShellcmdCommandDialog, gbp_shellcmd_command_dialog, ADW_TYPE_WINDOW)
+
+static GParamSpec *properties [N_PROPS];
+static GtkWidget *
+create_envvar_row_cb (gpointer item,
+                      gpointer user_data)
+{
+  GtkStringObject *obj = item;
+  const char *str = gtk_string_object_get_string (obj);
+  AdwActionRow *row;
+  GtkButton *button;
+
+  row = g_object_new (ADW_TYPE_ACTION_ROW,
+                      "title", str,
+                      "title-selectable", TRUE,
+                      NULL);
+  button = g_object_new (GTK_TYPE_BUTTON,
+                         "icon-name", "list-remove-symbolic",
+                         "css-classes", IDE_STRV_INIT ("flat", "circular"),
+                         "valign", GTK_ALIGN_CENTER,
+                         NULL);
+  adw_action_row_add_suffix (row, GTK_WIDGET (button));
+
+  return GTK_WIDGET (row);
+}
+
+static void
+on_env_entry_changed_cb (GbpShellcmdCommandDialog *self,
+                         IdeEntryPopover          *popover)
+{
+  gboolean valid = FALSE;
+  const char *text;
+  const char *eq;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_DIALOG (self));
+  g_assert (IDE_IS_ENTRY_POPOVER (popover));
+
+  text = ide_entry_popover_get_text (popover);
+  eq = strchr (text, '=');
+
+  if (eq != NULL && eq != text)
+    {
+      for (const char *iter = text; iter < eq; iter = g_utf8_next_char (iter))
+        {
+          gunichar ch = g_utf8_get_char (iter);
+
+          if (!g_unichar_isalnum (ch) && ch != '_')
+            goto failure;
+        }
+
+      if (g_ascii_isalpha (*text))
+        valid = TRUE;
+    }
+
+failure:
+  ide_entry_popover_set_ready (popover, valid);
+}
+
+static void
+on_env_entry_activate_cb (GbpShellcmdCommandDialog *self,
+                          const char               *text,
+                          IdeEntryPopover          *popover)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_DIALOG (self));
+  g_assert (IDE_IS_ENTRY_POPOVER (popover));
+  g_assert (GTK_IS_STRING_LIST (self->envvars));
+
+  gtk_string_list_append (self->envvars, text);
+  ide_entry_popover_set_text (popover, "");
+
+  IDE_EXIT;
+}
+
+static void
+gbp_shellcmd_command_dialog_dispose (GObject *object)
+{
+  GbpShellcmdCommandDialog *self = (GbpShellcmdCommandDialog *)object;
+
+  g_clear_object (&self->command);
+
+  G_OBJECT_CLASS (gbp_shellcmd_command_dialog_parent_class)->dispose (object);
+}
+
+static void
+gbp_shellcmd_command_dialog_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  GbpShellcmdCommandDialog *self = GBP_SHELLCMD_COMMAND_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      g_value_set_object (value, self->command);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shellcmd_command_dialog_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  GbpShellcmdCommandDialog *self = GBP_SHELLCMD_COMMAND_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      self->command = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shellcmd_command_dialog_class_init (GbpShellcmdCommandDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_shellcmd_command_dialog_dispose;
+  object_class->get_property = gbp_shellcmd_command_dialog_get_property;
+  object_class->set_property = gbp_shellcmd_command_dialog_set_property;
+
+  properties [PROP_COMMAND] =
+    g_param_spec_object ("command", NULL, NULL,
+                         GBP_TYPE_SHELLCMD_RUN_COMMAND,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/plugins/shellcmd/gbp-shellcmd-command-dialog.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpShellcmdCommandDialog, envvars);
+  gtk_widget_class_bind_template_child (widget_class, GbpShellcmdCommandDialog, envvars_list_box);
+  gtk_widget_class_bind_template_callback (widget_class, on_env_entry_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_env_entry_activate_cb);
+}
+
+static void
+gbp_shellcmd_command_dialog_init (GbpShellcmdCommandDialog *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+#if DEVELOPMENT_BUILD
+  gtk_widget_add_css_class (GTK_WIDGET (self), "devel");
+#endif
+
+  gtk_list_box_bind_model (self->envvars_list_box,
+                           G_LIST_MODEL (self->envvars),
+                           create_envvar_row_cb,
+                           NULL, NULL);
+  ide_gtk_widget_hide_when_empty (GTK_WIDGET (self->envvars_list_box),
+                                  G_LIST_MODEL (self->envvars));
+}
+
+GbpShellcmdCommandDialog *
+gbp_shellcmd_command_dialog_new (GbpShellcmdRunCommand *command)
+{
+  g_return_val_if_fail (GBP_IS_SHELLCMD_RUN_COMMAND (command), NULL);
+
+  return g_object_new (GBP_TYPE_SHELLCMD_COMMAND_DIALOG,
+                       "command", command,
+                       NULL);
+}
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-dialog.h 
b/src/plugins/shellcmd/gbp-shellcmd-command-dialog.h
new file mode 100644
index 000000000..f7765bae8
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-dialog.h
@@ -0,0 +1,37 @@
+/* gbp-shellcmd-command-dialog.h
+ *
+ * Copyright 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
+ */
+
+#pragma once
+
+#include <adwaita.h>
+
+#include <libide-foundry.h>
+
+#include "gbp-shellcmd-run-command.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHELLCMD_COMMAND_DIALOG (gbp_shellcmd_command_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShellcmdCommandDialog, gbp_shellcmd_command_dialog, GBP, SHELLCMD_COMMAND_DIALOG, 
AdwWindow)
+
+GbpShellcmdCommandDialog *gbp_shellcmd_command_dialog_new (GbpShellcmdRunCommand *command);
+
+G_END_DECLS
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-dialog.ui 
b/src/plugins/shellcmd/gbp-shellcmd-command-dialog.ui
new file mode 100644
index 000000000..890a038f9
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-dialog.ui
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpShellcmdCommandDialog" parent="AdwWindow">
+    <property name="default-width">650</property>
+    <property name="default-height">650</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="AdwHeaderBar">
+            <property name="show-start-title-buttons">false</property>
+            <property name="show-end-title-buttons">false</property>
+            <child type="start">
+              <object class="GtkButton" id="cancel">
+                <property name="label" translatable="yes">_Cancel</property>
+                <property name="use-underline">true</property>
+                <property name="width-request">100</property>
+                <property name="action-name">window.close</property>
+              </object>
+            </child>
+            <child type="end">
+              <object class="GtkButton" id="save">
+                <property name="label" translatable="yes">_Apply</property>
+                <property name="use-underline">true</property>
+                <property name="width-request">100</property>
+                <style>
+                  <class name="suggested-action"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesPage">
+            <property name="vexpand">true</property>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwEntryRow" id="display_name">
+                    <property name="title" translatable="yes">Name</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwEntryRow" id="command">
+                    <property name="title" translatable="yes">Command</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwEntryRow" id="location">
+                    <property name="title" translatable="yes">Location</property>
+                    <child type="suffix">
+                      <object class="GtkButton">
+                        <property name="valign">center</property>
+                        <property name="icon-name">folder-symbolic</property>
+                        <style>
+                          <class name="flat"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="label" translatable="yes">The command will be run from this location. 
Use &lt;tt&gt;$BUILDDIR&lt;/tt&gt;, &lt;tt&gt;$SRCDIR&lt;/tt&gt;, or &lt;tt&gt;$HOME&lt;/tt&gt; for a 
relative path.</property>
+                    <property name="use-markup">true</property>
+                    <property name="margin-top">12</property>
+                    <property name="wrap">true</property>
+                    <property name="xalign">0</property>
+                    <style>
+                      <class name="caption"/>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwActionRow" id="shortcut">
+                    <property name="title" translatable="yes">Keyboard Shortcut</property>
+                    <property name="icon-name">preferences-desktop-keyboard-shortcuts-symbolic</property>
+                    <child type="suffix">
+                      <object class="GtkShortcutsShortcut">
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <property name="title" translatable="yes">Environment</property>
+                <property name="header-suffix">
+                  <object class="GtkMenuButton">
+                    <style>
+                      <class name="flat"/>
+                    </style>
+                    <property name="direction">left</property>
+                    <property name="icon-name">list-add-symbolic</property>
+                    <property name="popover">
+                      <object class="IdeEntryPopover">
+                        <property name="title" translatable="yes">Add Environment Variable</property>
+                        <property name="button-text" translatable="yes">_Add</property>
+                        <signal name="changed" handler="on_env_entry_changed_cb" swapped="true" 
object="GbpShellcmdCommandDialog"/>
+                        <signal name="activate" handler="on_env_entry_activate_cb" swapped="true" 
object="GbpShellcmdCommandDialog"/>
+                      </object>
+                    </property>
+                  </object>
+                </property>
+                <child>
+                  <object class="GtkListBox" id="envvars_list_box">
+                    <property name="selection-mode">none</property>
+                    <style>
+                      <class name="boxed-list"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSeparator">
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="margin-top">6</property>
+            <property name="margin-bottom">6</property>
+            <property name="margin-start">6</property>
+            <property name="margin-end">6</property>
+            <style>
+              <class name="toolbar"/>
+            </style>
+            <child>
+              <object class="GtkButton">
+                <property name="label" translatable="yes">Delete Command</property>
+                <property name="halign">end</property>
+                <property name="hexpand">true</property>
+                <style>
+                  <class name="destructive-action"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="save"/>
+      <widget name="cancel"/>
+    </widgets>
+  </object>
+  <object class="GtkStringList" id="envvars">
+  </object>
+</interface>
diff --git a/src/plugins/shellcmd/gbp-shellcmd-preferences-addin.c 
b/src/plugins/shellcmd/gbp-shellcmd-preferences-addin.c
index 847546ce2..c055ab0a4 100644
--- a/src/plugins/shellcmd/gbp-shellcmd-preferences-addin.c
+++ b/src/plugins/shellcmd/gbp-shellcmd-preferences-addin.c
@@ -26,6 +26,7 @@
 
 #include <libide-gui.h>
 
+#include "gbp-shellcmd-command-dialog.h"
 #include "gbp-shellcmd-command-model.h"
 #include "gbp-shellcmd-preferences-addin.h"
 #include "gbp-shellcmd-run-command.h"
@@ -49,31 +50,6 @@ argv_to_string (GBinding     *binding,
   return TRUE;
 }
 
-static void
-on_items_changed_cb (GListModel *model,
-                     guint       removed,
-                     guint       added,
-                     GtkWidget  *widget)
-{
-  gboolean was_visible = gtk_widget_get_visible (widget);
-  gboolean is_visible = added > 0 || g_list_model_get_n_items (model) > 0;
-
-  if (was_visible != is_visible)
-    gtk_widget_set_visible (widget, is_visible);
-}
-
-static void
-bind_visibility_to_nonempty (GtkWidget  *widget,
-                             GListModel *model)
-{
-  gtk_widget_set_visible (widget, g_list_model_get_n_items (model) > 0);
-  g_signal_connect_object (model,
-                           "items-changed",
-                           G_CALLBACK (on_items_changed_cb),
-                           widget,
-                           0);
-}
-
 static GtkWidget *
 gbp_shellcmd_preferences_addin_create_row_cb (gpointer item,
                                               gpointer item_data)
@@ -108,7 +84,9 @@ on_row_activated_cb (GtkListBox           *list_box,
                      AdwActionRow         *row,
                      IdePreferencesWindow *window)
 {
+  g_autoptr(GbpShellcmdRunCommand) new_command = NULL;
   GbpShellcmdRunCommand *command;
+  GbpShellcmdCommandDialog *dialog;
 
   IDE_ENTRY;
 
@@ -117,17 +95,30 @@ on_row_activated_cb (GtkListBox           *list_box,
   g_assert (IDE_IS_PREFERENCES_WINDOW (window));
 
   command = g_object_get_data (G_OBJECT (row), "COMMAND");
+
   g_assert (!command || GBP_IS_SHELLCMD_RUN_COMMAND (command));
 
   if (command == NULL)
     {
-      /* TODO: create new command */
-    }
-  else
-    {
-      /* TODO: edit existing command */
+      IdePreferencesMode mode = ide_preferences_window_get_mode (window);
+      IdeContext *context = ide_preferences_window_get_context (window);
+      g_autofree char *settings_path = NULL;
+
+      if (mode == IDE_PREFERENCES_MODE_PROJECT)
+        new_command = gbp_shellcmd_run_command_create (context);
+      else
+        new_command = gbp_shellcmd_run_command_create (NULL);
+
+      command = new_command;
     }
 
+  dialog = gbp_shellcmd_command_dialog_new (command);
+  gtk_window_set_title (GTK_WINDOW (dialog),
+                        new_command ? _("Create Command") : _("Edit Command"));
+  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  gtk_window_present (GTK_WINDOW (dialog));
+
   IDE_EXIT;
 }
 
@@ -209,7 +200,7 @@ handle_shellcmd_list (const char                   *page_name,
                            gbp_shellcmd_preferences_addin_create_row_cb,
                            NULL, NULL);
   adw_preferences_group_add (group, GTK_WIDGET (list_box));
-  bind_visibility_to_nonempty (GTK_WIDGET (list_box), G_LIST_MODEL (model));
+  ide_gtk_widget_hide_when_empty (GTK_WIDGET (list_box), G_LIST_MODEL (model));
   g_signal_connect_object (list_box,
                            "row-activated",
                            G_CALLBACK (on_row_activated_cb),
diff --git a/src/plugins/shellcmd/meson.build b/src/plugins/shellcmd/meson.build
index c56bfd5b6..0476d6297 100644
--- a/src/plugins/shellcmd/meson.build
+++ b/src/plugins/shellcmd/meson.build
@@ -2,6 +2,7 @@ if get_option('plugin_shellcmd')
 
 plugins_sources += files([
   'shellcmd-plugin.c',
+  'gbp-shellcmd-command-dialog.c',
   'gbp-shellcmd-command-model.c',
   'gbp-shellcmd-preferences-addin.c',
   'gbp-shellcmd-run-command.c',
diff --git a/src/plugins/shellcmd/shellcmd.gresource.xml b/src/plugins/shellcmd/shellcmd.gresource.xml
index 4af547c31..9a01bc34d 100644
--- a/src/plugins/shellcmd/shellcmd.gresource.xml
+++ b/src/plugins/shellcmd/shellcmd.gresource.xml
@@ -2,5 +2,6 @@
 <gresources>
   <gresource prefix="/plugins/shellcmd">
     <file>shellcmd.plugin</file>
+    <file preprocess="xml-stripblanks">gbp-shellcmd-command-dialog.ui</file>
   </gresource>
 </gresources>


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