From eccb70cede67d608164b51f9393958bea79fcfc2 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 22 Oct 2010 10:00:24 +0100 Subject: [PATCH 1/6] Fix bulk edit of bool columns when the tristate tweak is set to no --- src/calibre/gui2/custom_column_widgets.py | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 3be4c19d17..3103d7c459 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -429,7 +429,38 @@ class BulkBase(Base): self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) class BulkBool(BulkBase, Bool): - pass + + def get_initial_value(self, book_ids): + value = None + for book_id in book_ids: + val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) + if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: + val = False + if value is not None and value != val: + return None + value = val + return value + + def setup_ui(self, parent): + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), + QComboBox(parent)] + w = self.widgets[1] + items = [_('Yes'), _('No'), _('Undefined')] + icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] + for icon, text in zip(icons, items): + w.addItem(QIcon(icon), text) + + def setter(self, val): + val = {None: 2, False: 1, True: 0}[val] + self.widgets[1].setCurrentIndex(val) + + def commit(self, book_ids, notify=False): + val = self.gui_val + val = self.normalize_ui_val(val) + if val != self.initial_val: + if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: + val = False + self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) class BulkInt(BulkBase, Int): pass From 8ad31bbf087b7503e94cd5b142a2c8af514ca9d7 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 22 Oct 2010 12:31:56 +0100 Subject: [PATCH 2/6] Change bulk edit and the template function titlecase to use utils.titlecase instead of str.title() --- src/calibre/gui2/dialogs/metadata_bulk.py | 7 ++++--- src/calibre/utils/formatter.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index e27f4b5eab..de62f20de0 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -16,6 +16,7 @@ from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2 import error_dialog from calibre.gui2.progress_indicator import ProgressIndicator from calibre.utils.config import dynamic +from calibre.utils.titlecase import titlecase class MyBlockingBusy(QDialog): @@ -115,7 +116,7 @@ class MyBlockingBusy(QDialog): aum = [a.strip().replace('|', ',') for a in aum.split(',')] new_title = authors_to_string(aum) if do_title_case: - new_title = new_title.title() + new_title = titlecase(new_title) self.db.set_title(id, new_title, notify=False) title_set = True if title: @@ -123,7 +124,7 @@ class MyBlockingBusy(QDialog): self.db.set_authors(id, new_authors, notify=False) if do_title_case and not title_set: title = self.db.title(id, index_is_id=True) - self.db.set_title(id, title.title(), notify=False) + self.db.set_title(id, titlecase(title), notify=False) if au: self.db.set_authors(id, string_to_authors(au), notify=False) elif self.current_phase == 2: @@ -179,7 +180,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): s_r_functions = { '' : lambda x: x, _('Lower Case') : lambda x: x.lower(), _('Upper Case') : lambda x: x.upper(), - _('Title Case') : lambda x: x.title(), + _('Title Case') : lambda x: titlecase(x), } s_r_match_modes = [ _('Character match'), diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index 76c086cc58..336ac2390b 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -7,6 +7,7 @@ Created on 23 Sep 2010 import re, string, traceback from calibre.constants import DEBUG +from calibre.utils.titlecase import titlecase class TemplateFormatter(string.Formatter): ''' @@ -81,7 +82,7 @@ class TemplateFormatter(string.Formatter): functions = { 'uppercase' : (0, lambda s,x: x.upper()), 'lowercase' : (0, lambda s,x: x.lower()), - 'titlecase' : (0, lambda s,x: x.title()), + 'titlecase' : (0, lambda s,x: titlecase(x)), 'capitalize' : (0, lambda s,x: x.capitalize()), 'contains' : (3, _contains), 'ifempty' : (1, _ifempty), From abd8ca4cb9a179e71f7512175e88b0decea027d9 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 22 Oct 2010 13:55:17 +0100 Subject: [PATCH 3/6] Add the possibility of sorting collections by an arbitrary metadata field --- resources/default_tweaks.py | 18 ++++++++++++++ src/calibre/devices/usbms/books.py | 40 ++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 86921886ad..dbb1154172 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -137,6 +137,24 @@ auto_connect_to_folder = '' sony_collection_renaming_rules={} +# Specify how sony collections are sorted. This tweak is only applicable if +# metadata management is set to automatic. You can indicate which metadata is to +# be used to sort on a collection-by-collection basis. The format of the tweak +# is a list of metadata fields from which collections are made, followed by the +# name of the metadata field containing the sort value. +# Example: The following indicates that collections built from pubdate and tags +# are to be sorted by the value in the custom column '#mydate', that collections +# built from 'series' are to be sorted by 'series_index', and that all other +# collections are to be sorted by title. If a collection metadata field is not +# named, then if it is a series- based collection it is sorted by series order, +# otherwise it is sorted by title order. +# [(['pubdate', 'tags'],'#mydate'), (['series'],'series_index'), (['*'], 'title')] +# Note that the bracketing and parentheses are required. The syntax is +# [ ( [list of fields], sort field ) , ( [ list of fields ] , sort field ) ] +# Default: empty (no rules), so no collection attributes are named. +sony_collection_sorting_rules = [] + + # Create search terms to apply a query across several built-in search terms. # Syntax: {'new term':['existing term 1', 'term 2', ...], 'new':['old'...] ...} # Example: create the term 'myseries' that when used as myseries:foo would diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 462d78b233..f54bfbddf9 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -99,6 +99,15 @@ class CollectionsBookList(BookList): def supports_collections(self): return True + def in_category_sort_rules(self, attr): + sorts = tweaks['sony_collection_sorting_rules'] + for attrs,sortattr in sorts: + if attr in attrs or '*' in attrs: + print 'in category sort:', attr, sortattr + return sortattr + print 'in category sort:', attr, 'None' + return None + def compute_category_name(self, attr, category, field_meta): renames = tweaks['sony_collection_renaming_rules'] attr_name = renames.get(attr, None) @@ -116,6 +125,7 @@ class CollectionsBookList(BookList): from calibre.devices.usbms.driver import debug_print debug_print('Starting get_collections:', prefs['manage_device_metadata']) debug_print('Renaming rules:', tweaks['sony_collection_renaming_rules']) + debug_print('Sorting rules:', tweaks['sony_collection_sorting_rules']) # Complexity: we can use renaming rules only when using automatic # management. Otherwise we don't always have the metadata to make the @@ -171,6 +181,7 @@ class CollectionsBookList(BookList): else: val = [val] + sort_attr = self.in_category_sort_rules(attr) for category in val: is_series = False if doing_dc: @@ -199,22 +210,41 @@ class CollectionsBookList(BookList): if cat_name not in collections: collections[cat_name] = {} - if is_series: + if use_renaming_rules and sort_attr: + sort_val = book.get(sort_attr, None) + collections[cat_name][lpath] = \ + (book, sort_val, book.get('title_sort', 'zzzz')) + elif is_series: if doing_dc: collections[cat_name][lpath] = \ - (book, book.get('series_index', sys.maxint)) + (book, book.get('series_index', sys.maxint), '') else: collections[cat_name][lpath] = \ - (book, book.get(attr+'_index', sys.maxint)) + (book, book.get(attr+'_index', sys.maxint), '') else: if lpath not in collections[cat_name]: collections[cat_name][lpath] = \ - (book, book.get('title_sort', 'zzzz')) + (book, book.get('title_sort', 'zzzz'), '') # Sort collections result = {} + + def none_cmp(xx, yy): + x = xx[1] + y = yy[1] + if x is None and y is None: + return cmp(xx[2], yy[2]) + if x is None: + return 1 + if y is None: + return -1 + c = cmp(x, y) + if c != 0: + return c + return cmp(xx[2], yy[2]) + for category, lpaths in collections.items(): books = lpaths.values() - books.sort(cmp=lambda x,y:cmp(x[1], y[1])) + books.sort(cmp=none_cmp) result[category] = [x[0] for x in books] return result From e568a55d5944c483751baa65b11c73c12e244aa6 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 22 Oct 2010 13:58:19 +0100 Subject: [PATCH 4/6] Improve sony_collection_renaming_rules documentation. --- resources/default_tweaks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index dbb1154172..270b7e0b06 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -106,7 +106,8 @@ title_sort_articles=r'^(A|The|An)\s+' auto_connect_to_folder = '' -# Specify renaming rules for sony collections. Collections on Sonys are named +# Specify renaming rules for sony collections. This tweak is only applicable if +# metadata management is set to automatic. Collections on Sonys are named # depending upon whether the field is standard or custom. A collection derived # from a standard field is named for the value in that field. For example, if # the standard 'series' column contains the name 'Darkover', then the series From 122fb530873c8559fe5434b3479f935473f63458 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 22 Oct 2010 14:01:11 +0100 Subject: [PATCH 5/6] Remove print statements --- src/calibre/devices/usbms/books.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index f54bfbddf9..5063daa29f 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -103,9 +103,7 @@ class CollectionsBookList(BookList): sorts = tweaks['sony_collection_sorting_rules'] for attrs,sortattr in sorts: if attr in attrs or '*' in attrs: - print 'in category sort:', attr, sortattr return sortattr - print 'in category sort:', attr, 'None' return None def compute_category_name(self, attr, category, field_meta): From fddf3abf92b84eabc0a11b9bceda826e0a24be37 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 22 Oct 2010 18:20:26 +0100 Subject: [PATCH 6/6] Fix broken autonumbering for standard series columns --- src/calibre/gui2/dialogs/metadata_bulk.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index de62f20de0..32350c36b7 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -51,6 +51,7 @@ class MyBlockingBusy(QDialog): self.start() self.args = args + self.series_start_value = None self.db = db self.ids = ids self.error = None @@ -148,8 +149,10 @@ class MyBlockingBusy(QDialog): if do_series: if do_series_restart: - next = series_start_value - series_start_value += 1 + if self.series_start_value is None: + self.series_start_value = series_start_value + next = self.series_start_value + self.series_start_value += 1 else: next = self.db.get_next_series_num_for(series) self.db.set_series(id, series, notify=False, commit=False)