[gimp] app, tools: allow creating demo "scenarios" from the AppStream metadata.



commit 3904c40dc11a788a6416ac5c548cadff1f09d5df
Author: Jehan <jehan girinstud io>
Date:   Sat Mar 5 23:06:46 2022 +0100

    app, tools: allow creating demo "scenarios" from the AppStream metadata.
    
    The idea is to add some "demo" attribute to a list item inside the
    <release> tag, since we already decided that (for now at least) we'd
    keep a strict "intro + list" logics, as we did until now.
    
    This demo attribute uses an internal format to specify successive
    widgets to blink (like a demo path towards a feature). For now, what it
    allows is:
    
    * raise the toolbox, select a tool and blink the tool button.
    * raise a dockable, blink any widgets in there.
    
    Now it is still limited and needs to evolve. In particular:
    
    * What happens if the blinked tool button was explicitly removed from
      Preferences? Should we re-add it for the demo? And once done, should
      we remove it again? Then should we select back the tool previously
      selected?
    * What happens if the dockable widget is not visible? Should we allow
      changing the settings to be able to demo correctly the new/changed
      settings? Should it be temporary? If temporary, it can be annoying as
      you'd still have to look attentively the demo to find back the path to
      the new settings. If not temporary, some people may dislike we touch
      their settings.
    * What if docks are hidden? Should we unhide them, then hide them back
      after demo time?
    
    Also regarding the implementation: originally I wanted to just grab the
    demo attribute directly from the AppStream metadata file, but I realized
    that appstream-glib cleans out unknown attribute from the XML. I could
    then simply parse the file with a generic XML parser, but I found
    simpler to pre-parse it into a header built within GIMP. I still use
    appstream-glib at runtime as it takes care of localization for us
    (though in the same time, we also have the localization in the main po
    files, so maybe we could just embed the release note strings as well).
    
    See appstream-glib report: https://github.com/hughsie/appstream-glib/issues/431

 app/dialogs/Makefile.am               |  12 +++-
 app/dialogs/meson.build               |  15 +++++
 app/dialogs/welcome-dialog.c          | 110 +++++++++++++++++++++++++++++---
 app/widgets/gimptoolbutton.c          |  27 ++++++++
 tools/generate-welcome-dialog-data.py | 114 ++++++++++++++++++++++++++++++++++
 5 files changed, 268 insertions(+), 10 deletions(-)
---
diff --git a/app/dialogs/Makefile.am b/app/dialogs/Makefile.am
index 4391216a3c..aa8ea3829f 100644
--- a/app/dialogs/Makefile.am
+++ b/app/dialogs/Makefile.am
@@ -106,7 +106,9 @@ libappdialogs_a_sources = \
        vectors-options-dialog.c        \
        vectors-options-dialog.h        \
        welcome-dialog.c                \
-       welcome-dialog.h
+       welcome-dialog.h                \
+       welcome-dialog-data.c           \
+       welcome-dialog-data.h
 
 libappdialogs_a_built_sources = \
        authors.h
@@ -126,3 +128,11 @@ if HAVE_XSLTPROC
 else
        @echo "*** xsltproc is required to regenerate $(@) ***"; exit 1;
 endif
+
+$(srcdir)/welcome-dialog.c: welcome-dialog-data.h welcome-dialog-data.c
+
+welcome-dialog-data.h: ../../desktop/org.gimp.GIMP.appdata.xml.in.in 
../../tools/generate-welcome-dialog-data.py
+       $(top_srcdir)/tools/generate-welcome-dialog-data.py --header $(GIMP_VERSION) > $@
+
+welcome-dialog-data.c: ../../desktop/org.gimp.GIMP.appdata.xml.in.in 
../../tools/generate-welcome-dialog-data.py
+       $(top_srcdir)/tools/generate-welcome-dialog-data.py $(GIMP_VERSION) > $@
diff --git a/app/dialogs/meson.build b/app/dialogs/meson.build
index 3f4cbb6daa..26e73e06f5 100644
--- a/app/dialogs/meson.build
+++ b/app/dialogs/meson.build
@@ -1,3 +1,16 @@
+welcome_dialog_data_h = custom_target('welcome-dialog-data-h',
+                                      input : [meson.source_root() / 'tools/generate-welcome-dialog-data.py',
+                                               meson.source_root() / 
'desktop/org.gimp.GIMP.appdata.xml.in.in'],
+                                      output : ['welcome-dialog-data.h'],
+                                      command : ['python3', '@INPUT0@', gimp_version, '--header'],
+                                      capture: true)
+welcome_dialog_data_c = custom_target('welcome-dialog-data-c',
+                                      input : [meson.source_root() / 'tools/generate-welcome-dialog-data.py',
+                                               meson.source_root() / 
'desktop/org.gimp.GIMP.appdata.xml.in.in'],
+                                      output : ['welcome-dialog-data.c'],
+                                      command : ['python3', '@INPUT0@', gimp_version],
+                                      capture: true)
+
 libappdialogs_sources = [
   'about-dialog.c',
   'action-search-dialog.c',
@@ -45,6 +58,8 @@ libappdialogs_sources = [
   'vectors-options-dialog.c',
   'welcome-dialog.c',
   gitversion_h,
+  welcome_dialog_data_c,
+  welcome_dialog_data_h,
 ]
 
 # Auto-generated sources
diff --git a/app/dialogs/welcome-dialog.c b/app/dialogs/welcome-dialog.c
index f4e1b15f3a..269a327248 100644
--- a/app/dialogs/welcome-dialog.c
+++ b/app/dialogs/welcome-dialog.c
@@ -37,22 +37,32 @@
 #include "core/gimp.h"
 #include "core/gimp-utils.h"
 
+#include "widgets/gimpaction.h"
+#include "widgets/gimpdialogfactory.h"
 #include "widgets/gimphelp-ids.h"
+#include "widgets/gimptoolbox.h"
+#include "widgets/gimpuimanager.h"
+#include "widgets/gimpwidgets-utils.h"
+#include "widgets/gimpwindowstrategy.h"
 
 #include "welcome-dialog.h"
+#include "welcome-dialog-data.h"
 
 #include "gimp-intl.h"
 
 
-static void     welcome_add_link        (GtkGrid        *grid,
-                                         gint            column,
-                                         gint           *row,
-                                         const gchar    *emoji,
-                                         const gchar    *title,
-                                         const gchar    *link);
-static void     welcome_size_allocate   (GtkWidget      *welcome_dialog,
-                                         GtkAllocation  *allocation,
-                                         gpointer        user_data);
+static void   welcome_dialog_release_item_activated (GtkListBox    *listbox,
+                                                     GtkListBoxRow *row,
+                                                     gpointer       user_data);
+static void   welcome_add_link                      (GtkGrid        *grid,
+                                                     gint            column,
+                                                     gint           *row,
+                                                     const gchar    *emoji,
+                                                     const gchar    *title,
+                                                     const gchar    *link);
+static void   welcome_size_allocate                 (GtkWidget      *welcome_dialog,
+                                                     GtkAllocation  *allocation,
+                                                     gpointer        user_data);
 
 
 GtkWidget *
@@ -389,6 +399,12 @@ welcome_dialog_create (Gimp *gimp)
               gtk_widget_show_all (row);
             }
           gtk_container_add (GTK_CONTAINER (scrolled_window), listbox);
+          gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox),
+                                           GTK_SELECTION_NONE);
+
+          g_signal_connect (listbox, "row-activated",
+                            G_CALLBACK (welcome_dialog_release_item_activated),
+                            gimp);
           gtk_widget_show (listbox);
 
           g_list_free_full (release_items, g_free);
@@ -440,6 +456,82 @@ welcome_dialog_create (Gimp *gimp)
   return welcome_dialog;
 }
 
+static void
+welcome_dialog_release_item_activated (GtkListBox    *listbox,
+                                       GtkListBoxRow *row,
+                                       gpointer       user_data)
+{
+  Gimp         *gimp          = user_data;
+  GList        *blink_script  = NULL;
+  const gchar  *script_string;
+  gchar       **script_steps;
+  gint          row_index;
+  gint          i;
+
+  row_index = gtk_list_box_row_get_index (row);
+
+  g_return_if_fail (row_index < n_gimp_welcome_dialog_demo);
+
+  script_string = gimp_welcome_dialog_demo[row_index];
+
+  if (script_string == NULL)
+    /* Not an error. Some release items have no demos. */
+    return;
+
+  script_steps = g_strsplit (script_string, ",", 0);
+
+  for (i = 0; script_steps[i]; i++)
+    {
+      gchar **ids;
+      gchar  *dockable_id = NULL;
+      gchar  *widget_id   = NULL;
+
+      ids = g_strsplit (script_steps[i], ":", 2);
+      /* Even if the string doesn't contain a second part, it is
+       * NULL-terminated, hence the widget_id will simply be NULL, which
+       * is fine when you just want to blink a dialog.
+       */
+      dockable_id = ids[0];
+      widget_id   = ids[1];
+
+      if (g_strcmp0 (dockable_id, "gimp-toolbox") == 0 &&
+          widget_id != NULL)
+        {
+          GimpUIManager *ui_manager;
+          GtkWidget     *toolbox;
+
+          /* As a special case, for the toolbox, we don't just raise it,
+           * we also select the tool if one was requested.
+           */
+          toolbox = gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY 
(gimp_get_window_strategy (gimp)),
+                                                               gimp,
+                                                               gimp_dialog_factory_get_singleton (),
+                                                               gimp_get_monitor_at_pointer (),
+                                                               "gimp-toolbox");
+          /* Find and activate the tool. */
+          if (toolbox &&
+              (ui_manager = gimp_dock_get_ui_manager (GIMP_DOCK (toolbox))))
+            {
+              GimpAction *action;
+
+              action = gimp_ui_manager_find_action (ui_manager, "tools", widget_id);
+                                                    /*"tools-bucket-fill");*/
+              gimp_action_activate (GIMP_ACTION (action));
+            }
+        }
+
+      /* Blink widget. */
+      gimp_blink_dockable (gimp, dockable_id, widget_id, &blink_script);
+
+      g_strfreev (ids);
+    }
+  if (blink_script != NULL)
+    gimp_blink_play_script (blink_script);
+
+  g_list_free (blink_script);
+  g_strfreev (script_steps);
+}
+
 static void
 welcome_add_link (GtkGrid     *grid,
                   gint         column,
diff --git a/app/widgets/gimptoolbutton.c b/app/widgets/gimptoolbutton.c
index 0322f7383a..c6e7b20d6e 100644
--- a/app/widgets/gimptoolbutton.c
+++ b/app/widgets/gimptoolbutton.c
@@ -28,6 +28,7 @@
 #include "libgimpbase/gimpbase.h"
 #include "libgimpmath/gimpmath.h"
 #include "libgimpwidgets/gimpwidgets.h"
+#include "libgimpwidgets/gimpwidgets-private.h"
 
 #include "widgets-types.h"
 
@@ -899,6 +900,32 @@ gimp_tool_button_update (GimpToolButton *tool_button)
       gimp_help_set_help_data (GTK_WIDGET (tool_button), NULL, NULL);
     }
 
+  if (tool_info)
+    {
+      gchar *id = gimp_object_get_name (tool_info);
+
+      if (g_str_has_prefix (id, "gimp-") &&
+          g_str_has_suffix (id, "-tool"))
+        {
+          /* The GimpToolInfo names are of the form "gimp-pencil-tool",
+           * and action names are of the form "tools-pencil".
+           * To simplify things, I make the tool button identifiers the
+           * same as the actions, which make them easier to find.
+           */
+          gchar *suffix;
+
+          id = g_strdup_printf ("tools-%s", id + 5);
+          suffix = g_strrstr (id, "-tool");
+          suffix[0] = '\0';
+        }
+      else
+        {
+          id = g_strdup (id);
+        }
+
+      gimp_widget_set_identifier (tool_button, id);
+    }
+
   gimp_tool_button_update_toggled (tool_button);
   gimp_tool_button_update_menu    (tool_button);
 }
diff --git a/tools/generate-welcome-dialog-data.py b/tools/generate-welcome-dialog-data.py
new file mode 100755
index 0000000000..a4ad4e65a4
--- /dev/null
+++ b/tools/generate-welcome-dialog-data.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+
+"""
+generate-welcome-dialog-data.py -- Generate app/dialogs/welcome-dialog-data.h
+Copyright (C) 2022 Jehan
+
+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 <https://www.gnu.org/licenses/>.
+
+
+Usage: generate-welcome-dialog-data.py
+"""
+
+import argparse
+import os.path
+import sys
+import xml.etree.ElementTree as ET
+
+tools_dir   = os.path.dirname(os.path.realpath(__file__))
+desktop_dir = os.path.join(tools_dir, '../desktop')
+outdir      = os.path.join(tools_dir, '../app/dialogs')
+
+infile      = os.path.join(desktop_dir, 'org.gimp.GIMP.appdata.xml.in.in')
+outfile     = os.path.join(outdir, 'welcome-dialog-data.h')
+
+def parse_appdata(infile, version):
+  release_demos = []
+  tree = ET.parse(infile)
+  root = tree.getroot()
+  releases_node = root.find('releases')
+  releases = releases_node.findall('release')
+  for release in releases:
+    if 'version' in release.attrib and release.attrib['version'] == version:
+      items = release.findall('./description/ul/_li')
+      for item in items:
+        demo = None
+        if 'demo' in item.attrib:
+          demo = item.attrib['demo']
+        release_demos += [demo]
+
+  return release_demos
+
+if __name__ == "__main__":
+  parser = argparse.ArgumentParser()
+  parser.add_argument('version')
+  parser.add_argument('--header', action='store_true')
+  args = parser.parse_args(sys.argv[1:])
+
+  top_comment = '''/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * welcome-dialog-data.h
+ * Copyright (C) 2022 Jehan
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ *
+ ***********************************************************************
+ * This file is autogenerated by tools/generate-welcome-dialog-data.py *
+ ***********************************************************************
+ *
+ * Modify the python script or desktop/org.gimp.GIMP.appdata.xml.in.in
+ * instead of this one
+ * Then run tools/generate-welcome-dialog-data.py again.
+ */
+
+'''
+  print(top_comment)
+
+  demos = parse_appdata(infile, args.version)
+
+  if args.header:
+    print('#ifndef __WELCOME_DIALOG_DATA_H__')
+    print('#define __WELCOME_DIALOG_DATA_H__\n\n')
+
+    print('extern gint          n_gimp_welcome_dialog_demo;')
+    print('extern const gchar * gimp_welcome_dialog_demo[];')
+
+    print('\n\n#endif /* __WELCOME_DIALOG_DATA_H__ */')
+  else:
+    print('#include "config.h"')
+    print('#include <glib.h>')
+    print()
+
+    print('const gint    n_gimp_welcome_dialog_demo = {};'.format(len(demos)))
+    print()
+    print('const gchar * gimp_welcome_dialog_demo[] =')
+    print('{')
+    for demo in demos:
+      if demo is None:
+        print('  NULL,')
+      else:
+        print('  "{}",'.format(demo))
+    print('  NULL,\n};')


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