diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 7be3dc9a66..385d444032 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -574,3 +574,17 @@ allow_template_database_functions_in_composites = False # for https://whatever URLs. %u is replaced by the URL to be opened. The scheme # takes a glob pattern allowing a single entry to match multiple URL types. openers_by_scheme = {} + +#: Change standard column heading text to some value +# Use the dictionary below to change a column heading. The format of the +# dictionary is +# {lookup_name: new_heading, ...} +# The new_heading must be unique: no two columns can have the same heading. +# This tweak works only with standard columns: it cannot be used to change +# the heading for a custom column. If a custom column has the same heading as +# one provided here then a number will appended to the custom column's heading +# to make it unique. +# +# Example: +# alternate_column_headings = {'authors':'Writers', 'size':'MBytes'} +alternate_column_headings = {} diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 597c29f29e..99c195c603 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -38,7 +38,7 @@ from calibre.utils.date import ( UNDEFINED_DATE, as_local_time, dt_factory, is_date_undefined, qt_to_dt, ) from calibre.utils.icu import sort_key -from calibre.utils.localization import calibre_langcode_to_name, ngettext +from calibre.utils.localization import calibre_langcode_to_name from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import iteritems, itervalues, string_or_bytes @@ -192,20 +192,12 @@ class BooksModel(QAbstractTableModel): # {{{ self.bi_font = QFont(self.bold_font) self.bi_font.setItalic(True) self.styled_columns = {} - self.orig_headers = { - 'title' : _("Title"), - 'ondevice' : _("On Device"), - 'authors' : _("Author(s)"), - 'size' : _("Size (MB)"), - 'timestamp' : _("Date"), - 'pubdate' : _('Published'), - 'rating' : _('Rating'), - 'publisher' : _("Publisher"), - 'tags' : _("Tags"), - 'series' : ngettext("Series", 'Series', 1), - 'last_modified' : _('Modified'), - 'languages' : _('Languages'), - } + possible_columns = ('title', 'ondevice', 'authors', 'size', 'timestamp', + 'pubdate', 'rating', 'publisher', 'tags', 'series', + 'last_modified', 'languages', 'formats', 'id', 'path') + from calibre.library.field_metadata import FieldMetadata + fm = FieldMetadata() + self.orig_headers = {k: fm[k]['name'] for k in possible_columns} self.db = None @@ -829,6 +821,10 @@ class BooksModel(QAbstractTableModel): # {{{ def renderer(field, decorator=False): idfunc = self.db.id + if field == 'id': + def func(idx): + return idfunc(idx) + return func fffunc = self.db.new_api.fast_field_for field_obj = self.db.new_api.fields[field] m = field_obj.metadata.copy() @@ -953,7 +949,7 @@ class BooksModel(QAbstractTableModel): # {{{ return func - self.dc = {f:renderer(f) for f in 'title authors size timestamp pubdate last_modified rating publisher tags series ondevice languages'.split()} + self.dc = {f:renderer(f) for f in self.orig_headers.keys()} self.dc_decorator = {f:renderer(f, True) for f in ('ondevice',)} for col in self.custom_columns: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e3b4b5dad6..3cf9f90ac9 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -808,8 +808,8 @@ class BooksView(QTableView): # {{{ state = {} state['hidden_columns'] = [cm[i] for i in range(h.count()) if h.isSectionHidden(i) and cm[i] != 'ondevice'] - state['last_modified_injected'] = True - state['languages_injected'] = True + for f in ('last_modified', 'languages', 'formats', 'id', 'path'): + state[f+'_injected'] = True state['sort_history'] = \ self.cleanup_sort_history(self.model().sort_history, ignore_column_map=self.is_library_view) state['column_positions'] = {} @@ -923,7 +923,7 @@ class BooksView(QTableView): # {{{ def get_default_state(self): old_state = { - 'hidden_columns': ['last_modified', 'languages'], + 'hidden_columns': ['last_modified', 'languages', 'formats', 'id', 'path'], 'sort_history':[DEFAULT_SORT], 'column_positions': {}, 'column_sizes': {}, @@ -931,9 +931,9 @@ class BooksView(QTableView): # {{{ 'size':'center', 'timestamp':'center', 'pubdate':'center'}, - 'last_modified_injected': True, - 'languages_injected': True, } + for f in ('last_modified', 'languages', 'formats', 'id', 'path'): + old_state[f+'_injected'] = True h = self.column_header cm = self.column_map for i in range(h.count()): @@ -965,18 +965,14 @@ class BooksView(QTableView): # {{{ db.new_api.set_pref(name, ans) else: injected = False - if not ans.get('last_modified_injected', False): - injected = True - ans['last_modified_injected'] = True - hc = ans.get('hidden_columns', []) - if 'last_modified' not in hc: - hc.append('last_modified') - if not ans.get('languages_injected', False): - injected = True - ans['languages_injected'] = True - hc = ans.get('hidden_columns', []) - if 'languages' not in hc: - hc.append('languages') + for f in ('last_modified', 'languages', 'formats', 'id', 'path'): + if not ans.get(f+'_injected', False): + print('injecting', f) + injected = True + ans[f+'_injected'] = True + hc = ans.get('hidden_columns', []) + if f not in hc: + hc.append(f) if injected: db.new_api.set_pref(name, ans) return ans diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 20dfd10bd5..ee6eef051c 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -5,6 +5,7 @@ Created on 25 May 2010 ''' import traceback +import sys from collections import OrderedDict from calibre.utils.config_base import tweaks @@ -197,7 +198,7 @@ def _builtin_field_metadata(): 'datatype':'int', 'is_multiple':{}, 'kind':'field', - 'name':None, + 'name': _('Id'), 'search_terms':['id'], 'is_custom':False, 'is_category':False, @@ -411,6 +412,15 @@ class FieldMetadata: self._tb_cats[k]['display'] = {} self._tb_cats[k]['is_editable'] = True self._add_search_terms_to_map(k, v['search_terms']) + alternate_headings = tweaks.get('alternate_column_headings', {}) + if alternate_headings: + existing_headings = {k['name'] for k in self._tb_cats.values() if k['name']} + for k,v in alternate_headings.items(): + if k in self._tb_cats.keys(): + v = self.get_unique_field_heading(v) + existing_headings.discard(self._tb_cats[k]['name']) + existing_headings.add(v) + self._tb_cats[k]['name'] = v self._tb_cats['timestamp']['display'] = { 'date_format': tweaks['gui_timestamp_display_format']} self._tb_cats['pubdate']['display'] = { @@ -454,6 +464,10 @@ class FieldMetadata: def __ne__(self, other): return not self.__eq__(other) + def set_field_heading(self, key, heading): + if key in self._tb_cats.keys(): + self._tb_cats[key]['name'] = heading + def sortable_field_keys(self): return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field' and @@ -560,6 +574,18 @@ class FieldMetadata: l[k] = self._tb_cats[k] return l + def get_unique_field_heading(self, name): + # Verify column heading is unique. Can only happen if the tweak is set + if tweaks.get('alternate_column_headings', {}): + existing_names = {icu_lower(c['name']) for c in self._tb_cats.values() if c['name'] is not None} + t = icu_lower(name) + if t in existing_names: + for i in range(1, sys.maxsize): + if (t + '_' + str(i)) not in existing_names: + name = name + '_' + str(i) + break + return name + def add_custom_field(self, label, table, column, datatype, colnum, name, display, is_editable, is_multiple, is_category, is_csp=False): @@ -568,6 +594,7 @@ class FieldMetadata: raise ValueError('Duplicate custom field [%s]'%(label)) if datatype not in self.VALID_DATA_TYPES: raise ValueError('Unknown datatype %s for field %s'%(datatype, key)) + name = self.get_unique_field_heading(name) self._tb_cats[key] = {'table':table, 'column':column, 'datatype':datatype, 'is_multiple':is_multiple, 'kind':'field', 'name':name,