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: