Create new Addon Manager dialog
16
data/gramps.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.lozenge {
|
||||||
|
font-size: small;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #0d6efd;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0px 0px 6px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addon-row {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: alpha(currentColor, .2);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
@ -157,7 +157,10 @@ register('behavior.translator-needed', True)
|
|||||||
register('behavior.use-tips', False)
|
register('behavior.use-tips', False)
|
||||||
register('behavior.welcome', 100)
|
register('behavior.welcome', 100)
|
||||||
register('behavior.web-search-url', 'http://google.com/#&q=%(text)s')
|
register('behavior.web-search-url', 'http://google.com/#&q=%(text)s')
|
||||||
register('behavior.addons-url', "https://raw.githubusercontent.com/gramps-project/addons/master/gramps52")
|
register('behavior.addons-url', 'https://raw.githubusercontent.com/gramps-project/addons/master/gramps52')
|
||||||
|
register('behavior.addons-projects',
|
||||||
|
[['Gramps', 'https://raw.githubusercontent.com/gramps-project/addons/master/gramps52', True]])
|
||||||
|
register('behavior.addons-allow-install', False)
|
||||||
|
|
||||||
register('csv.dialect', 'excel')
|
register('csv.dialect', 'excel')
|
||||||
register('csv.delimiter', ',')
|
register('csv.delimiter', ',')
|
||||||
@ -225,7 +228,7 @@ register('interface.toolbar-on', True)
|
|||||||
register('interface.toolbar-text', False)
|
register('interface.toolbar-text', False)
|
||||||
register('interface.hide-lds', False)
|
register('interface.hide-lds', False)
|
||||||
register('interface.toolbar-clipboard', True)
|
register('interface.toolbar-clipboard', True)
|
||||||
register('interface.toolbar-plugin', True)
|
register('interface.toolbar-addons', True)
|
||||||
register('interface.toolbar-preference', True)
|
register('interface.toolbar-preference', True)
|
||||||
register('interface.toolbar-reports', True)
|
register('interface.toolbar-reports', True)
|
||||||
register('interface.toolbar-tools', True)
|
register('interface.toolbar-tools', True)
|
||||||
|
@ -34,7 +34,7 @@ from ._pluginreg import (PluginData, PluginRegister, REPORT, TOOL,
|
|||||||
CATEGORY_QR_PLACE, CATEGORY_QR_REPOSITORY, CATEGORY_QR_NOTE,
|
CATEGORY_QR_PLACE, CATEGORY_QR_REPOSITORY, CATEGORY_QR_NOTE,
|
||||||
CATEGORY_QR_DATE, PTYPE_STR, CATEGORY_QR_MEDIA,
|
CATEGORY_QR_DATE, PTYPE_STR, CATEGORY_QR_MEDIA,
|
||||||
CATEGORY_QR_CITATION, CATEGORY_QR_SOURCE_OR_CITATION,
|
CATEGORY_QR_CITATION, CATEGORY_QR_SOURCE_OR_CITATION,
|
||||||
START, END, make_environment,
|
START, END, make_environment, AUDIENCETEXT, STATUSTEXT,
|
||||||
)
|
)
|
||||||
from ._import import ImportPlugin
|
from ._import import ImportPlugin
|
||||||
from ._export import ExportPlugin
|
from ._export import ExportPlugin
|
||||||
|
@ -104,6 +104,14 @@ class BasePluginManager:
|
|||||||
self.__loaded_plugins = {}
|
self.__loaded_plugins = {}
|
||||||
self.__scanned_dirs = []
|
self.__scanned_dirs = []
|
||||||
|
|
||||||
|
def reg_plugin_dir(self, direct, dbstate=None, uistate=None,
|
||||||
|
load_on_reg=False, rescan=False):
|
||||||
|
"""
|
||||||
|
Register plugins in a given directory.
|
||||||
|
"""
|
||||||
|
self.__scanned_dirs.remove(direct)
|
||||||
|
self.reg_plugins(direct, dbstate, uistate, load_on_reg, rescan)
|
||||||
|
|
||||||
def reg_plugins(self, direct, dbstate=None, uistate=None,
|
def reg_plugins(self, direct, dbstate=None, uistate=None,
|
||||||
load_on_reg=False, rescan=False):
|
load_on_reg=False, rescan=False):
|
||||||
"""
|
"""
|
||||||
@ -128,8 +136,7 @@ class BasePluginManager:
|
|||||||
# " been_here=%s, pahte exists:%s", direct, load_on_reg,
|
# " been_here=%s, pahte exists:%s", direct, load_on_reg,
|
||||||
# direct in self.__scanned_dirs, os.path.isdir(direct))
|
# direct in self.__scanned_dirs, os.path.isdir(direct))
|
||||||
|
|
||||||
if os.path.isdir(direct) and direct not in self.__scanned_dirs:
|
if os.path.isdir(direct):
|
||||||
self.__scanned_dirs.append(direct)
|
|
||||||
for (dirpath, dirnames, filenames) in os.walk(direct,
|
for (dirpath, dirnames, filenames) in os.walk(direct,
|
||||||
topdown=True):
|
topdown=True):
|
||||||
for dirname in dirnames[:]:
|
for dirname in dirnames[:]:
|
||||||
@ -138,7 +145,9 @@ class BasePluginManager:
|
|||||||
"__pycache__"]:
|
"__pycache__"]:
|
||||||
dirnames.remove(dirname)
|
dirnames.remove(dirname)
|
||||||
# LOG.warning("Plugin dir scanned: %s", dirpath)
|
# LOG.warning("Plugin dir scanned: %s", dirpath)
|
||||||
self.__pgr.scan_dir(dirpath, filenames, uistate=uistate)
|
if dirpath not in self.__scanned_dirs:
|
||||||
|
self.__pgr.scan_dir(dirpath, filenames, uistate=uistate)
|
||||||
|
self.__scanned_dirs.append(dirpath)
|
||||||
|
|
||||||
if load_on_reg:
|
if load_on_reg:
|
||||||
# Run plugins that request to be loaded on startup and
|
# Run plugins that request to be loaded on startup and
|
||||||
|
@ -40,6 +40,7 @@ import traceback
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from ...version import VERSION as GRAMPSVERSION, VERSION_TUPLE
|
from ...version import VERSION as GRAMPSVERSION, VERSION_TUPLE
|
||||||
|
from ..utils.requirements import Requirements
|
||||||
from ..const import IMAGE_DIR
|
from ..const import IMAGE_DIR
|
||||||
from ..const import GRAMPS_LOCALE as glocale
|
from ..const import GRAMPS_LOCALE as glocale
|
||||||
_ = glocale.translation.gettext
|
_ = glocale.translation.gettext
|
||||||
@ -1271,6 +1272,7 @@ class PluginRegister:
|
|||||||
self.stable_only = False
|
self.stable_only = False
|
||||||
self.__plugindata = []
|
self.__plugindata = []
|
||||||
self.__id_to_pdata = {}
|
self.__id_to_pdata = {}
|
||||||
|
self.__req = Requirements()
|
||||||
|
|
||||||
def add_plugindata(self, plugindata):
|
def add_plugindata(self, plugindata):
|
||||||
""" This is used to add an entry to the registration list. The way it
|
""" This is used to add an entry to the registration list. The way it
|
||||||
@ -1323,10 +1325,11 @@ class PluginRegister:
|
|||||||
exec (compile(stream, filename, 'exec'),
|
exec (compile(stream, filename, 'exec'),
|
||||||
make_environment(_=local_gettext), {'uistate': uistate})
|
make_environment(_=local_gettext), {'uistate': uistate})
|
||||||
for pdata in self.__plugindata[lenpd:]:
|
for pdata in self.__plugindata[lenpd:]:
|
||||||
# should not be duplicate IDs in different plugins
|
if pdata.id in self.__id_to_pdata:
|
||||||
assert pdata.id not in self.__id_to_pdata
|
# reloading
|
||||||
# if pdata.id in self.__id_to_pdata:
|
old = self.__id_to_pdata[pdata.id]
|
||||||
# print("Error: %s is duplicated!" % pdata.id)
|
self.__plugindata.remove(old)
|
||||||
|
lenpd -= 1
|
||||||
self.__id_to_pdata[pdata.id] = pdata
|
self.__id_to_pdata[pdata.id] = pdata
|
||||||
except ValueError as msg:
|
except ValueError as msg:
|
||||||
print(_('ERROR: Failed reading plugin registration %(filename)s') % \
|
print(_('ERROR: Failed reading plugin registration %(filename)s') % \
|
||||||
@ -1358,6 +1361,9 @@ class PluginRegister:
|
|||||||
))
|
))
|
||||||
rmlist.append(ind)
|
rmlist.append(ind)
|
||||||
continue
|
continue
|
||||||
|
if not self.__req.check_plugin(plugin):
|
||||||
|
rmlist.append(ind)
|
||||||
|
continue
|
||||||
if not plugin.status == STABLE and self.stable_only:
|
if not plugin.status == STABLE and self.stable_only:
|
||||||
rmlist.append(ind)
|
rmlist.append(ind)
|
||||||
continue
|
continue
|
||||||
|
@ -31,6 +31,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
from io import StringIO, BytesIO
|
from io import StringIO, BytesIO
|
||||||
|
import json
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -192,8 +193,7 @@ def urlopen_maybe_no_check_cert(URL):
|
|||||||
fp = urlopen(URL, timeout=timeout)
|
fp = urlopen(URL, timeout=timeout)
|
||||||
return fp
|
return fp
|
||||||
|
|
||||||
def available_updates():
|
def get_addons(project, url):
|
||||||
whattypes = config.get('behavior.check-for-addon-update-types')
|
|
||||||
|
|
||||||
LOG.debug("Checking for updated addons...")
|
LOG.debug("Checking for updated addons...")
|
||||||
langs = glocale.get_language_list()
|
langs = glocale.get_language_list()
|
||||||
@ -201,15 +201,13 @@ def available_updates():
|
|||||||
# now we have a list of languages to try:
|
# now we have a list of languages to try:
|
||||||
fp = None
|
fp = None
|
||||||
for lang in langs:
|
for lang in langs:
|
||||||
URL = ("%s/listings/addons-%s.txt" %
|
URL = ("%s/listings/addons-%s.json" % (url, lang))
|
||||||
(config.get("behavior.addons-url"), lang))
|
|
||||||
LOG.debug(" trying: %s" % URL)
|
LOG.debug(" trying: %s" % URL)
|
||||||
try:
|
try:
|
||||||
fp = urlopen_maybe_no_check_cert(URL)
|
fp = urlopen_maybe_no_check_cert(URL)
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
URL = ("%s/listings/addons-%s.txt" %
|
URL = ("%s/listings/addons-%s.json" % (url, lang[:2]))
|
||||||
(config.get("behavior.addons-url"), lang[:2]))
|
|
||||||
fp = urlopen_maybe_no_check_cert(URL)
|
fp = urlopen_maybe_no_check_cert(URL)
|
||||||
except Exception as err: # some error
|
except Exception as err: # some error
|
||||||
LOG.warning("Failed to open addon metadata for {lang} {url}: {err}".
|
LOG.warning("Failed to open addon metadata for {lang} {url}: {err}".
|
||||||
@ -218,60 +216,73 @@ def available_updates():
|
|||||||
if fp and (fp.getcode() == 200 or fp.file):
|
if fp and (fp.getcode() == 200 or fp.file):
|
||||||
break
|
break
|
||||||
|
|
||||||
pmgr = BasePluginManager.get_instance()
|
addon_list = []
|
||||||
addon_update_list = []
|
|
||||||
if fp and (fp.getcode() == 200 or fp.file):
|
if fp and (fp.getcode() == 200 or fp.file):
|
||||||
lines = list(fp.readlines())
|
addon_list = json.load(fp)
|
||||||
count = 0
|
for plugin_dict in addon_list:
|
||||||
for line in lines:
|
if 'a' not in plugin_dict:
|
||||||
line = line.decode('utf-8')
|
plugin_dict['a'] = 0
|
||||||
try:
|
if 's' not in plugin_dict:
|
||||||
plugin_dict = safe_eval(line)
|
plugin_dict['s'] = 0
|
||||||
if type(plugin_dict) != type({}):
|
if 'h' not in plugin_dict:
|
||||||
raise TypeError("Line with addon metadata is not "
|
plugin_dict['h'] = ''
|
||||||
"a dictionary")
|
plugin_dict['_p'] = project
|
||||||
except:
|
plugin_dict['_u'] = url
|
||||||
LOG.warning("Skipped a line in the addon listing: " +
|
|
||||||
str(line))
|
|
||||||
continue
|
|
||||||
id = plugin_dict["i"]
|
id = plugin_dict["i"]
|
||||||
|
pmgr = BasePluginManager.get_instance()
|
||||||
plugin = pmgr.get_plugin(id)
|
plugin = pmgr.get_plugin(id)
|
||||||
if plugin:
|
if plugin:
|
||||||
LOG.debug("Comparing %s > %s" %
|
plugin_dict['_v'] = plugin.version
|
||||||
(version_str_to_tup(plugin_dict["v"], 3),
|
|
||||||
version_str_to_tup(plugin.version, 3)))
|
|
||||||
if (version_str_to_tup(plugin_dict["v"], 3) >
|
|
||||||
version_str_to_tup(plugin.version, 3)):
|
|
||||||
LOG.debug(" Downloading '%s'..." % plugin_dict["z"])
|
|
||||||
if "update" in whattypes:
|
|
||||||
if (not config.get('behavior.do-not-show-previously-seen-addon-updates') or
|
|
||||||
plugin_dict["i"] not in config.get('behavior.previously-seen-addon-updates')):
|
|
||||||
addon_update_list.append((_("Updated"),
|
|
||||||
"%s/download/%s" %
|
|
||||||
(config.get("behavior.addons-url"),
|
|
||||||
plugin_dict["z"]),
|
|
||||||
plugin_dict))
|
|
||||||
else:
|
|
||||||
LOG.debug(" '%s' is ok" % plugin_dict["n"])
|
|
||||||
else:
|
|
||||||
LOG.debug(" '%s' is not installed" % plugin_dict["n"])
|
|
||||||
if "new" in whattypes:
|
|
||||||
if (not config.get('behavior.do-not-show-previously-seen-addon-updates') or
|
|
||||||
plugin_dict["i"] not in config.get('behavior.previously-seen-addon-updates')):
|
|
||||||
addon_update_list.append((_("New", "updates"),
|
|
||||||
"%s/download/%s" %
|
|
||||||
(config.get("behavior.addons-url"),
|
|
||||||
plugin_dict["z"]),
|
|
||||||
plugin_dict))
|
|
||||||
config.set("behavior.last-check-for-addon-updates",
|
|
||||||
datetime.date.today().strftime("%Y/%m/%d"))
|
|
||||||
count += 1
|
|
||||||
if fp:
|
|
||||||
fp.close()
|
|
||||||
else:
|
else:
|
||||||
LOG.debug("Checking Addons Failed")
|
LOG.debug("Checking Addons Failed")
|
||||||
LOG.debug("Done checking!")
|
LOG.debug("Done checking!")
|
||||||
|
|
||||||
|
return addon_list
|
||||||
|
|
||||||
|
def get_all_addons():
|
||||||
|
projects = config.get('behavior.addons-projects')
|
||||||
|
all_addons = []
|
||||||
|
for project, url, enabled in projects:
|
||||||
|
if enabled:
|
||||||
|
addons_list = get_addons(project, url)
|
||||||
|
all_addons.extend(addons_list)
|
||||||
|
return all_addons
|
||||||
|
|
||||||
|
def available_updates():
|
||||||
|
|
||||||
|
whattypes = config.get('behavior.check-for-addon-update-types')
|
||||||
|
addon_update_list = []
|
||||||
|
for plugin_dict in get_all_addons():
|
||||||
|
if '_v' in plugin_dict:
|
||||||
|
LOG.debug("Comparing %s > %s" %
|
||||||
|
(version_str_to_tup(plugin_dict["v"], 3),
|
||||||
|
version_str_to_tup(plugin_dict["_v"], 3)))
|
||||||
|
if (version_str_to_tup(plugin_dict["v"], 3) >
|
||||||
|
version_str_to_tup(plugin_dict["_v"], 3)):
|
||||||
|
LOG.debug(" Downloading '%s'..." % plugin_dict["z"])
|
||||||
|
if "update" in whattypes:
|
||||||
|
if (not config.get('behavior.do-not-show-previously-seen-addon-updates') or
|
||||||
|
plugin_dict["i"] not in config.get('behavior.previously-seen-addon-updates')):
|
||||||
|
addon_update_list.append((_("Updated"),
|
||||||
|
"%s/download/%s" %
|
||||||
|
(plugin_dict["_u"],
|
||||||
|
plugin_dict["z"]),
|
||||||
|
plugin_dict))
|
||||||
|
else:
|
||||||
|
LOG.debug(" '%s' is ok" % plugin_dict["n"])
|
||||||
|
else:
|
||||||
|
LOG.debug(" '%s' is not installed" % plugin_dict["n"])
|
||||||
|
if "new" in whattypes:
|
||||||
|
if (not config.get('behavior.do-not-show-previously-seen-addon-updates') or
|
||||||
|
plugin_dict["i"] not in config.get('behavior.previously-seen-addon-updates')):
|
||||||
|
addon_update_list.append((_("New", "updates"),
|
||||||
|
"%s/download/%s" %
|
||||||
|
(plugin_dict["_u"],
|
||||||
|
plugin_dict["z"]),
|
||||||
|
plugin_dict))
|
||||||
|
config.set("behavior.last-check-for-addon-updates",
|
||||||
|
datetime.date.today().strftime("%Y/%m/%d"))
|
||||||
|
|
||||||
return addon_update_list
|
return addon_update_list
|
||||||
|
|
||||||
def load_addon_file(path, callback=None):
|
def load_addon_file(path, callback=None):
|
||||||
|
171
gramps/gen/utils/requirements.py
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#
|
||||||
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023 Nick Hall
|
||||||
|
#
|
||||||
|
# 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 2 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, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Python modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
from importlib.util import find_spec
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GTK modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
import gi
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Gramps modules
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
from .file import search_for
|
||||||
|
from ..config import config
|
||||||
|
from ..const import GRAMPS_LOCALE as glocale
|
||||||
|
_ = glocale.translation.sgettext
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Requirements
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class Requirements():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.mod_list = []
|
||||||
|
self.gi_list = []
|
||||||
|
self.exe_list = []
|
||||||
|
|
||||||
|
def check_mod(self, module):
|
||||||
|
'''
|
||||||
|
Check to see if a given module is available.
|
||||||
|
'''
|
||||||
|
if module in self.mod_list:
|
||||||
|
return True
|
||||||
|
if find_spec(module) is not None:
|
||||||
|
self.mod_list.append(module)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_gi(self, module_spec):
|
||||||
|
'''
|
||||||
|
Check to see if a GObject introspection module is available.
|
||||||
|
'''
|
||||||
|
if module_spec in self.gi_list:
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
gi.require_version(*module_spec)
|
||||||
|
self.gi_list.append(module_spec)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_exe(self, executable):
|
||||||
|
'''
|
||||||
|
Check to see if a given executable is available.
|
||||||
|
'''
|
||||||
|
if executable in self.exe_list:
|
||||||
|
return True
|
||||||
|
if search_for(executable):
|
||||||
|
self.exe_list.append(executable)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check(self, gi_list, exe_list, mod_list, install=False):
|
||||||
|
'''
|
||||||
|
Check lists of requirements.
|
||||||
|
'''
|
||||||
|
for module_spec in gi_list:
|
||||||
|
if not self.check_gi(module_spec):
|
||||||
|
return False
|
||||||
|
for executable in exe_list:
|
||||||
|
if not self.check_exe(executable):
|
||||||
|
return False
|
||||||
|
if install and config.get('behavior.addons-allow-install'):
|
||||||
|
return True
|
||||||
|
for module in mod_list:
|
||||||
|
if not self.check_mod(module):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_addon(self, addon, install=False):
|
||||||
|
'''
|
||||||
|
Check the requirements of a given addon.
|
||||||
|
'''
|
||||||
|
return self.check(addon.get("rg", []),
|
||||||
|
addon.get("re", []),
|
||||||
|
addon.get("rm", []),
|
||||||
|
install)
|
||||||
|
|
||||||
|
def check_plugin(self, plugin):
|
||||||
|
'''
|
||||||
|
Check the requirements of a given plugin.
|
||||||
|
'''
|
||||||
|
return self.check(plugin.requires_gi,
|
||||||
|
plugin.requires_exe,
|
||||||
|
plugin.requires_mod)
|
||||||
|
|
||||||
|
def install(self, addon):
|
||||||
|
'''
|
||||||
|
Return a list of modules required to be installed.
|
||||||
|
'''
|
||||||
|
install_list = []
|
||||||
|
if "rm" in addon:
|
||||||
|
for module in addon.get("rm"):
|
||||||
|
if not self.check_mod(module):
|
||||||
|
install_list.append(module)
|
||||||
|
return install_list
|
||||||
|
|
||||||
|
def info(self, addon):
|
||||||
|
'''
|
||||||
|
Provide the requirements status of a given addon.
|
||||||
|
'''
|
||||||
|
info = []
|
||||||
|
if "rm" in addon:
|
||||||
|
info.append(_('Python modules'))
|
||||||
|
table = []
|
||||||
|
for module in addon.get("rm"):
|
||||||
|
result = self.check_mod(module)
|
||||||
|
table.append([module, tick_cross(result)])
|
||||||
|
info.append(table)
|
||||||
|
if "rg" in addon:
|
||||||
|
info.append(_('GObject introspection modules'))
|
||||||
|
table = []
|
||||||
|
for module_spec in addon.get("rg"):
|
||||||
|
result = self.check_gi(module_spec)
|
||||||
|
table.append([' '.join(module_spec), tick_cross(result)])
|
||||||
|
info.append(table)
|
||||||
|
if "re" in addon:
|
||||||
|
info.append(_('Executables'))
|
||||||
|
table = []
|
||||||
|
for executable in addon.get("re"):
|
||||||
|
result = self.check_exe(executable)
|
||||||
|
table.append([executable, tick_cross(result)])
|
||||||
|
info.append(table)
|
||||||
|
return info
|
||||||
|
|
||||||
|
def tick_cross(value):
|
||||||
|
'''
|
||||||
|
Return a tick for True or a cross for False
|
||||||
|
'''
|
||||||
|
return '\u2714' if value else '\u2718'
|
@ -563,7 +563,6 @@ class GrampsPreferences(ConfigureDialog):
|
|||||||
page_funcs = (
|
page_funcs = (
|
||||||
self.add_data_panel,
|
self.add_data_panel,
|
||||||
self.add_general_panel,
|
self.add_general_panel,
|
||||||
self.add_addons_panel,
|
|
||||||
self.add_famtree_panel,
|
self.add_famtree_panel,
|
||||||
self.add_import_panel,
|
self.add_import_panel,
|
||||||
self.add_limits_panel,
|
self.add_limits_panel,
|
||||||
@ -1543,29 +1542,12 @@ class GrampsPreferences(ConfigureDialog):
|
|||||||
self.pformat.set_model(model)
|
self.pformat.set_model(model)
|
||||||
self.pformat.set_active(0)
|
self.pformat.set_active(0)
|
||||||
|
|
||||||
def check_for_type_changed(self, obj):
|
|
||||||
active = obj.get_active()
|
|
||||||
if active == 0: # update
|
|
||||||
config.set('behavior.check-for-addon-update-types', ["update"])
|
|
||||||
elif active == 1: # update
|
|
||||||
config.set('behavior.check-for-addon-update-types', ["new"])
|
|
||||||
elif active == 2: # update
|
|
||||||
config.set('behavior.check-for-addon-update-types',
|
|
||||||
["update", "new"])
|
|
||||||
|
|
||||||
def toggle_tag_on_import(self, obj):
|
def toggle_tag_on_import(self, obj):
|
||||||
"""
|
"""
|
||||||
Update Entry sensitive for tag on import.
|
Update Entry sensitive for tag on import.
|
||||||
"""
|
"""
|
||||||
self.tag_format_entry.set_sensitive(obj.get_active())
|
self.tag_format_entry.set_sensitive(obj.get_active())
|
||||||
|
|
||||||
def check_for_updates_changed(self, obj):
|
|
||||||
"""
|
|
||||||
Save "Check for addon updates" option.
|
|
||||||
"""
|
|
||||||
active = obj.get_active()
|
|
||||||
config.set('behavior.check-for-addon-updates', active)
|
|
||||||
|
|
||||||
def date_format_changed(self, obj):
|
def date_format_changed(self, obj):
|
||||||
"""
|
"""
|
||||||
Save "Date format" option.
|
Save "Date format" option.
|
||||||
@ -1728,11 +1710,11 @@ class GrampsPreferences(ConfigureDialog):
|
|||||||
extra_callback=self.cb_toolbar_changed)
|
extra_callback=self.cb_toolbar_changed)
|
||||||
|
|
||||||
row += 1
|
row += 1
|
||||||
# Show Plugins Icon:
|
# Show Addons Icon:
|
||||||
self.add_checkbox(
|
self.add_checkbox(
|
||||||
grid, _("Show Plugins icon on toolbar"),
|
grid, _("Show Addons icon on toolbar"),
|
||||||
row, 'interface.toolbar-plugin', start=1, stop=3,
|
row, 'interface.toolbar-addons', start=1, stop=3,
|
||||||
tooltip=_("Show or hide the Plugins icon on the toolbar."),
|
tooltip=_("Show or hide the Addons icon on the toolbar."),
|
||||||
extra_callback=self.cb_toolbar_changed)
|
extra_callback=self.cb_toolbar_changed)
|
||||||
|
|
||||||
row += 1
|
row += 1
|
||||||
@ -1794,101 +1776,6 @@ class GrampsPreferences(ConfigureDialog):
|
|||||||
|
|
||||||
return _('General'), grid
|
return _('General'), grid
|
||||||
|
|
||||||
def add_addons_panel(self, configdialog):
|
|
||||||
"""
|
|
||||||
Config tab with 'Addons' install settings.
|
|
||||||
"""
|
|
||||||
grid = self.create_grid()
|
|
||||||
|
|
||||||
row = 1
|
|
||||||
label = self.add_text(
|
|
||||||
grid, _('Configuration settings to have Gramps check for'
|
|
||||||
' new or updated third party Addons and Plugins.'
|
|
||||||
' The Plugin Manager has the complete list of installed'
|
|
||||||
' Addons and Plugins and their activation status.\n'), row,
|
|
||||||
line_wrap=True, start=0, stop=9)
|
|
||||||
label.set_margin_top(10)
|
|
||||||
|
|
||||||
row += 1
|
|
||||||
# Check for addon updates:
|
|
||||||
obox = Gtk.ComboBoxText()
|
|
||||||
formats = [_("Never"),
|
|
||||||
_("Once a month"),
|
|
||||||
_("Once a week"),
|
|
||||||
_("Once a day"),
|
|
||||||
_("Always"), ]
|
|
||||||
list(map(obox.append_text, formats))
|
|
||||||
active = config.get('behavior.check-for-addon-updates')
|
|
||||||
obox.set_active(active)
|
|
||||||
obox.connect('changed', self.check_for_updates_changed)
|
|
||||||
lwidget = BasicLabel(_("%s: ") % _('Check for addon updates'))
|
|
||||||
grid.attach(lwidget, 2, row, 1, 1)
|
|
||||||
grid.attach(obox, 3, row, 1, 1)
|
|
||||||
|
|
||||||
row += 1
|
|
||||||
self.whattype_box = Gtk.ComboBoxText()
|
|
||||||
formats = [_("Updated addons only"),
|
|
||||||
_("New addons only"),
|
|
||||||
_("New and updated addons")]
|
|
||||||
list(map(self.whattype_box.append_text, formats))
|
|
||||||
whattype = config.get('behavior.check-for-addon-update-types')
|
|
||||||
if "new" in whattype and "update" in whattype:
|
|
||||||
self.whattype_box.set_active(2)
|
|
||||||
elif "new" in whattype:
|
|
||||||
self.whattype_box.set_active(1)
|
|
||||||
elif "update" in whattype:
|
|
||||||
self.whattype_box.set_active(0)
|
|
||||||
self.whattype_box.connect('changed', self.check_for_type_changed)
|
|
||||||
lwidget = BasicLabel(_("%s: ") % _('What to check'))
|
|
||||||
grid.attach(lwidget, 2, row, 1, 1)
|
|
||||||
grid.attach(self.whattype_box, 3, row, 1, 1)
|
|
||||||
|
|
||||||
row += 1
|
|
||||||
self.add_entry(grid, _('Where to check'), row,
|
|
||||||
'behavior.addons-url', col_attach=2)
|
|
||||||
|
|
||||||
row += 1
|
|
||||||
self.add_checkbox(
|
|
||||||
grid, _('Do not ask about previously notified addons'),
|
|
||||||
row, 'behavior.do-not-show-previously-seen-addon-updates',
|
|
||||||
start=2, stop=9)
|
|
||||||
|
|
||||||
row += 1
|
|
||||||
button = Gtk.Button(label=_("Check for updated addons now"))
|
|
||||||
button.connect("clicked", self.check_for_updates)
|
|
||||||
button.set_hexpand(False)
|
|
||||||
button.set_halign(Gtk.Align.CENTER)
|
|
||||||
grid.attach(button, 1, row, 3, 1)
|
|
||||||
|
|
||||||
return _('Addons'), grid
|
|
||||||
|
|
||||||
def check_for_updates(self, button):
|
|
||||||
try:
|
|
||||||
addon_update_list = available_updates()
|
|
||||||
except:
|
|
||||||
OkDialog(_("Checking Addons Failed"),
|
|
||||||
_("The addon repository appears to be unavailable. "
|
|
||||||
"Please try again later."),
|
|
||||||
parent=self.window)
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(addon_update_list) > 0:
|
|
||||||
rescan = PluginWindows.UpdateAddons(self.uistate, self.track,
|
|
||||||
addon_update_list).rescan
|
|
||||||
self.uistate.viewmanager.do_reg_plugins(self.dbstate, self.uistate,
|
|
||||||
rescan=rescan)
|
|
||||||
else:
|
|
||||||
check_types = config.get('behavior.check-for-addon-update-types')
|
|
||||||
OkDialog(
|
|
||||||
_("There are no available addons of this type"),
|
|
||||||
_("Checked for '%s'") %
|
|
||||||
_("' and '").join([_(t) for t in check_types]),
|
|
||||||
parent=self.window)
|
|
||||||
|
|
||||||
# List of translated strings used here
|
|
||||||
# Dead code for l10n
|
|
||||||
_('new'), _('update')
|
|
||||||
|
|
||||||
def database_backend_changed(self, obj):
|
def database_backend_changed(self, obj):
|
||||||
"""
|
"""
|
||||||
Update Database Backend.
|
Update Database Backend.
|
||||||
|
@ -409,10 +409,10 @@ TOOLS = {
|
|||||||
'win.Tools',
|
'win.Tools',
|
||||||
_('Open the tools dialog'),
|
_('Open the tools dialog'),
|
||||||
_('Tools')),
|
_('Tools')),
|
||||||
'plugin': ('gramps-plugin-manager',
|
'addons': ('gramps-addon',
|
||||||
'win.PluginStatus',
|
'win.AddonManager',
|
||||||
_('Open Plugin Manager'),
|
_('Open Addon Manager'),
|
||||||
_('Plugins')),
|
_('Addons')),
|
||||||
'preference': ('gramps-preferences',
|
'preference': ('gramps-preferences',
|
||||||
'app.preferences',
|
'app.preferences',
|
||||||
_('Open Preferences'),
|
_('Open Preferences'),
|
||||||
|
@ -173,6 +173,12 @@ UIDEFAULT = (
|
|||||||
<attribute name="action">win.Clipboard</attribute>
|
<attribute name="action">win.Clipboard</attribute>
|
||||||
<attribute name="label" translatable="yes">Clip_board</attribute>
|
<attribute name="label" translatable="yes">Clip_board</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="action">win.AddonManager</attribute>
|
||||||
|
<attribute name="label" translatable="yes">'''
|
||||||
|
'''_Addon Manager...</attribute>
|
||||||
|
</item>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<placeholder groups='OSX' id='osxpref'>
|
<placeholder groups='OSX' id='osxpref'>
|
||||||
|
@ -28,6 +28,10 @@
|
|||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
|
from html import escape
|
||||||
|
import threading
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -44,6 +48,7 @@ LOG = logging.getLogger(".gui.plug")
|
|||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
|
from gi.repository import GLib
|
||||||
from gi.repository import Pango
|
from gi.repository import Pango
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
|
||||||
@ -57,7 +62,8 @@ _ = glocale.translation.gettext
|
|||||||
ngettext = glocale.translation.ngettext # else "nearby" comments are ignored
|
ngettext = glocale.translation.ngettext # else "nearby" comments are ignored
|
||||||
from ..managedwindow import ManagedWindow
|
from ..managedwindow import ManagedWindow
|
||||||
from gramps.gen.errors import UnavailableError, WindowActiveError
|
from gramps.gen.errors import UnavailableError, WindowActiveError
|
||||||
from gramps.gen.plug import PluginRegister, PTYPE_STR, load_addon_file
|
from gramps.gen.plug import (PluginRegister, PTYPE_STR, load_addon_file,
|
||||||
|
AUDIENCETEXT, STATUSTEXT)
|
||||||
from ..utils import open_file_with_default_application
|
from ..utils import open_file_with_default_application
|
||||||
from ..pluginmanager import GuiPluginManager
|
from ..pluginmanager import GuiPluginManager
|
||||||
from . import tool
|
from . import tool
|
||||||
@ -66,11 +72,17 @@ from ..dialog import InfoDialog, OkDialog
|
|||||||
from ..editors import EditPerson
|
from ..editors import EditPerson
|
||||||
from ..glade import Glade
|
from ..glade import Glade
|
||||||
from ..listmodel import ListModel, NOSORT, TOGGLE
|
from ..listmodel import ListModel, NOSORT, TOGGLE
|
||||||
from gramps.gen.const import URL_WIKISTRING, USER_HOME, WIKI_EXTRAPLUGINS_RAWDATA
|
from gramps.gen.const import URL_WIKISTRING, USER_HOME, WIKI_EXTRAPLUGINS_RAWDATA, COLON
|
||||||
from gramps.gen.config import config
|
from gramps.gen.config import config
|
||||||
from ..widgets.progressdialog import (LongOpStatus, ProgressMonitor,
|
from ..widgets.progressdialog import (LongOpStatus, ProgressMonitor,
|
||||||
GtkProgressDialog)
|
GtkProgressDialog)
|
||||||
|
|
||||||
|
from gramps.gen.plug.utils import get_all_addons, available_updates
|
||||||
|
from ..display import display_help, display_url
|
||||||
|
from gramps.gui.widgets import BasicLabel, SimpleButton
|
||||||
|
from gramps.gen.utils.requirements import Requirements
|
||||||
|
from gramps.gen.const import USER_PLUGINS
|
||||||
|
|
||||||
def display_message(message):
|
def display_message(message):
|
||||||
"""
|
"""
|
||||||
A default callback for displaying messages.
|
A default callback for displaying messages.
|
||||||
@ -79,6 +91,713 @@ def display_message(message):
|
|||||||
|
|
||||||
RELOAD = 777 # A custom Gtk response_type for the Reload button
|
RELOAD = 777 # A custom Gtk response_type for the Reload button
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# GetAddons
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class GetAddons(threading.Thread):
|
||||||
|
"""
|
||||||
|
A class for retrieving a list of addons as a background task.
|
||||||
|
"""
|
||||||
|
def __init__(self, callback):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.callback = callback
|
||||||
|
self.addon_list = []
|
||||||
|
self.__pmgr = GuiPluginManager.get_instance()
|
||||||
|
|
||||||
|
def emit_signal(self):
|
||||||
|
self.callback(self.addon_list)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.addon_list = self.__get_addon_list()
|
||||||
|
GLib.idle_add(self.emit_signal)
|
||||||
|
|
||||||
|
def __get_addon_list(self):
|
||||||
|
return get_all_addons()
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# ProjectRow
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class ProjectRow(Gtk.ListBoxRow):
|
||||||
|
"""
|
||||||
|
A class to display an external addons repository.
|
||||||
|
"""
|
||||||
|
def __init__(self, manager, project):
|
||||||
|
Gtk.ListBoxRow.__init__(self)
|
||||||
|
self.manager = manager
|
||||||
|
self.project = project
|
||||||
|
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
hbox.set_spacing(12)
|
||||||
|
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
vbox.set_spacing(6)
|
||||||
|
|
||||||
|
self.check = Gtk.CheckButton()
|
||||||
|
|
||||||
|
hbox.pack_start(self.check, False, False, 0)
|
||||||
|
hbox.pack_start(vbox, True, True, 0)
|
||||||
|
|
||||||
|
self.name = Gtk.Label()
|
||||||
|
self.name.set_use_markup(True)
|
||||||
|
self.name.set_halign(Gtk.Align.START)
|
||||||
|
self.url = Gtk.Label()
|
||||||
|
self.url.set_halign(Gtk.Align.START)
|
||||||
|
vbox.pack_start(self.name, False, False, 0)
|
||||||
|
vbox.pack_start(self.url, False, False, 0)
|
||||||
|
|
||||||
|
self.add(hbox)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
self.check.connect('toggled', self.__check_toggled)
|
||||||
|
|
||||||
|
def __check_toggled(self, check):
|
||||||
|
self.project[2] = check.get_active()
|
||||||
|
projects = [row.project for row in self.manager.project_list]
|
||||||
|
config.set('behavior.addons-projects', projects)
|
||||||
|
self.manager.refresh()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Update the row when the project data has been updated.
|
||||||
|
"""
|
||||||
|
text = self.project[0]
|
||||||
|
self.name.set_markup('<span weight="bold">%s</span>' % text)
|
||||||
|
self.url.set_text(self.project[1])
|
||||||
|
self.check.set_active(self.project[2])
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# AddonManager
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class AddonRow(Gtk.ListBoxRow):
|
||||||
|
"""
|
||||||
|
A class representing an addon in the Addon Manager.
|
||||||
|
"""
|
||||||
|
def __init__(self, manager, addon, req, window):
|
||||||
|
Gtk.ListBoxRow.__init__(self)
|
||||||
|
self.manager = manager
|
||||||
|
self.addon = addon
|
||||||
|
self.req = req
|
||||||
|
self.window = window
|
||||||
|
|
||||||
|
context = self.get_style_context()
|
||||||
|
context.add_class('addon-row')
|
||||||
|
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
vbox.set_spacing(6)
|
||||||
|
|
||||||
|
text = escape(addon['n'])
|
||||||
|
title = Gtk.Label()
|
||||||
|
title.set_text('<span size="larger" weight="bold">%s</span>' % text)
|
||||||
|
title.set_use_markup(True)
|
||||||
|
title.set_halign(Gtk.Align.CENTER)
|
||||||
|
vbox.pack_start(title, False, False, 0)
|
||||||
|
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
hbox.set_spacing(6)
|
||||||
|
lozenge = self.__create_lozenge(_('Project'), addon['_p'])
|
||||||
|
hbox.pack_start(lozenge, False, False, 0)
|
||||||
|
lozenge = self.__create_lozenge(_('Type'), addon['t'])
|
||||||
|
hbox.pack_start(lozenge, False, False, 0)
|
||||||
|
lozenge = self.__create_lozenge(_('Audience'), AUDIENCETEXT[addon['a']])
|
||||||
|
hbox.pack_start(lozenge, False, False, 0)
|
||||||
|
lozenge = self.__create_lozenge(_('Status'), STATUSTEXT[addon['s']])
|
||||||
|
hbox.pack_start(lozenge, False, False, 0)
|
||||||
|
lozenge = self.__create_lozenge(_('Version'), addon['v'])
|
||||||
|
hbox.pack_start(lozenge, False, False, 0)
|
||||||
|
if '_v' in addon:
|
||||||
|
lozenge = self.__create_lozenge(_('Installed version'), addon['_v'])
|
||||||
|
hbox.pack_end(lozenge, False, False, 0)
|
||||||
|
|
||||||
|
vbox.pack_start(hbox, False, False, 0)
|
||||||
|
|
||||||
|
text = addon['d']
|
||||||
|
descr = Gtk.Label()
|
||||||
|
descr.set_text(text)
|
||||||
|
descr.set_halign(Gtk.Align.START)
|
||||||
|
descr.set_hexpand(False)
|
||||||
|
descr.set_line_wrap(True)
|
||||||
|
descr.set_line_wrap_mode(Pango.WrapMode.WORD)
|
||||||
|
vbox.pack_start(descr, False, False, 0)
|
||||||
|
|
||||||
|
bb = Gtk.Box()
|
||||||
|
bb.set_spacing(6)
|
||||||
|
|
||||||
|
if '_v' not in addon and req.check_addon(addon, install=True):
|
||||||
|
b1 = Gtk.Button(label=_("Install"))
|
||||||
|
b1.set_label(_("Install"))
|
||||||
|
b1.connect('clicked', self.__on_install_clicked, addon)
|
||||||
|
bb.pack_end(b1, False, False, 0)
|
||||||
|
|
||||||
|
if addon['h']:
|
||||||
|
b2 = Gtk.Button(label=_("Wiki"))
|
||||||
|
b2.connect('clicked', self.__on_wiki_clicked, addon['h'])
|
||||||
|
bb.pack_start(b2, False, False, 0)
|
||||||
|
|
||||||
|
if not req.check_addon(addon):
|
||||||
|
b3 = Gtk.Button(label=_("Requires"))
|
||||||
|
b3.connect('clicked', self.__on_requires_clicked, addon)
|
||||||
|
bb.pack_start(b3, False, False, 0)
|
||||||
|
|
||||||
|
if '_v' in addon and addon['_v'] != addon['v']:
|
||||||
|
b4 = Gtk.Button(label=_("Upgrade"))
|
||||||
|
b4.connect('clicked', self.__on_upgrade_clicked, addon)
|
||||||
|
bb.pack_end(b4, False, False, 0)
|
||||||
|
|
||||||
|
vbox.pack_start(bb, False, False, 0)
|
||||||
|
|
||||||
|
self.add(vbox)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def __on_install_clicked(self, button, addon):
|
||||||
|
"""
|
||||||
|
Install the addon and possibly some required python modules.
|
||||||
|
"""
|
||||||
|
# Install required modules
|
||||||
|
for package in self.req.install(addon):
|
||||||
|
try:
|
||||||
|
subprocess.check_output(
|
||||||
|
[sys.executable, '-m', 'pip', 'install', package],
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
button.set_sensitive(False)
|
||||||
|
InfoDialog(_('Module installation failed'),
|
||||||
|
err.output.decode("utf-8"),
|
||||||
|
parent=self.window)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Install addon
|
||||||
|
path = addon['_u'] + '/download/' + addon['z']
|
||||||
|
load_addon_file(path)
|
||||||
|
self.manager.install_addon(addon['i'])
|
||||||
|
self.manager.refresh()
|
||||||
|
|
||||||
|
def __on_wiki_clicked(self, button, url):
|
||||||
|
"""
|
||||||
|
Display the wiki page for the addon.
|
||||||
|
"""
|
||||||
|
if url.startswith(('http://', 'https://')):
|
||||||
|
display_url(url)
|
||||||
|
else:
|
||||||
|
display_help(url)
|
||||||
|
|
||||||
|
def __on_requires_clicked(self, button, addon):
|
||||||
|
"""
|
||||||
|
Display the requirements for the addon.
|
||||||
|
"""
|
||||||
|
InfoDialog(_('Requirements'), self.req.info(addon), parent=self.window)
|
||||||
|
|
||||||
|
def __on_upgrade_clicked(self, button, addon):
|
||||||
|
"""
|
||||||
|
Upgrade the addon.
|
||||||
|
"""
|
||||||
|
path = addon['_u'] + '/download/' + addon['z']
|
||||||
|
load_addon_file(path)
|
||||||
|
self.manager.upgrade_addon(addon['i'])
|
||||||
|
self.manager.refresh()
|
||||||
|
|
||||||
|
def __create_lozenge(self, description, text):
|
||||||
|
"""
|
||||||
|
Create a lozenge shaped label to display addon information.
|
||||||
|
"""
|
||||||
|
label = Gtk.Label()
|
||||||
|
label.set_tooltip_text(description)
|
||||||
|
context = label.get_style_context()
|
||||||
|
context.add_class('lozenge')
|
||||||
|
label.set_text(text)
|
||||||
|
label.set_margin_start(6)
|
||||||
|
return label
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# AddonManager
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
class AddonManager(ManagedWindow):
|
||||||
|
"""
|
||||||
|
A class to allow the user to easily select addons to install.
|
||||||
|
"""
|
||||||
|
def __init__(self, dbstate, uistate, track):
|
||||||
|
self.dbstate = dbstate
|
||||||
|
self.title = _("Addon Manager")
|
||||||
|
ManagedWindow.__init__(self, uistate, [], self)
|
||||||
|
|
||||||
|
self.__pmgr = GuiPluginManager.get_instance()
|
||||||
|
self.__preg = PluginRegister.get_instance()
|
||||||
|
dialog = Gtk.Dialog(title="", transient_for=uistate.window,
|
||||||
|
destroy_with_parent=True)
|
||||||
|
dialog.add_button(_('Refresh'), RELOAD)
|
||||||
|
dialog.add_button(_('_Close'), Gtk.ResponseType.CLOSE)
|
||||||
|
self.set_window(dialog, None, self.title)
|
||||||
|
|
||||||
|
self.req = Requirements()
|
||||||
|
|
||||||
|
self.setup_configs('interface.addonmanager', 750, 400)
|
||||||
|
self.window.connect('response', self.__on_dialog_button)
|
||||||
|
|
||||||
|
book = Gtk.Notebook()
|
||||||
|
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
vbox.set_spacing(6)
|
||||||
|
vbox.set_margin_start(6)
|
||||||
|
vbox.set_margin_end(6)
|
||||||
|
vbox.set_margin_top(6)
|
||||||
|
vbox.set_margin_bottom(6)
|
||||||
|
|
||||||
|
self.search = Gtk.Entry()
|
||||||
|
self.search.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
|
||||||
|
'system-search')
|
||||||
|
self.search.connect("changed", self.__combo_changed)
|
||||||
|
vbox.pack_start(self.search, False, False, 0)
|
||||||
|
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
hbox.set_spacing(6)
|
||||||
|
self.lb = Gtk.ListBox()
|
||||||
|
self.lb.set_activate_on_single_click(False)
|
||||||
|
|
||||||
|
label = Gtk.Label(label=_('Filters') + COLON)
|
||||||
|
label.set_margin_end(12)
|
||||||
|
hbox.pack_start(label, False, False, 0)
|
||||||
|
|
||||||
|
self.projects = config.get('behavior.addons-projects')
|
||||||
|
self.project_combo = Gtk.ComboBoxText()
|
||||||
|
self.project_combo.set_entry_text_column(0)
|
||||||
|
self.project_combo.connect("changed", self.__combo_changed)
|
||||||
|
self.project_combo.append_text(_('All'))
|
||||||
|
for project in self.projects:
|
||||||
|
self.project_combo.append_text(project[0])
|
||||||
|
self.project_combo.set_active(0)
|
||||||
|
hbox.pack_start(self.project_combo, False, False, 0)
|
||||||
|
|
||||||
|
self.type_combo = Gtk.ComboBoxText()
|
||||||
|
self.type_combo.set_entry_text_column(0)
|
||||||
|
self.type_combo.connect("changed", self.__combo_changed)
|
||||||
|
self.type_combo.append_text(_('All'))
|
||||||
|
for typestr in PTYPE_STR.values():
|
||||||
|
self.type_combo.append_text(typestr)
|
||||||
|
self.type_combo.set_active(0)
|
||||||
|
hbox.pack_start(self.type_combo, False, False, 0)
|
||||||
|
|
||||||
|
audience_store = Gtk.ListStore(int, str)
|
||||||
|
audience_store.append([-1, _('All')])
|
||||||
|
for key, value in AUDIENCETEXT.items():
|
||||||
|
audience_store.append([key, value])
|
||||||
|
self.audience_combo = Gtk.ComboBox()
|
||||||
|
self.audience_combo.set_model(audience_store)
|
||||||
|
self.audience_combo.set_entry_text_column(1)
|
||||||
|
self.audience_combo.connect("changed", self.__combo_changed)
|
||||||
|
self.audience_combo.set_active(1)
|
||||||
|
renderer_text = Gtk.CellRendererText()
|
||||||
|
self.audience_combo.pack_start(renderer_text, True)
|
||||||
|
self.audience_combo.add_attribute(renderer_text, "text", 1)
|
||||||
|
hbox.pack_start(self.audience_combo, False, False, 0)
|
||||||
|
|
||||||
|
status_store = Gtk.ListStore(int, str)
|
||||||
|
status_store.append([-1, _('All')])
|
||||||
|
for key, value in STATUSTEXT.items():
|
||||||
|
status_store.append([key, value])
|
||||||
|
self.status_combo = Gtk.ComboBox()
|
||||||
|
self.status_combo.set_model(status_store)
|
||||||
|
self.status_combo.set_entry_text_column(1)
|
||||||
|
self.status_combo.connect("changed", self.__combo_changed)
|
||||||
|
self.status_combo.set_active(4)
|
||||||
|
renderer_text = Gtk.CellRendererText()
|
||||||
|
self.status_combo.pack_start(renderer_text, True)
|
||||||
|
self.status_combo.add_attribute(renderer_text, "text", 1)
|
||||||
|
hbox.pack_start(self.status_combo, False, False, 0)
|
||||||
|
|
||||||
|
clear = Gtk.Button.new_from_icon_name('edit-clear', Gtk.IconSize.BUTTON)
|
||||||
|
clear.connect("clicked", self.__clear_filters)
|
||||||
|
hbox.pack_start(clear, False, False, 0)
|
||||||
|
|
||||||
|
vbox.pack_start(hbox, False, False, 0)
|
||||||
|
|
||||||
|
sw = Gtk.ScrolledWindow()
|
||||||
|
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||||
|
sw.add(self.lb)
|
||||||
|
vbox.pack_start(sw, True, True, 0)
|
||||||
|
|
||||||
|
book.append_page(vbox, Gtk.Label(_("Addons")))
|
||||||
|
|
||||||
|
grid = self.create_settings_panel()
|
||||||
|
book.append_page(grid, Gtk.Label(_("Settings")))
|
||||||
|
|
||||||
|
grid = self.create_projects_panel()
|
||||||
|
book.append_page(grid, Gtk.Label(_("Projects")))
|
||||||
|
|
||||||
|
for project in self.projects:
|
||||||
|
self.project_list.add(ProjectRow(self, project))
|
||||||
|
|
||||||
|
self.window.get_content_area().pack_start(book, True, True, 0)
|
||||||
|
|
||||||
|
self.lb.set_sort_func(self.__sort_func)
|
||||||
|
self.lb.set_filter_func(self.__filter_func)
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
"""
|
||||||
|
Refresh the addons list.
|
||||||
|
"""
|
||||||
|
for child in self.lb.get_children():
|
||||||
|
self.lb.remove(child)
|
||||||
|
self.__placeholder(_('Loading...'))
|
||||||
|
|
||||||
|
thread = GetAddons(self.load_addons)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def upgrade_addon(self, addon_id):
|
||||||
|
"""
|
||||||
|
Upgrade the given addon.
|
||||||
|
"""
|
||||||
|
pdata = self.__preg.get_plugin(addon_id)
|
||||||
|
self.__pmgr.reg_plugin_dir(pdata.directory, self.dbstate, self.uistate,
|
||||||
|
load_on_reg=True)
|
||||||
|
pdata = self.__preg.get_plugin(addon_id)
|
||||||
|
self.__pmgr.load_plugin(pdata)
|
||||||
|
|
||||||
|
def install_addon(self, addon_id):
|
||||||
|
"""
|
||||||
|
Install the given addon.
|
||||||
|
"""
|
||||||
|
self.__pmgr.reg_plugins(USER_PLUGINS, self.dbstate, self.uistate,
|
||||||
|
load_on_reg=True)
|
||||||
|
pdata = self.__preg.get_plugin(addon_id)
|
||||||
|
self.__pmgr.load_plugin(pdata)
|
||||||
|
|
||||||
|
def build_menu_names(self, obj):
|
||||||
|
return (self.title, self.title)
|
||||||
|
|
||||||
|
def __placeholder(self, text):
|
||||||
|
"""
|
||||||
|
A placeholder label if no addons are listed.
|
||||||
|
"""
|
||||||
|
label = Gtk.Label('<span size="larger" weight="bold">%s</span>' % text)
|
||||||
|
label.set_use_markup(True)
|
||||||
|
label.show()
|
||||||
|
self.lb.set_placeholder(label)
|
||||||
|
|
||||||
|
def load_addons(self, addon_list):
|
||||||
|
"""
|
||||||
|
Populate the list box.
|
||||||
|
"""
|
||||||
|
for addon in addon_list:
|
||||||
|
self.lb.add(AddonRow(self, addon, self.req, self.window))
|
||||||
|
self.__placeholder(_('No matching addons found.'))
|
||||||
|
|
||||||
|
def __clear_filters(self, combo):
|
||||||
|
"""
|
||||||
|
Reset the filters back to their defaults.
|
||||||
|
"""
|
||||||
|
self.search.set_text('')
|
||||||
|
self.type_combo.set_active(0)
|
||||||
|
self.project_combo.set_active(0)
|
||||||
|
self.audience_combo.set_active(1)
|
||||||
|
self.status_combo.set_active(4)
|
||||||
|
|
||||||
|
def __combo_changed(self, combo):
|
||||||
|
"""
|
||||||
|
Called when a filter is changed.
|
||||||
|
"""
|
||||||
|
self.lb.invalidate_filter()
|
||||||
|
|
||||||
|
def __sort_func(self, row1, row2):
|
||||||
|
"""
|
||||||
|
Sort the addons by name.
|
||||||
|
"""
|
||||||
|
value1 = row1.addon['n']
|
||||||
|
value2 = row2.addon['n']
|
||||||
|
if value1 > value2:
|
||||||
|
return 1
|
||||||
|
elif value1 < value2:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __filter_func(self, row):
|
||||||
|
"""
|
||||||
|
Filter the addons list according to the user selection.
|
||||||
|
"""
|
||||||
|
search_text = self.search.get_text()
|
||||||
|
type_text = self.type_combo.get_active_text()
|
||||||
|
project_text = self.project_combo.get_active_text()
|
||||||
|
audience_iter = self.audience_combo.get_active_iter()
|
||||||
|
status_iter = self.status_combo.get_active_iter()
|
||||||
|
if type_text != _('All'):
|
||||||
|
if row.addon['t'] != type_text:
|
||||||
|
return False
|
||||||
|
if project_text != _('All'):
|
||||||
|
if row.addon['_p'] != project_text:
|
||||||
|
return False
|
||||||
|
model = self.audience_combo.get_model()
|
||||||
|
value = model.get_value(audience_iter, 0)
|
||||||
|
if value != -1 and row.addon['a'] != value:
|
||||||
|
return False
|
||||||
|
model = self.status_combo.get_model()
|
||||||
|
value = model.get_value(status_iter, 0)
|
||||||
|
if value != -1 and row.addon['s'] != value:
|
||||||
|
return False
|
||||||
|
if search_text and search_text not in row.addon['d']:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __on_dialog_button(self, dialog, response_id):
|
||||||
|
"""
|
||||||
|
Handle a main dialog button click.
|
||||||
|
"""
|
||||||
|
if response_id == Gtk.ResponseType.CLOSE:
|
||||||
|
self.close(dialog)
|
||||||
|
elif response_id == RELOAD:
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def create_projects_panel(self):
|
||||||
|
"""
|
||||||
|
Configuration tab with addons projects.
|
||||||
|
"""
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
vbox.set_spacing(6)
|
||||||
|
vbox.set_margin_start(6)
|
||||||
|
vbox.set_margin_end(6)
|
||||||
|
vbox.set_margin_top(6)
|
||||||
|
vbox.set_margin_bottom(6)
|
||||||
|
|
||||||
|
self.project_list = Gtk.ListBox()
|
||||||
|
self.project_list.set_activate_on_single_click(False)
|
||||||
|
self.project_list.connect('row-activated', self.__edit_project)
|
||||||
|
self.project_list.set_margin_start(6)
|
||||||
|
|
||||||
|
sw = Gtk.ScrolledWindow()
|
||||||
|
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||||
|
sw.add(self.project_list)
|
||||||
|
vbox.pack_start(sw, True, True, 0)
|
||||||
|
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
add_btn = SimpleButton('list-add', self.__add_project)
|
||||||
|
del_btn = SimpleButton('list-remove', self.__remove_project)
|
||||||
|
hbox.pack_start(add_btn, False, False, 0)
|
||||||
|
hbox.pack_start(del_btn, False, False, 0)
|
||||||
|
vbox.pack_start(hbox, False, False, 0)
|
||||||
|
|
||||||
|
return vbox
|
||||||
|
|
||||||
|
def create_settings_panel(self):
|
||||||
|
"""
|
||||||
|
Configuration tab with addons settings.
|
||||||
|
"""
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
vbox.set_spacing(6)
|
||||||
|
vbox.set_margin_start(6)
|
||||||
|
vbox.set_margin_end(6)
|
||||||
|
vbox.set_margin_top(6)
|
||||||
|
vbox.set_margin_bottom(6)
|
||||||
|
|
||||||
|
heading1 = Gtk.Label()
|
||||||
|
text = _('General')
|
||||||
|
heading1.set_text('<span weight="bold">%s</span>' % text)
|
||||||
|
heading1.set_use_markup(True)
|
||||||
|
heading1.set_halign(Gtk.Align.START)
|
||||||
|
vbox.pack_start(heading1, False, False, 0)
|
||||||
|
|
||||||
|
grid = Gtk.Grid()
|
||||||
|
grid.set_row_spacing(6)
|
||||||
|
grid.set_margin_start(6)
|
||||||
|
grid.set_margin_bottom(12)
|
||||||
|
vbox.pack_start(grid, False, False, 0)
|
||||||
|
|
||||||
|
row = 1
|
||||||
|
install = Gtk.CheckButton()
|
||||||
|
install.set_label(_('Allow Gramps to install required python modules'))
|
||||||
|
install.connect("toggled", self.install_changed)
|
||||||
|
grid.attach(install, 1, row, 1, 1)
|
||||||
|
|
||||||
|
heading2 = Gtk.Label()
|
||||||
|
text = _('Updates')
|
||||||
|
heading2.set_text('<span weight="bold">%s</span>' % text)
|
||||||
|
heading2.set_use_markup(True)
|
||||||
|
heading2.set_halign(Gtk.Align.START)
|
||||||
|
vbox.pack_start(heading2, False, False, 0)
|
||||||
|
|
||||||
|
grid = Gtk.Grid()
|
||||||
|
grid.set_row_spacing(6)
|
||||||
|
grid.set_margin_start(6)
|
||||||
|
vbox.pack_start(grid, False, False, 0)
|
||||||
|
|
||||||
|
# Check for addon updates:
|
||||||
|
row = 1
|
||||||
|
obox = Gtk.ComboBoxText()
|
||||||
|
formats = [_("Never"),
|
||||||
|
_("Once a month"),
|
||||||
|
_("Once a week"),
|
||||||
|
_("Once a day"),
|
||||||
|
_("Always"), ]
|
||||||
|
list(map(obox.append_text, formats))
|
||||||
|
active = config.get('behavior.check-for-addon-updates')
|
||||||
|
obox.set_active(active)
|
||||||
|
obox.connect('changed', self.check_for_updates_changed)
|
||||||
|
lwidget = BasicLabel(_("%s: ") % _('Check for addon updates'))
|
||||||
|
grid.attach(lwidget, 1, row, 1, 1)
|
||||||
|
grid.attach(obox, 2, row, 1, 1)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
self.whattype_box = Gtk.ComboBoxText()
|
||||||
|
formats = [_("Updated addons only"),
|
||||||
|
_("New addons only"),
|
||||||
|
_("New and updated addons")]
|
||||||
|
list(map(self.whattype_box.append_text, formats))
|
||||||
|
whattype = config.get('behavior.check-for-addon-update-types')
|
||||||
|
if "new" in whattype and "update" in whattype:
|
||||||
|
self.whattype_box.set_active(2)
|
||||||
|
elif "new" in whattype:
|
||||||
|
self.whattype_box.set_active(1)
|
||||||
|
elif "update" in whattype:
|
||||||
|
self.whattype_box.set_active(0)
|
||||||
|
self.whattype_box.connect('changed', self.check_for_type_changed)
|
||||||
|
lwidget = BasicLabel(_("%s: ") % _('What to check'))
|
||||||
|
grid.attach(lwidget, 1, row, 1, 1)
|
||||||
|
grid.attach(self.whattype_box, 2, row, 1, 1)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
previous = Gtk.CheckButton()
|
||||||
|
previous.set_label(_('Do not ask about previously notified addons'))
|
||||||
|
previous.connect("toggled", self.previous_changed)
|
||||||
|
grid.attach(previous, 1, row, 1, 1)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
button = Gtk.Button(label=_("Check for updated addons now"))
|
||||||
|
button.connect("clicked", self.check_for_updates)
|
||||||
|
button.set_hexpand(False)
|
||||||
|
button.set_halign(Gtk.Align.CENTER)
|
||||||
|
grid.attach(button, 1, row, 2, 1)
|
||||||
|
|
||||||
|
return vbox
|
||||||
|
|
||||||
|
def edit_project(self, row):
|
||||||
|
'''
|
||||||
|
Add or edit a project
|
||||||
|
'''
|
||||||
|
if row.project[0] == '':
|
||||||
|
title = _('New Project')
|
||||||
|
else:
|
||||||
|
title = _('Edit Project')
|
||||||
|
dialog = Gtk.Dialog(title=title,
|
||||||
|
transient_for=self.window)
|
||||||
|
dialog.set_border_width(12)
|
||||||
|
dialog.vbox.set_spacing(10)
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
label = Gtk.Label(label=_("%s: ") % _('Project name'))
|
||||||
|
hbox.pack_start(label, True, True, 0)
|
||||||
|
name = Gtk.Entry()
|
||||||
|
name.set_text(row.project[0])
|
||||||
|
name.set_activates_default(True)
|
||||||
|
hbox.pack_start(name, False, True, 0)
|
||||||
|
dialog.vbox.pack_start(hbox, False, True, 0)
|
||||||
|
hbox = Gtk.Box()
|
||||||
|
label = Gtk.Label(label=_("%s: ") % _('Url'))
|
||||||
|
hbox.pack_start(label, True, True, 0)
|
||||||
|
url = Gtk.Entry()
|
||||||
|
url.set_text(row.project[1])
|
||||||
|
hbox.pack_start(url, False, True, 0)
|
||||||
|
dialog.vbox.pack_start(hbox, False, True, 0)
|
||||||
|
|
||||||
|
dialog.add_buttons(_('_Cancel'), Gtk.ResponseType.CANCEL,
|
||||||
|
_('_OK'), Gtk.ResponseType.OK)
|
||||||
|
dialog.set_default_response(Gtk.ResponseType.OK)
|
||||||
|
dialog.vbox.show_all()
|
||||||
|
|
||||||
|
if dialog.run() == Gtk.ResponseType.OK:
|
||||||
|
if row.project[0] == '':
|
||||||
|
self.project_list.add(row)
|
||||||
|
row.project[0] = name.get_text()
|
||||||
|
row.project[1] = url.get_text()
|
||||||
|
row.update()
|
||||||
|
projects = [row.project for row in self.project_list]
|
||||||
|
config.set('behavior.addons-projects', projects)
|
||||||
|
self.refresh()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
def __add_project(self, button):
|
||||||
|
'''
|
||||||
|
Add a project
|
||||||
|
'''
|
||||||
|
self.edit_project(ProjectRow(self, ['', '', False]))
|
||||||
|
|
||||||
|
def __remove_project(self, button):
|
||||||
|
'''
|
||||||
|
Remove a project
|
||||||
|
'''
|
||||||
|
row = self.project_list.get_selected_row()
|
||||||
|
if row:
|
||||||
|
self.project_list.remove(row)
|
||||||
|
projects = [p for p in config.get('behavior.addons-projects')
|
||||||
|
if p[0] != row.project[0]]
|
||||||
|
config.set('behavior.addons-projects', projects)
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def __edit_project(self, listbox, row):
|
||||||
|
'''
|
||||||
|
Edit a project
|
||||||
|
'''
|
||||||
|
self.edit_project(row)
|
||||||
|
|
||||||
|
def check_for_updates(self, button):
|
||||||
|
try:
|
||||||
|
addon_update_list = available_updates()
|
||||||
|
except:
|
||||||
|
OkDialog(_("Checking Addons Failed"),
|
||||||
|
_("The addon repository appears to be unavailable. "
|
||||||
|
"Please try again later."),
|
||||||
|
parent=self.window)
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(addon_update_list) > 0:
|
||||||
|
rescan = UpdateAddons(self.uistate, self.track,
|
||||||
|
addon_update_list).rescan
|
||||||
|
self.uistate.viewmanager.do_reg_plugins(self.dbstate, self.uistate,
|
||||||
|
rescan=rescan)
|
||||||
|
else:
|
||||||
|
check_types = config.get('behavior.check-for-addon-update-types')
|
||||||
|
OkDialog(
|
||||||
|
_("There are no available addons of this type"),
|
||||||
|
_("Checked for '%s'") %
|
||||||
|
_("' and '").join([_(t) for t in check_types]),
|
||||||
|
parent=self.window)
|
||||||
|
|
||||||
|
# List of translated strings used here
|
||||||
|
# Dead code for l10n
|
||||||
|
_('new'), _('update')
|
||||||
|
|
||||||
|
def check_for_type_changed(self, obj):
|
||||||
|
active = obj.get_active()
|
||||||
|
if active == 0: # update
|
||||||
|
config.set('behavior.check-for-addon-update-types', ["update"])
|
||||||
|
elif active == 1: # update
|
||||||
|
config.set('behavior.check-for-addon-update-types', ["new"])
|
||||||
|
elif active == 2: # update
|
||||||
|
config.set('behavior.check-for-addon-update-types',
|
||||||
|
["update", "new"])
|
||||||
|
|
||||||
|
def check_for_updates_changed(self, obj):
|
||||||
|
"""
|
||||||
|
Save "Check for addon updates" option.
|
||||||
|
"""
|
||||||
|
active = obj.get_active()
|
||||||
|
config.set('behavior.check-for-addon-updates', active)
|
||||||
|
|
||||||
|
def previous_changed(self, obj):
|
||||||
|
active = obj.get_active()
|
||||||
|
config.set('behavior.do-not-show-previously-seen-addon-updates', active)
|
||||||
|
|
||||||
|
def install_changed(self, obj):
|
||||||
|
active = obj.get_active()
|
||||||
|
config.set('behavior.addons-allow-install', active)
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
@ -79,7 +79,7 @@ from gramps.gen.relationship import get_relationship_calculator
|
|||||||
from .displaystate import DisplayState, RecentDocsMenu
|
from .displaystate import DisplayState, RecentDocsMenu
|
||||||
from gramps.gen.const import (USER_DATA, ICON, URL_BUGTRACKER, URL_HOMEPAGE,
|
from gramps.gen.const import (USER_DATA, ICON, URL_BUGTRACKER, URL_HOMEPAGE,
|
||||||
URL_MAILINGLIST, URL_MANUAL_PAGE, URL_WIKISTRING,
|
URL_MAILINGLIST, URL_MANUAL_PAGE, URL_WIKISTRING,
|
||||||
WIKI_EXTRAPLUGINS, URL_BUGHOME)
|
WIKI_EXTRAPLUGINS, URL_BUGHOME, DATA_DIR)
|
||||||
from gramps.gen.constfunc import is_quartz
|
from gramps.gen.constfunc import is_quartz
|
||||||
from gramps.gen.config import config
|
from gramps.gen.config import config
|
||||||
from gramps.gen.errors import WindowActiveError
|
from gramps.gen.errors import WindowActiveError
|
||||||
@ -267,6 +267,8 @@ class ViewManager(CLIManager):
|
|||||||
self.window.set_default_size(width, height)
|
self.window.set_default_size(width, height)
|
||||||
self.window.move(horiz_position, vert_position)
|
self.window.move(horiz_position, vert_position)
|
||||||
|
|
||||||
|
self.load_css()
|
||||||
|
|
||||||
self.provider = Gtk.CssProvider()
|
self.provider = Gtk.CssProvider()
|
||||||
self.change_font(font)
|
self.change_font(font)
|
||||||
|
|
||||||
@ -382,6 +384,7 @@ class ViewManager(CLIManager):
|
|||||||
('ExtraPlugins', extra_plugins_activate),
|
('ExtraPlugins', extra_plugins_activate),
|
||||||
#('about', self.display_about_box),
|
#('about', self.display_about_box),
|
||||||
('PluginStatus', self.__plugin_status),
|
('PluginStatus', self.__plugin_status),
|
||||||
|
('AddonManager', self.__addon_manager),
|
||||||
('FAQ', faq_activate),
|
('FAQ', faq_activate),
|
||||||
('KeyBindings', key_bindings),
|
('KeyBindings', key_bindings),
|
||||||
('UserManual', manual_activate, 'F1'),
|
('UserManual', manual_activate, 'F1'),
|
||||||
@ -679,6 +682,13 @@ class ViewManager(CLIManager):
|
|||||||
except WindowActiveError:
|
except WindowActiveError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def load_css(self):
|
||||||
|
provider = Gtk.CssProvider()
|
||||||
|
provider.load_from_path(os.path.join(DATA_DIR, 'gramps.css'))
|
||||||
|
Gtk.StyleContext.add_provider_for_screen(
|
||||||
|
self.window.get_screen(), provider,
|
||||||
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
|
|
||||||
def reset_font(self):
|
def reset_font(self):
|
||||||
"""
|
"""
|
||||||
Reset to the default application font.
|
Reset to the default application font.
|
||||||
@ -722,6 +732,15 @@ class ViewManager(CLIManager):
|
|||||||
except WindowActiveError:
|
except WindowActiveError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def __addon_manager(self, obj=None, data=None):
|
||||||
|
"""
|
||||||
|
Display the addon manager dialog
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
PluginWindows.AddonManager(self.dbstate, self.uistate, [])
|
||||||
|
except WindowActiveError:
|
||||||
|
pass
|
||||||
|
|
||||||
def navigator_toggle(self, action, value):
|
def navigator_toggle(self, action, value):
|
||||||
"""
|
"""
|
||||||
Set the sidebar based on the value of the toggle button. Save the
|
Set the sidebar based on the value of the toggle button. Save the
|
||||||
|
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 831 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
@ -375,6 +375,7 @@ gramps/gen/utils/image.py
|
|||||||
gramps/gen/utils/keyword.py
|
gramps/gen/utils/keyword.py
|
||||||
gramps/gen/utils/lds.py
|
gramps/gen/utils/lds.py
|
||||||
gramps/gen/utils/place.py
|
gramps/gen/utils/place.py
|
||||||
|
gramps/gen/utils/requirements.py
|
||||||
gramps/gen/utils/string.py
|
gramps/gen/utils/string.py
|
||||||
gramps/gen/utils/symbols.py
|
gramps/gen/utils/symbols.py
|
||||||
gramps/gen/utils/unknown.py
|
gramps/gen/utils/unknown.py
|
||||||
|
2
setup.py
@ -348,6 +348,7 @@ GRAMPS_FILES = glob.glob(os.path.join('example', 'gramps', '*.*'))
|
|||||||
IMAGE_WEB = glob.glob(os.path.join('images', 'webstuff', '*.png'))
|
IMAGE_WEB = glob.glob(os.path.join('images', 'webstuff', '*.png'))
|
||||||
IMAGE_WEB.extend(glob.glob(os.path.join('images', 'webstuff','*.ico')))
|
IMAGE_WEB.extend(glob.glob(os.path.join('images', 'webstuff','*.ico')))
|
||||||
IMAGE_WEB.extend(glob.glob(os.path.join('images', 'webstuff', '*.gif')))
|
IMAGE_WEB.extend(glob.glob(os.path.join('images', 'webstuff', '*.gif')))
|
||||||
|
GRAMPS_CSS = glob.glob(os.path.join('data', '*.css'))
|
||||||
CSS_FILES = glob.glob(os.path.join('data', 'css', '*.css'))
|
CSS_FILES = glob.glob(os.path.join('data', 'css', '*.css'))
|
||||||
SWANKY_PURSE = glob.glob(os.path.join('data', 'css', 'swanky-purse', '*.css'))
|
SWANKY_PURSE = glob.glob(os.path.join('data', 'css', 'swanky-purse', '*.css'))
|
||||||
SWANKY_IMG = glob.glob(os.path.join('data', 'css', 'swanky-purse', 'images', '*.png'))
|
SWANKY_IMG = glob.glob(os.path.join('data', 'css', 'swanky-purse', 'images', '*.png'))
|
||||||
@ -355,6 +356,7 @@ data_files_core.append(('share/doc/gramps', DOC_FILES))
|
|||||||
data_files_core.append(('share/doc/gramps/example/gedcom', GEDCOM_FILES))
|
data_files_core.append(('share/doc/gramps/example/gedcom', GEDCOM_FILES))
|
||||||
data_files_core.append(('share/doc/gramps/example/gramps', GRAMPS_FILES))
|
data_files_core.append(('share/doc/gramps/example/gramps', GRAMPS_FILES))
|
||||||
data_files_core.append(('share/gramps/images/webstuff', IMAGE_WEB))
|
data_files_core.append(('share/gramps/images/webstuff', IMAGE_WEB))
|
||||||
|
data_files_core.append(('share/gramps', GRAMPS_CSS))
|
||||||
data_files_core.append(('share/gramps/css', CSS_FILES))
|
data_files_core.append(('share/gramps/css', CSS_FILES))
|
||||||
data_files_core.append(('share/gramps/css/swanky-purse', SWANKY_PURSE))
|
data_files_core.append(('share/gramps/css/swanky-purse', SWANKY_PURSE))
|
||||||
data_files_core.append(('share/gramps/css/swanky-purse/images', SWANKY_IMG))
|
data_files_core.append(('share/gramps/css/swanky-purse/images', SWANKY_IMG))
|
||||||
|