From 47b0c65c501f8c936d234e7c866a10816da588b3 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 14 Oct 2020 15:04:36 +0100 Subject: [PATCH] Add option tag browser to combine 'first letters' together if there are fewer items under the letter than X. --- src/calibre/gui2/__init__.py | 1 + src/calibre/gui2/preferences/look_feel.py | 1 + src/calibre/gui2/preferences/look_feel.ui | 33 +++++++++++-- src/calibre/gui2/tag_browser/model.py | 58 ++++++++++++++++++++++- 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 9f7252694e..cf95ab8a9f 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -134,6 +134,7 @@ def create_defs(): defs['font'] = None defs['tags_browser_partition_method'] = 'first letter' defs['tags_browser_collapse_at'] = 100 + defs['tags_browser_collapse_fl_at'] = 0 defs['tag_browser_dont_collapse'] = [] defs['edit_metadata_single_layout'] = 'default' defs['preserve_date_on_ctl'] = True diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 0eafe6e948..5445ca38f1 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -487,6 +487,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): (_('Partitioned'), 'partition')] r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_collapse_at', gprefs) + r('tags_browser_collapse_fl_at', gprefs) r('tag_browser_dont_collapse', gprefs, setting=CommaSeparatedList) choices = {k for k in db.field_metadata.all_field_keys() diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index f4d5b4141a..562157873c 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -914,7 +914,32 @@ up into subcategories. If the partition method is set to disable, this value is - + + + + Combine letters &when fewer items than: + + + opt_tags_browser_collapse_fl_at + + + + + + + If collapsing by first letter, combine adjacent letters together if +there are fewer items under a letter than specified here. If the partition method is +not set to first letter, this value is ignored. + + 1 + + + + 10000 + + + + Categories &not to partition: @@ -924,7 +949,7 @@ up into subcategories. If the partition method is set to disable, this value is - + @@ -1044,7 +1069,7 @@ see the counts by hovering your mouse over any item. - + Spacing between &items: @@ -1054,7 +1079,7 @@ see the counts by hovering your mouse over any item. - + The spacing between consecutive items in the Tag browser. In units of (ex) which is the approximate height of the letter 'x' in the currently used font. diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 86d3aa3217..b7606e8339 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -7,7 +7,7 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' import traceback, copy, os -from collections import OrderedDict +from collections import OrderedDict, namedtuple from PyQt5.Qt import (QAbstractItemModel, QIcon, QFont, Qt, QMimeData, QModelIndex, pyqtSignal, QObject) @@ -290,6 +290,8 @@ class TagTreeItem(object): # {{{ # }}} +FL_Interval = namedtuple('FL_Interval', ['first_chr', 'last_chr', 'length']) + class TagsModel(QAbstractItemModel): # {{{ search_item_renamed = pyqtSignal() @@ -525,18 +527,72 @@ class TagsModel(QAbstractItemModel): # {{{ # Build a list of 'equal' first letters by noticing changes # in ICU's 'ordinal' for the first letter. In this case, the # first letter can actually be more than one letter long. + fl_collapse_when = self.prefs['tags_browser_collapse_fl_at'] + fl_collapse = True if fl_collapse_when > 1 else False + intervals = list() cl_list = [None] * len(data[key]) last_ordnum = 0 last_c = ' ' + last_idx = 0 for idx,tag in enumerate(data[key]): # Deal with items that don't have sorts, such as formats t = tag.sort if tag.sort else tag.name c = icu_upper(t) if t else ' ' ordnum, ordlen = collation_order(c) if last_ordnum != ordnum: + if fl_collapse and idx > 0: + intervals.append(FL_Interval(last_c, last_c, idx-last_idx)) + last_idx = idx last_c = c[0:ordlen] last_ordnum = ordnum cl_list[idx] = last_c + if fl_collapse: + intervals.append(FL_Interval(last_c, last_c, len(cl_list)-last_idx)) + # Combine together first letter categories that are smaller + # than the specified option. We choose which item to combine + # by the size of the items before and after, privileging making + # smaller categories. Loop through the intervals doing the combine + # until nothing changes. Multiple iterations are required because + # we might need to combine categories that are already combined. + fl_intervals_changed = True + null_interval = FL_Interval('', '', 100000000) + while fl_intervals_changed and len(intervals) > 1: + fl_intervals_changed = False + for idx,interval in enumerate(intervals): + if interval.length >= fl_collapse_when: + continue + prev = next_ = null_interval + if idx == 0: + next_ = intervals[idx+1] + else: + prev = intervals[idx-1] + if idx < len(intervals) - 1: + next_ = intervals[idx+1] + if prev.length < next_.length: + intervals[idx-1] = FL_Interval(prev.first_chr, + interval.last_chr, + prev.length + interval.length) + else: + intervals[idx+1] = FL_Interval(interval.first_chr, + next_.last_chr, + next_.length + interval.length) + del intervals[idx] + fl_intervals_changed = True + break + # Now correct the first letter list, entering either the letter + # or the range for each item in the category. If we ended up + # with only one 'first letter' category then don't combine + # letters and revert to basic 'by first letter' + if len(intervals) > 1: + cur_idx = 0 + for interval in intervals: + first_chr, last_chr, length = interval + for i in range(0, length): + if first_chr == last_chr: + cl_list[cur_idx] = first_chr + else: + cl_list[cur_idx] = '{0} - {1}'.format(first_chr, last_chr) + cur_idx += 1 top_level_component = 'z' + data[key][0].original_name last_idx = -collapse