diff --git a/Changelog.yaml b/Changelog.yaml
index 79ef8f249c..e46d937411 100644
--- a/Changelog.yaml
+++ b/Changelog.yaml
@@ -5,7 +5,7 @@
# Also, each release can have new and improved recipes.
- version: 0.7.36
- date: 2010-01-01
+ date: 2011-01-01
new features:
- title: "Tag browser: Add subcategories and search"
diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index 9726ed3b09..4ae0278133 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -55,16 +55,9 @@ author_sort_copy_method = 'invert'
# categories_use_field_for_author_name = 'author_sort'
categories_use_field_for_author_name = 'author'
-# Control how the tags pane displays categories containing many items. If the
-# number of items is larger than categories_collapse_more_than, a sub-category
-# will be added. If sorting by name, then the subcategories can be organized by
-# first letter (categories_collapse_model = 'first letter') or into equal-sized
-# groups (categories_collapse_model = 'partition'). If sorting by average rating
-# or by popularity, then 'partition' is always used. The addition of
-# subcategories can be disabled by setting categories_collapse_more_than = 0.
-# When using partition, the format of the subcategory label is controlled by a
-# template: categories_collapsed_name_template if sorting by name,
-# categories_collapsed_rating_template if sorting by average rating, and
+# When partitioning the tags browser, the format of the subcategory label is
+# controlled by a template: categories_collapsed_name_template if sorting by
+# name, categories_collapsed_rating_template if sorting by average rating, and
# categories_collapsed_popularity_template if sorting by popularity. There are
# two variables available to the template: first and last. The variable 'first'
# is the initial item in the subcategory, and the variable 'last' is the final
@@ -76,11 +69,10 @@ categories_use_field_for_author_name = 'author'
# avg_rating: the averate rating of all the books referencing this item
# sort: the sort value. For authors, this is the author_sort for that author
# category: the category (e.g., authors, series) that the item is in.
-categories_collapse_more_than = 50
-categories_collapsed_name_template = '{first.name:shorten(4,'',0)} - {last.name::shorten(4,'',0)}'
+categories_collapsed_name_template = '{first.sort:shorten(4,'',0)} - {last.sort:shorten(4,'',0)}'
categories_collapsed_rating_template = '{first.avg_rating:4.2f:ifempty(0)} - {last.avg_rating:4.2f:ifempty(0)}'
categories_collapsed_popularity_template = '{first.count:d} - {last.count:d}'
-categories_collapse_model = 'first letter'
+
# Set whether boolean custom columns are two- or three-valued.
# Two-values for true booleans
diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py
index 380dcb7440..241d503c14 100644
--- a/src/calibre/devices/scanner.py
+++ b/src/calibre/devices/scanner.py
@@ -30,6 +30,12 @@ class Drive(str):
typ.order = order
return typ
+def drivecmp(a, b):
+ ans = cmp(getattr(a, 'order', 0), getattr(b, 'order', 0))
+ if ans == 0:
+ ans = cmp(a, b)
+ return ans
+
class WinPNPScanner(object):
@@ -57,7 +63,13 @@ class WinPNPScanner(object):
order = 0
match = re.search(r'REV_.*?&(\d+)#', pnp_id)
if match is None:
- match = re.search(r'REV_.*?&(\d+)', pnp_id)
+ # Windows XP
+ # On the Nook Color this is the last digit
+ #
+ # USBSTOR\DISK&VEN_B&N&PROD_EBOOK_DISK&REV_0100\7&13EAFDB8&0&2004760017462009&1
+ # USBSTOR\DISK&VEN_B&N&PROD_EBOOK_DISK&REV_0100\7&13EAFDB8&0&2004760017462009&0
+ #
+ match = re.search(r'REV_.*&(\d+)', pnp_id)
if match is not None:
order = int(match.group(1))
return order
diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py
index 2c095d6f7b..4711a8eec4 100644
--- a/src/calibre/devices/usbms/device.py
+++ b/src/calibre/devices/usbms/device.py
@@ -11,7 +11,7 @@ intended to be subclassed with the relevant parts implemented for a particular
device. This class handles device detection.
'''
-import os, subprocess, time, re, sys, glob, operator
+import os, subprocess, time, re, sys, glob
from itertools import repeat
from calibre.devices.interface import DevicePlugin
@@ -225,7 +225,7 @@ class Device(DeviceConfig, DevicePlugin):
return False
def open_windows(self):
- from calibre.devices.scanner import win_pnp_drives
+ from calibre.devices.scanner import win_pnp_drives, drivecmp
time.sleep(5)
drives = {}
@@ -263,7 +263,7 @@ class Device(DeviceConfig, DevicePlugin):
if self.WINDOWS_MAIN_MEM in (self.WINDOWS_CARD_A_MEM,
self.WINDOWS_CARD_B_MEM) or \
self.WINDOWS_CARD_A_MEM == self.WINDOWS_CARD_B_MEM:
- letters = sorted(drives.values(), key=operator.attrgetter('order'))
+ letters = sorted(drives.values(), cmp=drivecmp)
drives = {}
for which, letter in zip(['main', 'carda', 'cardb'], letters):
drives[which] = letter
diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py
index 4ff10290c9..b0884417f6 100644
--- a/src/calibre/ebooks/pdf/writer.py
+++ b/src/calibre/ebooks/pdf/writer.py
@@ -175,7 +175,7 @@ class PDFWriter(QObject): # {{{
if self.cover_data is None:
return
item_path = os.path.join(self.tmp_path, 'cover.pdf')
- printer = self.get_printer()
+ printer = get_pdf_printer(self.opts)
printer.setOutputFileName(item_path)
self.combine_queue.insert(0, item_path)
p = QPixmap()
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 1c99d9d9d5..8c62304f09 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -53,6 +53,8 @@ gprefs.defaults['toolbar_icon_size'] = 'medium'
gprefs.defaults['toolbar_text'] = 'auto'
gprefs.defaults['show_child_bar'] = False
gprefs.defaults['font'] = None
+gprefs.defaults['tags_browser_partition_method'] = 'first letter'
+gprefs.defaults['tags_browser_collapse_at'] = 50
# }}}
diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py
index eac8461299..4da897920c 100644
--- a/src/calibre/gui2/dialogs/book_info.py
+++ b/src/calibre/gui2/dialogs/book_info.py
@@ -23,7 +23,7 @@ class BookInfo(QDialog, Ui_BookInfo):
self.cover_pixmap = None
self.comments.sizeHint = self.comments_size_hint
self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks)
- self.comments.linkClicked(self.link_clicked)
+ self.comments.linkClicked.connect(self.link_clicked)
self.view_func = view_func
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 322199a4f9..c1dd5b3766 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -386,7 +386,12 @@ class BooksView(QTableView): # {{{
old_state = self.get_default_state()
if tweaks['sort_columns_at_startup'] is not None:
- old_state['sort_history'] = tweaks['sort_columns_at_startup']
+ sh = []
+ for c,d in tweaks['sort_columns_at_startup']:
+ if not isinstance(d, bool):
+ d = True if d == 0 else False
+ sh.append((c, d))
+ old_state['sort_history'] = sh
self.apply_state(old_state)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 371c4df701..c8b5cb001e 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -303,7 +303,7 @@ def run_gui(opts, args, actions, listener, app, gui_debug=None):
runner.main.system_tray_icon.hide()
except:
pass
- if runner.main.gui_debug is not None:
+ if getattr(runner.main, 'gui_debug', None) is not None:
e = sys.executable if getattr(sys, 'frozen', False) else sys.argv[0]
import subprocess
creationflags = 0
diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py
index de1116c231..263d19325d 100644
--- a/src/calibre/gui2/preferences/look_feel.py
+++ b/src/calibre/gui2/preferences/look_feel.py
@@ -57,6 +57,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
(_('Never'), 'never')]
r('toolbar_text', gprefs, choices=choices)
+ choices = [(_('Disabled'), 'disabled'), (_('By first letter'), 'first letter'),
+ (_('Partitioned'), 'partition')]
+ r('tags_browser_partition_method', gprefs, choices=choices)
+ r('tags_browser_collapse_at', gprefs)
+
self.current_font = None
self.change_font_button.clicked.connect(self.change_font)
@@ -113,6 +118,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def refresh_gui(self, gui):
gui.search.search_as_you_type(config['search_as_you_type'])
self.update_font_display()
+ gui.tags_view.reread_collapse_parameters()
if __name__ == '__main__':
app = QApplication([])
diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui
index 8e57f8c17e..36a45b8dce 100644
--- a/src/calibre/gui2/preferences/look_feel.ui
+++ b/src/calibre/gui2/preferences/look_feel.ui
@@ -7,7 +7,7 @@
0
0
670
- 385
+ 420
@@ -141,7 +141,37 @@
- -
+
-
+
+
+ Tags browser: partitioning method:
+
+
+ opt_tags_browser_partition_method
+
+
+
+ -
+
+
+ -
+
+
+ Tags browser - collapse when more items than:
+
+
+ opt_tags_browser_collapse_at
+
+
+
+ -
+
+
+ 1000000
+
+
+
+ -
&Toolbar
@@ -183,7 +213,7 @@
- -
+
-
-
@@ -204,14 +234,14 @@
- -
+
-
Change &font (needs restart)
- -
+
-
Qt::Vertical
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index f04902283e..8d42e51dfc 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -17,7 +17,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, \
QShortcut, QKeySequence, SIGNAL
from calibre.ebooks.metadata import title_sort
-from calibre.gui2 import config, NONE
+from calibre.gui2 import config, NONE, gprefs
from calibre.library.field_metadata import TagsIcons, category_icon_map
from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key, upper, lower, strcmp
@@ -94,6 +94,10 @@ class TagsView(QTreeView): # {{{
self.setDropIndicatorShown(True)
self.setAutoExpandDelay(500)
self.pane_is_visible = False
+ if gprefs['tags_browser_collapse_at'] == 0:
+ self.collapse_model = 'disable'
+ else:
+ self.collapse_model = gprefs['tags_browser_partition_method']
def set_pane_is_visible(self, to_what):
pv = self.pane_is_visible
@@ -101,12 +105,20 @@ class TagsView(QTreeView): # {{{
if to_what and not pv:
self.recount()
+ def reread_collapse_parameters(self):
+ if gprefs['tags_browser_collapse_at'] == 0:
+ self.collapse_model = 'disable'
+ else:
+ self.collapse_model = gprefs['tags_browser_partition_method']
+ self.set_new_model(self._model.get_filter_categories_by())
+
def set_database(self, db, tag_match, sort_by):
self.hidden_categories = config['tag_browser_hidden_categories']
self._model = TagsModel(db, parent=self,
hidden_categories=self.hidden_categories,
search_restriction=None,
- drag_drop_finished=self.drag_drop_finished)
+ drag_drop_finished=self.drag_drop_finished,
+ collapse_model=self.collapse_model)
self.pane_is_visible = True # because TagsModel.init did a recount
self.sort_by = sort_by
self.tag_match = tag_match
@@ -194,6 +206,12 @@ class TagsView(QTreeView): # {{{
self.hidden_categories.add(category)
elif action == 'show':
self.hidden_categories.discard(category)
+ elif action == 'categorization':
+ changed = self.collapse_model != category
+ self.collapse_model = category
+ if changed:
+ self.set_new_model(self._model.get_filter_categories_by())
+ gprefs['tags_browser_partition_method'] = category
elif action == 'defaults':
self.hidden_categories.clear()
config.set('tag_browser_hidden_categories', self.hidden_categories)
@@ -216,6 +234,8 @@ class TagsView(QTreeView): # {{{
item = item.parent
if item.type == TagTreeItem.CATEGORY:
+ while item.parent != self._model.root_item:
+ item = item.parent
category = unicode(item.name.toString())
key = item.category_key
# Verify that we are working with a field that we know something about
@@ -277,6 +297,23 @@ class TagsView(QTreeView): # {{{
self.context_menu.addAction(_('Show all categories'),
partial(self.context_menu_handler, action='defaults'))
+ m = self.context_menu.addMenu(_('Change sub-categorization scheme'))
+ da = m.addAction('Disable',
+ partial(self.context_menu_handler, action='categorization', category='disable'))
+ fla = m.addAction('By first letter',
+ partial(self.context_menu_handler, action='categorization', category='first letter'))
+ pa = m.addAction('Partition',
+ partial(self.context_menu_handler, action='categorization', category='partition'))
+ if self.collapse_model == 'disable':
+ da.setCheckable(True)
+ da.setChecked(True)
+ elif self.collapse_model == 'first letter':
+ fla.setCheckable(True)
+ fla.setChecked(True)
+ else:
+ pa.setCheckable(True)
+ pa.setChecked(True)
+
if not self.context_menu.isEmpty():
self.context_menu.popup(self.mapToGlobal(point))
return True
@@ -338,7 +375,8 @@ class TagsView(QTreeView): # {{{
hidden_categories=self.hidden_categories,
search_restriction=self.search_restriction,
drag_drop_finished=self.drag_drop_finished,
- filter_categories_by=filter_categories_by)
+ filter_categories_by=filter_categories_by,
+ collapse_model=self.collapse_model)
self.setModel(self._model)
except:
# The DB must be gone. Set the model to None and hope that someone
@@ -467,7 +505,7 @@ class TagsModel(QAbstractItemModel): # {{{
def __init__(self, db, parent, hidden_categories=None,
search_restriction=None, drag_drop_finished=None,
- filter_categories_by=None):
+ filter_categories_by=None, collapse_model='disable'):
QAbstractItemModel.__init__(self, parent)
# must do this here because 'QPixmap: Must construct a QApplication
@@ -488,6 +526,7 @@ class TagsModel(QAbstractItemModel): # {{{
self.search_restriction = search_restriction
self.row_map = []
self.filter_categories_by = filter_categories_by
+ self.collapse_model = collapse_model
# get_node_tree cannot return None here, because row_map is empty
data = self.get_node_tree(config['sort_tags_by'])
@@ -678,16 +717,19 @@ class TagsModel(QAbstractItemModel): # {{{
if data is None:
return False
row_index = -1
- collapse = tweaks['categories_collapse_more_than']
- collapse_model = tweaks['categories_collapse_model']
- if sort_by == 'name':
- collapse_template = tweaks['categories_collapsed_name_template']
- elif sort_by == 'rating':
- collapse_model = 'partition'
- collapse_template = tweaks['categories_collapsed_rating_template']
- else:
- collapse_model = 'partition'
- collapse_template = tweaks['categories_collapsed_popularity_template']
+ collapse = gprefs['tags_browser_collapse_at']
+ collapse_model = self.collapse_model
+ if collapse == 0:
+ collapse_model = 'disable'
+ elif collapse_model != 'disable':
+ if sort_by == 'name':
+ collapse_template = tweaks['categories_collapsed_name_template']
+ elif sort_by == 'rating':
+ collapse_model = 'partition'
+ collapse_template = tweaks['categories_collapsed_rating_template']
+ else:
+ collapse_model = 'partition'
+ collapse_template = tweaks['categories_collapsed_popularity_template']
collapse_letter = None
for i, r in enumerate(self.row_map):
@@ -722,7 +764,7 @@ class TagsModel(QAbstractItemModel): # {{{
tag.avg_rating = None
tag.state = state_map.get(tag.name, 0)
- if collapse > 0 and cat_len > collapse:
+ if collapse_model != 'disable' and cat_len > collapse:
if collapse_model == 'partition':
if (idx % collapse) == 0:
d = {'first': tag}
@@ -738,7 +780,7 @@ class TagsModel(QAbstractItemModel): # {{{
category_key=category_node.category_key)
else:
if upper(tag.sort[0]) != collapse_letter:
- collapse_letter = upper(tag.name[0])
+ collapse_letter = upper(tag.sort[0])
sub_cat = TagTreeItem(parent=category,
data = collapse_letter,
category_icon = category_node.icon,
diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py
index c91c6bca88..23b1ee9fdd 100644
--- a/src/calibre/library/catalog.py
+++ b/src/calibre/library/catalog.py
@@ -1930,7 +1930,6 @@ class EPUB_MOBI(CatalogPlugin):
# Loop through booksByAuthor
book_count = 0
current_author = ''
- previous_author = ''
current_letter = ''
current_series = None
for book in self.booksByAuthor: