From e983578fffc4b6f2090a542cc013deed5a937b24 Mon Sep 17 00:00:00 2001 From: prculley Date: Sat, 31 Aug 2019 09:39:07 -0500 Subject: [PATCH] Fix to make Gtk 'action names' always valid Fixes #11291 --- gramps/gen/plug/_pluginreg.py | 2 +- gramps/gui/managedwindow.py | 4 ++-- gramps/gui/plug/quick/_quickreports.py | 11 +++++----- gramps/gui/uimanager.py | 16 ++++++++++++++ gramps/gui/viewmanager.py | 4 ++-- gramps/gui/views/tags.py | 2 +- gramps/gui/widgets/grampletpane.py | 29 +++++++++++++++----------- 7 files changed, 45 insertions(+), 23 deletions(-) diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 3da23c35d..b2e710fec 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -193,7 +193,7 @@ class PluginData: .. attribute:: id A unique identifier for the plugin. This is eg used to store the plugin - settings. + settings. MUST be in ASCII, with only "_- ().,'" special characters. .. attribute:: name A friendly name to call this plugin (normally translated) .. attribute:: name_accell diff --git a/gramps/gui/managedwindow.py b/gramps/gui/managedwindow.py index 98333c498..b9aff7e90 100644 --- a/gramps/gui/managedwindow.py +++ b/gramps/gui/managedwindow.py @@ -49,7 +49,7 @@ from gramps.gen.const import GLADE_FILE, ICON from gramps.gen.errors import WindowActiveError from gramps.gen.config import config from gramps.gen.constfunc import is_quartz -from .uimanager import ActionGroup +from .uimanager import ActionGroup, valid_action_name from .glade import Glade #------------------------------------------------------------------------- @@ -280,7 +280,7 @@ class GrampsWindowManager: return func def generate_id(self, item): - return 'wm/' + str(item.window_id).replace(' ', '_') + return valid_action_name('wm-' + str(item.window_id)) def display_menu_list(self, data, action_data, mlist): menuitem = ('\n' diff --git a/gramps/gui/plug/quick/_quickreports.py b/gramps/gui/plug/quick/_quickreports.py index c8e1d1718..3bc1893b8 100644 --- a/gramps/gui/plug/quick/_quickreports.py +++ b/gramps/gui/plug/quick/_quickreports.py @@ -55,6 +55,7 @@ from gi.repository import Gtk # #------------------------------------------------------------------------- from ...pluginmanager import GuiPluginManager +from ...uimanager import valid_action_name from gramps.gen.plug import (CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY, CATEGORY_QR_MEDIA, CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE, CATEGORY_QR_MISC, CATEGORY_QR_PLACE, CATEGORY_QR_REPOSITORY, @@ -97,11 +98,9 @@ def create_web_connect_menu(dbstate, uistate, nav_group, handle, prefix): top = ("\n" '' 'Web Connection\n') - actions = [] ofile = StringIO() ofile.write(top) #select the web connects to show - showlst = [] pmgr = GuiPluginManager.get_instance() plugins = pmgr.process_plugin_data('WebConnect') try: @@ -114,8 +113,10 @@ def create_web_connect_menu(dbstate, uistate, nav_group, handle, prefix): connections = flatten(connections) connections.sort(key=lambda plug: plug.name) actions = [] - for connect in connections: - action = connect.key.replace(' ', '-') + for indx, connect in enumerate(connections): + # action would be better with "connect.key", but it seems to be + # non-ASCII sometimes. So we use an action number instead. + action = "web-con-%d" % indx ofile.write(MENUITEM.format(prefix=prefix, action=action, label=connect.name)) callback = connect(dbstate, uistate, nav_group, handle) @@ -156,7 +157,7 @@ def create_quickreport_menu(category, dbstate, uistate, handle, prefix, track=[] showlst.sort(key=lambda x: x.name) for pdata in showlst: - new_key = pdata.id.replace(' ', '-') + new_key = valid_action_name("qr-%s" % pdata.id) ofile.write(MENUITEM.format(prefix=prefix, action=new_key, label=pdata.name)) actions.append((new_key, make_quick_report_callback( diff --git a/gramps/gui/uimanager.py b/gramps/gui/uimanager.py index dd870ce33..9f4c62610 100644 --- a/gramps/gui/uimanager.py +++ b/gramps/gui/uimanager.py @@ -364,6 +364,8 @@ class UIManager(): else: window_group = group.act_group = self.app.window for item in group.actionlist: + if not Gio.action_name_is_valid(item[ACTION_NAME]): + LOG.warning('**Invalid action name %s', item[ACTION_NAME]) # deal with accelerator overrides from a file accel = self.accel_dict.get(group.prefix + item[ACTION_NAME]) if accel: @@ -526,3 +528,17 @@ class UIManager(): with open(filename, 'r') as hndl: accels = hndl.read() self.accel_dict = ast.literal_eval(accels) + + +INVALID_CHARS = [' ', '_', '(', ')', ',', "'"] + + +def valid_action_name(text): + """ This function cleans up action names to avoid some illegal + characters. It does NOT clean up non-ASCII characters. + This is used for plugin IDs to clean them up. It would be better if we + made all plugin ids: + ASCII Alphanumeric and the '.' or '-' characters.""" + for char in INVALID_CHARS: + text = text.replace(char, '-') + return text diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index 37778bc18..dbf6e8304 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -92,7 +92,7 @@ from .configure import GrampsPreferences from .aboutdialog import GrampsAboutDialog from .navigator import Navigator from .views.tags import Tags -from .uimanager import ActionGroup +from .uimanager import ActionGroup, valid_action_name from gramps.gen.lib import (Person, Surname, Family, Media, Note, Place, Source, Repository, Citation, Event, EventType, ChildRef) @@ -1429,7 +1429,7 @@ class ViewManager(CLIManager): pdatas = hash_data[key] pdatas.sort(key=lambda x: x.name) for pdata in pdatas: - new_key = pdata.id.replace(' ', '-') + new_key = valid_action_name(pdata.id) ofile.write(menuitem % (new_key, pdata.name)) actions.append((new_key, func(pdata, self.dbstate, self.uistate))) diff --git a/gramps/gui/views/tags.py b/gramps/gui/views/tags.py index ad0d9071d..5f0eb42bd 100644 --- a/gramps/gui/views/tags.py +++ b/gramps/gui/views/tags.py @@ -241,7 +241,7 @@ class Tags(DbGUIElement): tag_menu = '' menuitem = ''' - win.TAG_%s + win.TAG-%s %s ''' diff --git a/gramps/gui/widgets/grampletpane.py b/gramps/gui/widgets/grampletpane.py index f85b4c2e2..e07030955 100644 --- a/gramps/gui/widgets/grampletpane.py +++ b/gramps/gui/widgets/grampletpane.py @@ -31,6 +31,7 @@ GrampletView interface. from gi.repository import Gdk from gi.repository import Gtk from gi.repository import Pango +from xml.sax.saxutils import escape import time import os import configparser @@ -49,7 +50,7 @@ from gramps.gen.const import URL_MANUAL_PAGE, VERSION_DIR, COLON from ..editors import EditPerson, EditFamily from ..managedwindow import ManagedWindow from ..utils import is_right_click, match_primary_mask, get_link_color -from ..uimanager import ActionGroup +from ..uimanager import ActionGroup, valid_action_name from ..plug import make_gui_option from ..plug.quick import run_quick_report_by_name from ..display import display_help, display_url @@ -971,8 +972,11 @@ class GridGramplet(GuiGramplet): def set_title(self, new_title, set_override=True): # can't do it if already titled that way - if self.title == new_title: return True - if new_title in self.pane.gramplet_map: return False + if self.title == new_title: + return True + if(new_title in self.pane.gramplet_map or + new_title != escape(new_title)): # avoid XML specific characters + return False if set_override: self.title_override = True del self.pane.gramplet_map[self.title] @@ -1464,21 +1468,22 @@ class GrampletPane(Gtk.ScrolledWindow): actions = [] r_menuitems = '' a_menuitems = '' - names = [gplug.name for gplug in PLUGMAN.get_reg_gramplets() - if gplug.navtypes == [] - or 'Dashboard' in gplug.navtypes] - names.sort() - for name in names: - action_name = name.replace(' ', '-') - a_menuitems += menuitem % (action_name, name) + plugs = [gplug for gplug in PLUGMAN.get_reg_gramplets() if + gplug.navtypes == [] or 'Dashboard' in gplug.navtypes] + plugs.sort(key=lambda x: x.name) + for plug in plugs: + action_name = valid_action_name(plug.id) + a_menuitems += menuitem % (action_name, plug.name) actions.append((action_name, - make_callback(self.add_gramplet, name))) + make_callback(self.add_gramplet, plug.name))) names = [gramplet.title for gramplet in self.closed_gramplets] names.extend(opts["title"] for opts in self.closed_opts) names.sort() if len(names) > 0: for name in names: - action_name = name.replace(' ', '-') + # 'name' could be non-ASCII when in non-English language + # action names must be in ASCII, so use 'id' instead. + action_name = valid_action_name(str(id(name))) r_menuitems += menuitem % (action_name, name) actions.append((action_name, make_callback(self.restore_gramplet,