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
This commit is contained in:
@ -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) > $@
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
114
tools/generate-welcome-dialog-data.py
Executable file
114
tools/generate-welcome-dialog-data.py
Executable file
@ -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};')
|
Reference in New Issue
Block a user