From 545ce0b6ce60be60ce81af39c03f810a70f266d7 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 18 Apr 2020 18:32:06 +0100 Subject: [PATCH 1/3] If bools are bistate, remove the "Clear" button from edit medata single --- src/calibre/gui2/custom_column_widgets.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 5646e57ff9..7d941b92ba 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -164,12 +164,13 @@ class Bool(Base): l.addWidget(c) c.clicked.connect(self.set_to_no) - t = _('Clear') - c = QPushButton(t, parent) - width = c.fontMetrics().boundingRect(t).width() + 7 - c.setMaximumWidth(width) - l.addWidget(c) - c.clicked.connect(self.set_to_cleared) + if self.db.prefs.get('bools_are_tristate'): + t = _('Clear') + c = QPushButton(t, parent) + width = c.fontMetrics().boundingRect(t).width() + 7 + c.setMaximumWidth(width) + l.addWidget(c) + c.clicked.connect(self.set_to_cleared) c = QLabel('', parent) c.setMaximumWidth(1) From 5187d5dc0239b15b190127aca8428759b78536ca Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 18 Apr 2020 18:33:09 +0100 Subject: [PATCH 2/3] Improve performance of rules templates by not calling the "and" function if there is only one condition. --- src/calibre/library/coloring.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index bd3b21f8b7..30240df8e6 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -62,14 +62,22 @@ class Rule(object): # {{{ return None conditions = [x for x in map(self.apply_condition, self.conditions) if x is not None] conditions = (',\n' + ' '*9).join(conditions) - return dedent('''\ - program: - {sig} - test(and( - {conditions} - ), '{color}', ''); - ''').format(sig=self.signature, conditions=conditions, - color=self.color) + if len(self.conditions) > 1: + return dedent('''\ + program: + {sig} + test(and( + {conditions} + ), '{color}', ''); + ''').format(sig=self.signature, conditions=conditions, + color=self.color) + else: + return dedent('''\ + program: + {sig} + test({conditions}, '{color}', ''); + ''').format(sig=self.signature, conditions=conditions, + color=self.color) def apply_condition(self, condition): col, action, val = condition From 2fc75178d894d45e1445800936fbe38408d25e8b Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 18 Apr 2020 18:42:02 +0100 Subject: [PATCH 3/3] Fix for bug #1873106: when bools are not tristate, undefined values don't show emblems/icons as if the value is false. The user must edit and save the rule to see the fix. Changing the tristate preference may require editing the rule to ensure that behavior is correct. Add a new formatter function to test yes/no columns against a set of conditions, improving the performance of rules containing such conditions. --- manual/template_lang.rst | 5 ++++ src/calibre/gui2/preferences/coloring.py | 13 ++++++++- src/calibre/library/coloring.py | 11 +++++--- src/calibre/utils/formatter_functions.py | 34 ++++++++++++++++++++++-- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index d80c33467d..de697ae75d 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -242,6 +242,11 @@ The following functions are available in addition to those described in single-f * ``author_links(val_separator, pair_separator)`` -- returns a string containing a list of authors and that author's link values in the form ``author1 val_separator author1link pair_separator author2 val_separator author2link`` etc. An author is separated from its link value by the ``val_separator`` string with no added spaces. ``author:linkvalue`` pairs are separated by the ``pair_separator`` string argument with no added spaces. It is up to you to choose separator strings that do not occur in author names or links. An author is included even if the author link is empty. * ``author_sorts(val_separator)`` -- returns a string containing a list of author's sort values for the authors of the book. The sort is the one in the author metadata (different from the author_sort in books). The returned list has the form author sort 1 ``val_separator`` author sort 2 etc. The author sort values in this list are in the same order as the authors of the book. If you want spaces around ``val_separator`` then include them in the separator string * ``booksize()`` -- returns the value of the calibre 'size' field. Returns '' if there are no formats. + * ``check_yes_no(field_name, is_undefined, is_false, is_true)`` -- checks the value of the yes/no field named by the lookup key ``field_name`` for a value specified by the parameters, returning "yes" if a match is found, otherwise returning an empty string. Set the parameter ``is_undefined``, ``is_false``, or ``is_true`` to 1 (the number) to check that condition, otherwise set it to 0. Example:: + + check_yes_no("#bool", 1, 0, 1) + + returns "yes" if the yes/no field ``"#bool"`` is either undefined (neither True nor False) or True. More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1. This function is usually used by the ``test()`` or ``is_empty()`` functions. * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``current_library_name()`` -- return the last name on the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_name()'}``. * ``current_library_path()`` -- return the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_path()'}``. diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index fc16705ee2..6656542c61 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -38,10 +38,17 @@ icon_rule_kinds = [(_('icon with text'), 'icon'), class ConditionEditor(QWidget): # {{{ ACTION_MAP = { + 'bool2' : ( + (_('is true'), 'is true',), + (_('is false'), 'is not true'), + ), 'bool' : ( (_('is true'), 'is true',), + (_('is not true'), 'is not true'), (_('is false'), 'is false'), - (_('is undefined'), 'is undefined') + (_('is not false'), 'is not false'), + (_('is undefined'), 'is undefined'), + (_('is defined'), 'is defined'), ), 'ondevice' : ( (_('is true'), 'is set',), @@ -200,6 +207,10 @@ class ConditionEditor(QWidget): # {{{ if col: m = self.fm[col] dt = m['datatype'] + if dt == 'bool': + from calibre.gui2.ui import get_gui + if not get_gui().current_db.prefs.get('bools_are_tristate'): + dt = 'bool2' if dt in self.action_map: actions = self.action_map[dt] else: diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index 30240df8e6..e877db474b 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -120,10 +120,13 @@ class Rule(object): # {{{ return "test(ondevice(), '', '1')" def bool_condition(self, col, action, val): - test = {'is true': 'True', - 'is false': 'False', - 'is undefined': 'None'}[action] - return "strcmp('%s', raw_field('%s'), '', '1', '')"%(test, col) + test = {'is true': '0, 0, 1', + 'is not true': '1, 1, 0', + 'is false': '0, 1, 0', + 'is not false': '1, 0, 1', + 'is undefined': '1, 0, 0', + 'is defined': '0, 1, 1'}[action] + return "check_yes_no('%s', %s)"%(col, test) def number_condition(self, col, action, val): lt, eq, gt = { diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index f9add7a6a4..072c332f88 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -1602,12 +1602,42 @@ class BuiltinAuthorSorts(BuiltinFormatterFunction): names = [sort_data.get(n) for n in mi.authors if n.strip()] return val_sep.join(n for n in names) +class BuiltinCheckYesNo(BuiltinFormatterFunction): + name = 'check_yes_no' + arg_count = 4 + category = 'If-then-else' + __doc__ = doc = _('check_yes_no(field_name, is_undefined, is_false, is_true) ' + '-- checks the value of the yes/no field named by the ' + 'lookup key field_name for a value specified by the ' + 'parameters, returning "yes" if a match is found, otherwise ' + 'returning an empty string. Set the parameter is_undefined, ' + 'is_false, or is_true to 1 (the number) to check that ' + 'condition, otherwise set it to 0. Example: ' + 'check_yes_no("#bool", 1, 0, 1) returns "yes" if the ' + 'yes/no field "#bool" is either undefined (neither True ' + 'nor False) or True. More than one of is_undefined, ' + 'is_false, or is_true can be set to 1. This function ' + 'is usually used by the test() or is_empty() functions.') + + def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true): + res = getattr(mi, field, None) + if res is None: + if is_undefined == '1': + return 'yes' + return "" + if not isinstance(res, bool): + raise ValueError(_('check_yes_no requires the field be a Yes/No custom column')) + if is_false == '1' and not res: + return 'yes' + if is_true == '1' and res: + return 'yes' + return "" _formatter_builtins = [ BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinAssign(), BuiltinAuthorLinks(), BuiltinAuthorSorts(), BuiltinBooksize(), - BuiltinCapitalize(), BuiltinCmp(), BuiltinContains(), BuiltinCount(), - BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(), + BuiltinCapitalize(), BuiltinCheckYesNo(), BuiltinCmp(), BuiltinContains(), + BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(), BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFormatDate(), BuiltinFormatNumber(), BuiltinFormatsModtimes(),