From 73a8f519fa05865dc1bbdcb2efd171c021a082a6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 May 2013 17:45:54 +0530 Subject: [PATCH 01/11] add clear/touch keyb shortcuts to custom column date edit widgets as well --- src/calibre/gui2/custom_column_widgets.py | 29 ++++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 3597b0fb19..51e02b2cde 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -7,10 +7,10 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \ - QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, \ - QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ - QPushButton, QMessageBox, QToolButton +from PyQt4.Qt import (QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, + QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, + QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, + QPushButton, QMessageBox, QToolButton, Qt) from calibre.utils.date import qt_to_dt, now from calibre.gui2.complete2 import EditWithComplete @@ -39,7 +39,6 @@ class Base(object): def gui_val(self): return self.getter() - def commit(self, book_id, notify=False): val = self.gui_val val = self.normalize_ui_val(val) @@ -159,6 +158,17 @@ class DateTimeEdit(QDateTimeEdit): def set_to_clear(self): self.setDateTime(UNDEFINED_QDATETIME) + def keyPressEvent(self, ev): + if ev.key() == Qt.Key_Minus: + ev.accept() + self.setDateTime(self.minimumDateTime()) + elif ev.key() == Qt.Key_Equal: + ev.accept() + self.setDateTime(QDateTime.currentDateTime()) + else: + return QDateTimeEdit.keyPressEvent(self, ev) + + class DateTime(Base): def setup_ui(self, parent): @@ -211,7 +221,7 @@ class Comments(Base): self._layout = QVBoxLayout() self._tb = CommentsEditor(self._box) self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) - #self._tb.setTabChangesFocus(True) + # self._tb.setTabChangesFocus(True) self._layout.addWidget(self._tb) self._box.setLayout(self._layout) self.widgets = [self._box] @@ -534,7 +544,7 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa column = row = base_row = max_row = 0 for key in cols: if not fm[key]['is_editable']: - continue # this almost never happens + continue # this almost never happens dt = fm[key]['datatype'] if dt == 'composite' or (bulk and dt == 'comments'): continue @@ -595,7 +605,6 @@ class BulkBase(Base): self._cached_gui_val_ = self.getter() return self._cached_gui_val_ - def get_initial_value(self, book_ids): values = set([]) for book_id in book_ids: @@ -633,7 +642,7 @@ class BulkBase(Base): self.main_widget = main_widget_class(w) l.addWidget(self.main_widget) l.setStretchFactor(self.main_widget, 10) - self.a_c_checkbox = QCheckBox( _('Apply changes'), w) + self.a_c_checkbox = QCheckBox(_('Apply changes'), w) l.addWidget(self.a_c_checkbox) self.ignore_change_signals = True @@ -1054,3 +1063,5 @@ bulk_widgets = { 'series': BulkSeries, 'enumeration': BulkEnumeration, } + + From 4edcbaa4455d284388fa90b6db9c1ab58d588c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20D=C5=82ugosz?= Date: Sat, 18 May 2013 18:44:14 +0200 Subject: [PATCH 02/11] woblink as an affiliate take 2 --- src/calibre/customize/builtins.py | 1 + src/calibre/gui2/store/stores/woblink_plugin.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index c321bb62ae..06a0d98a6b 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1661,6 +1661,7 @@ class StoreWoblinkStore(StoreBase): headquarters = 'PL' formats = ['EPUB', 'MOBI', 'PDF', 'WOBLINK'] + affiliate = True class XinXiiStore(StoreBase): name = 'XinXii' diff --git a/src/calibre/gui2/store/stores/woblink_plugin.py b/src/calibre/gui2/store/stores/woblink_plugin.py index a71be4840b..9b99271192 100644 --- a/src/calibre/gui2/store/stores/woblink_plugin.py +++ b/src/calibre/gui2/store/stores/woblink_plugin.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) -store_version = 3 # Needed for dynamic plugin loading +store_version = 4 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2013, Tomasz Długosz ' @@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en' import re import urllib +from base64 import b64encode from contextlib import closing from lxml import html @@ -25,21 +26,19 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class WoblinkStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - #aff_root = 'https://www.a4b-tracking.com/pl/stat-click-text-link/16/58/' + aff_root = 'https://www.a4b-tracking.com/pl/stat-click-text-link/16/58/' url = 'http://woblink.com/publication' - #aff_url = aff_root + str(b64encode(url)) + aff_url = aff_root + str(b64encode(url)) detail_url = None if detail_item: - detail_url = 'http://woblink.com' + detail_item #aff_root + str(b64encode('http://woblink.com' + detail_item)) + detail_url = aff_root + str(b64encode('http://woblink.com' + detail_item)) if external or self.config.get('open_external', False): - #open_url(QUrl(url_slash_cleaner(detail_url if detail_url else aff_url))) - open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) + open_url(QUrl(url_slash_cleaner(detail_url if detail_url else aff_url))) else: - #d = WebStoreDialog(self.gui, url, parent, detail_url if detail_url else aff_url) - d = WebStoreDialog(self.gui, url, parent, detail_url if detail_url else url) + d = WebStoreDialog(self.gui, url, parent, detail_url if detail_url else aff_url) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) d.exec_() From a9440f99b3ba4f536defb9fbc61b168a0ba5c4d7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 19 May 2013 09:26:47 +0530 Subject: [PATCH 03/11] Update The Australian --- recipes/the_oz.recipe | 64 ++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/recipes/the_oz.recipe b/recipes/the_oz.recipe index cffeb173da..3909265b30 100644 --- a/recipes/the_oz.recipe +++ b/recipes/the_oz.recipe @@ -26,14 +26,14 @@ class DailyTelegraph(BasicNewsRecipe): keep_only_tags = [dict(name='div', attrs={'id': 'story'})] - #remove_tags = [dict(name=['object','link'])] - remove_tags = [dict(name ='div', attrs = {'class': 'story-info'}), - dict(name ='div', attrs = {'class': 'story-header-tools'}), - dict(name ='div', attrs = {'class': 'story-sidebar'}), - dict(name ='div', attrs = {'class': 'story-footer'}), - dict(name ='div', attrs = {'id': 'comments'}), - dict(name ='div', attrs = {'class': 'story-extras story-extras-2'}), - dict(name ='div', attrs = {'class': 'group item-count-1 story-related'}) + # remove_tags = [dict(name=['object','link'])] + remove_tags = [dict(name='div', attrs={'class': 'story-info'}), + dict(name='div', attrs={'class': 'story-header-tools'}), + dict(name='div', attrs={'class': 'story-sidebar'}), + dict(name='div', attrs={'class': 'story-footer'}), + dict(name='div', attrs={'id': 'comments'}), + dict(name='div', attrs={'class': 'story-extras story-extras-2'}), + dict(name='div', attrs={'class': 'group item-count-1 story-related'}) ] extra_css = ''' @@ -45,30 +45,31 @@ class DailyTelegraph(BasicNewsRecipe): .caption{font-family:Trebuchet MS,Trebuchet,Helvetica,sans-serif; font-size: xx-small;} ''' - feeds = [ (u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'), - (u'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'), - (u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'), - (u'World News', u'http://feeds.news.com.au/public/rss/2.0/aus_world_808.xml'), - (u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'), - (u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'), - (u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'), - (u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'), - (u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'), - (u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'), - (u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'), - (u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'), - (u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'), - (u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'), - (u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'), - (u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'), - (u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'), - (u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.xml')] + feeds = [ + (u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'), + (u'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'), + (u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'), + (u'World News', u'http://feeds.news.com.au/public/rss/2.0/aus_world_808.xml'), + (u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'), + (u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'), + (u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'), + (u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'), + (u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'), + (u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'), + (u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'), + (u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'), + (u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'), + (u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'), + (u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'), + (u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'), + (u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'), + (u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.xml')] def get_browser(self): br = BasicNewsRecipe.get_browser(self) if self.username and self.password: br.open('http://www.theaustralian.com.au') - br.select_form(nr=0) + br.select_form(nr=1) br['username'] = self.username br['password'] = self.password raw = br.submit().read() @@ -80,10 +81,11 @@ class DailyTelegraph(BasicNewsRecipe): def get_article_url(self, article): return article.id - #br = self.get_browser() - #br.open(article.link).read() - #print br.geturl() + # br = self.get_browser() + # br.open(article.link).read() + # print br.geturl() + + # return br.geturl() - #return br.geturl() From 1e5036ab730ad4a5d6b11681901da666977d375d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 19 May 2013 10:32:33 +0530 Subject: [PATCH 04/11] pep8 --- src/calibre/gui2/widgets.py | 54 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 5d3d1e26c6..d59831ec11 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -24,7 +24,7 @@ from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files, history = XMLConfig('history') -class ProgressIndicator(QWidget): # {{{ +class ProgressIndicator(QWidget): # {{{ def __init__(self, *args): QWidget.__init__(self, *args) @@ -57,7 +57,7 @@ class ProgressIndicator(QWidget): # {{{ self.setVisible(False) # }}} -class FilenamePattern(QWidget, Ui_Form): # {{{ +class FilenamePattern(QWidget, Ui_Form): # {{{ changed_signal = pyqtSignal() @@ -82,7 +82,8 @@ class FilenamePattern(QWidget, Ui_Form): # {{{ val = prefs['filename_pattern'] self.re.lineEdit().setText(val) - val_hist += gprefs.get('filename_pattern_history', ['(?P.+)', '(?P<author>[^_-]+) -?\s*(?P<series>[^_0-9-]*)(?P<series_index>[0-9]*)\s*-\s*(?P<title>[^_].+) ?']) + val_hist += gprefs.get('filename_pattern_history', [ + '(?P<title>.+)', '(?P<author>[^_-]+) -?\s*(?P<series>[^_0-9-]*)(?P<series_index>[0-9]*)\s*-\s*(?P<title>[^_].+) ?']) if val in val_hist: del val_hist[val_hist.index(val)] val_hist.insert(0, val) @@ -136,7 +137,6 @@ class FilenamePattern(QWidget, Ui_Form): # {{{ self.isbn.setText(_('No match') if mi.isbn is None else str(mi.isbn)) - def pattern(self): pat = unicode(self.re.lineEdit().text()) return re.compile(pat) @@ -157,7 +157,7 @@ class FilenamePattern(QWidget, Ui_Form): # {{{ # }}} -class FormatList(QListWidget): # {{{ +class FormatList(QListWidget): # {{{ DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS formats_dropped = pyqtSignal(object, object) delete_format = pyqtSignal() @@ -186,7 +186,6 @@ class FormatList(QListWidget): # {{{ if d.err is None: self.formats_dropped.emit(event, [d.fpath]) - def dragMoveEvent(self, event): event.acceptProposedAction() @@ -198,7 +197,7 @@ class FormatList(QListWidget): # {{{ # }}} -class ImageDropMixin(object): # {{{ +class ImageDropMixin(object): # {{{ ''' Adds support for dropping images onto widgets and a context menu for copy/pasting images. @@ -272,7 +271,7 @@ class ImageDropMixin(object): # {{{ pixmap_to_data(pmap)) # }}} -class ImageView(QWidget, ImageDropMixin): # {{{ +class ImageView(QWidget, ImageDropMixin): # {{{ BORDER_WIDTH = 1 cover_changed = pyqtSignal(object) @@ -338,7 +337,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{ p.end() # }}} -class CoverView(QGraphicsView, ImageDropMixin): # {{{ +class CoverView(QGraphicsView, ImageDropMixin): # {{{ cover_changed = pyqtSignal(object) @@ -393,7 +392,7 @@ class BasicList(QListWidget): yield self.item(i) # }}} -class LineEditECM(object): # {{{ +class LineEditECM(object): # {{{ ''' Extend the context menu of a QLineEdit to include more actions. @@ -438,7 +437,7 @@ class LineEditECM(object): # {{{ # }}} -class EnLineEdit(LineEditECM, QLineEdit): # {{{ +class EnLineEdit(LineEditECM, QLineEdit): # {{{ ''' Enhanced QLineEdit. @@ -449,7 +448,7 @@ class EnLineEdit(LineEditECM, QLineEdit): # {{{ pass # }}} -class ItemsCompleter(QCompleter): # {{{ +class ItemsCompleter(QCompleter): # {{{ ''' A completer object that completes a list of tags. It is used in conjunction @@ -541,7 +540,7 @@ class CompleteLineEdit(EnLineEdit): # {{{ # }}} -class EnComboBox(QComboBox): # {{{ +class EnComboBox(QComboBox): # {{{ ''' Enhanced QComboBox. @@ -567,7 +566,7 @@ class EnComboBox(QComboBox): # {{{ # }}} -class CompleteComboBox(EnComboBox): # {{{ +class CompleteComboBox(EnComboBox): # {{{ def __init__(self, *args): EnComboBox.__init__(self, *args) @@ -584,7 +583,7 @@ class CompleteComboBox(EnComboBox): # {{{ # }}} -class HistoryLineEdit(QComboBox): # {{{ +class HistoryLineEdit(QComboBox): # {{{ lost_focus = pyqtSignal() @@ -637,7 +636,7 @@ class HistoryLineEdit(QComboBox): # {{{ # }}} -class ComboBoxWithHelp(QComboBox): # {{{ +class ComboBoxWithHelp(QComboBox): # {{{ ''' A combobox where item 0 is help text. CurrentText will return '' for item 0. Be sure to always fetch the text with currentText. Don't use the signals @@ -686,7 +685,7 @@ class ComboBoxWithHelp(QComboBox): # {{{ # }}} -class EncodingComboBox(QComboBox): # {{{ +class EncodingComboBox(QComboBox): # {{{ ''' A combobox that holds text encodings support by Python. This is only populated with the most @@ -711,7 +710,7 @@ class EncodingComboBox(QComboBox): # {{{ # }}} -class PythonHighlighter(QSyntaxHighlighter): # {{{ +class PythonHighlighter(QSyntaxHighlighter): # {{{ Rules = [] Formats = {} @@ -736,13 +735,11 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{ CONSTANTS = ["False", "True", "None", "NotImplemented", "Ellipsis"] - def __init__(self, parent=None): super(PythonHighlighter, self).__init__(parent) if not self.Config: self.loadConfig() - self.initializeFormats() PythonHighlighter.Rules.append((QRegExp( @@ -752,7 +749,7 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{ "|".join([r"\b%s\b" % builtin for builtin in self.BUILTINS])), "builtin")) PythonHighlighter.Rules.append((QRegExp( - "|".join([r"\b%s\b" % constant \ + "|".join([r"\b%s\b" % constant for constant in self.CONSTANTS])), "constant")) PythonHighlighter.Rules.append((QRegExp( r"\b[+-]?[0-9]+[lL]?\b" @@ -812,7 +809,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{ Config["%sfontbold" % name] = QVariant(bold).toBool() Config["%sfontitalic" % name] = QVariant(italic).toBool() - @classmethod def initializeFormats(cls): Config = cls.Config @@ -829,7 +825,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{ format.setFontItalic(Config["%sfontitalic" % name]) PythonHighlighter.Formats[name] = format - def highlightBlock(self, text): NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4) @@ -861,7 +856,7 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{ # Slow but good quality highlighting for comments. For more # speed, comment this out and add the following to __init__: - # PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment")) + # PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment")) if text.isEmpty(): pass elif text[0] == "#": @@ -900,7 +895,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{ self.setFormat(i, text.length(), PythonHighlighter.Formats["string"]) - def rehighlight(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QSyntaxHighlighter.rehighlight(self) @@ -1045,11 +1039,13 @@ class Splitter(QSplitter): @dynamic_property def side_index_size(self): def fget(self): - if self.count() < 2: return 0 + if self.count() < 2: + return 0 return self.sizes()[self.side_index] def fset(self, val): - if self.count() < 2: return + if self.count() < 2: + return if val == 0 and not self.is_side_index_hidden: self.save_state() sizes = list(self.sizes()) @@ -1081,7 +1077,8 @@ class Splitter(QSplitter): self.resize_timer.start() def get_state(self): - if self.count() < 2: return (False, 200) + if self.count() < 2: + return (False, 200) return (self.desired_show, self.desired_side_size) def apply_state(self, state, save_desired=True): @@ -1142,3 +1139,4 @@ class Splitter(QSplitter): # }}} + From d36fe893526bda91a3fd12ea62bbbf2de05f5ece Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 10:33:52 +0530 Subject: [PATCH 05/11] Fix #1180795 (Translation) --- src/calibre/gui2/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index d59831ec11..a10366c471 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -949,8 +949,8 @@ class LayoutButton(QToolButton): def set_state_to_hide(self, *args): self.setChecked(True) - self.setText(_('Hide %(label)s %(shortcut)s'%dict( - label=self.label, shortcut=self.shortcut))) + self.setText(_('Hide %(label)s %(shortcut)s')%dict( + label=self.label, shortcut=self.shortcut)) self.setToolTip(self.text()) self.setStatusTip(self.text()) From 474af52890f411c100e1e37a0ce27dbf071656c1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 11:00:23 +0530 Subject: [PATCH 06/11] EPUB Input: Handle EPUB files that have no <metadata> section in their OPF. See #1181546 --- src/calibre/ebooks/metadata/opf2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index f38a18989c..395fe0c484 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -560,7 +560,9 @@ class OPF(object): # {{{ self.package_version = 0 self.metadata = self.metadata_path(self.root) if not self.metadata: - raise ValueError('Malformed OPF file: No <metadata> element') + self.metadata = [self.root.makeelement('{http://www.idpf.org/2007/opf}metadata')] + self.root.insert(0, self.metadata[0]) + self.metadata[0].tail = '\n' self.metadata = self.metadata[0] if unquote_urls: self.unquote_urls() From 62061d2d6068520fa70b76df161cc45f0808ef96 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 12:34:25 +0530 Subject: [PATCH 07/11] Fix #1181669 (No detection of Yarvik tablet Xenta 13c) --- src/calibre/devices/android/driver.py | 39 ++++++++++++++------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 2855de16ae..8ee1e814e4 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -25,7 +25,7 @@ class ANDROID(USBMS): VENDOR_ID = { # HTC - 0x0bb4 : { 0xc02 : HTC_BCDS, + 0x0bb4 : {0xc02 : HTC_BCDS, 0xc01 : HTC_BCDS, 0xff9 : HTC_BCDS, 0xc86 : HTC_BCDS, @@ -52,13 +52,13 @@ class ANDROID(USBMS): }, # Eken - 0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] }, + 0x040d : {0x8510 : [0x0001], 0x0851 : [0x1]}, # Trekstor - 0x1e68 : { 0x006a : [0x0231] }, + 0x1e68 : {0x006a : [0x0231]}, # Motorola - 0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100], + 0x22b8 : {0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100], 0x2de8 : [0x229], 0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216], 0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216], @@ -111,7 +111,7 @@ class ANDROID(USBMS): }, # Samsung - 0x04e8 : { 0x681d : [0x0222, 0x0223, 0x0224, 0x0400], + 0x04e8 : {0x681d : [0x0222, 0x0223, 0x0224, 0x0400], 0x681c : [0x0222, 0x0223, 0x0224, 0x0400], 0x6640 : [0x0100], 0x685b : [0x0400, 0x0226], @@ -130,7 +130,7 @@ class ANDROID(USBMS): 0xc001 : [0x0226], 0xc004 : [0x0226], 0x8801 : [0x0226, 0x0227], - 0xe115 : [0x0216], # PocketBook A10 + 0xe115 : [0x0216], # PocketBook A10 }, # Another Viewsonic @@ -139,10 +139,10 @@ class ANDROID(USBMS): }, # Acer - 0x502 : { 0x3203 : [0x0100, 0x224]}, + 0x502 : {0x3203 : [0x0100, 0x224]}, # Dell - 0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]}, + 0x413c : {0xb007 : [0x0100, 0x0224, 0x0226]}, # LG 0x1004 : { @@ -166,25 +166,25 @@ class ANDROID(USBMS): # Huawei # Disabled as this USB id is used by various USB flash drives - #0x45e : { 0x00e1 : [0x007], }, + # 0x45e : { 0x00e1 : [0x007], }, # T-Mobile - 0x0408 : { 0x03ba : [0x0109], }, + 0x0408 : {0x03ba : [0x0109], }, # Xperia - 0x13d3 : { 0x3304 : [0x0001, 0x0002] }, + 0x13d3 : {0x3304 : [0x0001, 0x0002]}, # CREEL?? Also Nextbook and Wayteq - 0x5e3 : { 0x726 : [0x222] }, + 0x5e3 : {0x726 : [0x222]}, # ZTE - 0x19d2 : { 0x1353 : [0x226], 0x1351 : [0x227] }, + 0x19d2 : {0x1353 : [0x226], 0x1351 : [0x227]}, # Advent - 0x0955 : { 0x7100 : [0x9999] }, # This is the same as the Notion Ink Adam + 0x0955 : {0x7100 : [0x9999]}, # This is the same as the Notion Ink Adam # Kobo - 0x2237: { 0x2208 : [0x0226] }, + 0x2237: {0x2208 : [0x0226]}, # Lenovo 0x17ef : { @@ -193,10 +193,10 @@ class ANDROID(USBMS): }, # Pantech - 0x10a9 : { 0x6050 : [0x227] }, + 0x10a9 : {0x6050 : [0x227]}, # Prestigio and Teclast - 0x2207 : { 0 : [0x222], 0x10 : [0x222] }, + 0x2207 : {0 : [0x222], 0x10 : [0x222]}, } EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books', @@ -241,6 +241,7 @@ class ANDROID(USBMS): 'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E', 'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS', 'ICS', 'E400', '__FILE-STOR_GADG', 'ST80208-1', 'GT-S5660M_CARD', 'XT894', '_USB', + 'XENTA', ] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', @@ -253,7 +254,7 @@ class ANDROID(USBMS): 'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E', 'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD', 'ST80208-1', 'XT894', - '_USB', + '_USB', 'PROD_TAB13-201', ] OSX_MAIN_MEM = 'Android Device Main Memory' @@ -369,7 +370,6 @@ class WEBOS(USBMS): except ImportError: import Image, ImageDraw - coverdata = getattr(metadata, 'thumbnail', None) if coverdata and coverdata[2]: cover = Image.open(cStringIO.StringIO(coverdata[2])) @@ -418,3 +418,4 @@ class WEBOS(USBMS): coverfile.write(coverdata) + From dec4fbf39819bda05378a40aca5adda83a0983f7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 12:36:02 +0530 Subject: [PATCH 08/11] ... --- src/calibre/devices/android/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 8ee1e814e4..a39c190d05 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -219,7 +219,7 @@ class ANDROID(USBMS): 'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD', 'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0', 'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703', 'NEXT8D12', - 'MEDIATEK', 'KEENHI', 'TECLAST', 'SURFTAB'] + 'MEDIATEK', 'KEENHI', 'TECLAST', 'SURFTAB', 'XENTA',] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'A953', 'INC.NEXUS_ONE', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', @@ -241,7 +241,7 @@ class ANDROID(USBMS): 'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E', 'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS', 'ICS', 'E400', '__FILE-STOR_GADG', 'ST80208-1', 'GT-S5660M_CARD', 'XT894', '_USB', - 'XENTA', + 'PROD_TAB13-201', ] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', From 70340d8b7c2005e7c5027979a0e476e897b0654c Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 12:57:16 +0530 Subject: [PATCH 09/11] pep8 --- src/calibre/gui2/library/models.py | 54 ++++++++++++++---------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index d252af6395..2b0e7d9cc8 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -109,7 +109,7 @@ class ColumnIcon(object): except: pass -class BooksModel(QAbstractTableModel): # {{{ +class BooksModel(QAbstractTableModel): # {{{ about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') sorting_done = pyqtSignal(object, name='sortingDone') @@ -150,7 +150,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.default_image = default_image() self.sorted_on = DEFAULT_SORT self.sort_history = [self.sorted_on] - self.last_search = '' # The last search performed on this model + self.last_search = '' # The last search performed on this model self.column_map = [] self.headers = {} self.alignment_map = {} @@ -240,7 +240,6 @@ class BooksModel(QAbstractTableModel): # {{{ # Would like to to a join here, but the thread might be waiting to # do something on the GUI thread. Deadlock. - def refresh_ids(self, ids, current_row=-1): self._clear_caches() rows = self.db.refresh_ids(ids) @@ -284,7 +283,7 @@ class BooksModel(QAbstractTableModel): # {{{ def row_indices(self, index): ''' Return list indices of all cells in index.row()''' - return [ self.index(index.row(), c) for c in range(self.columnCount(None))] + return [self.index(index.row(), c) for c in range(self.columnCount(None))] @property def by_author(self): @@ -332,7 +331,7 @@ class BooksModel(QAbstractTableModel): # {{{ while True: row_ += 1 if forward else -1 if row_ < 0: - row_ = self.count() - 1; + row_ = self.count() - 1 elif row_ >= self.count(): row_ = 0 if self.id(row_) in self.ids_to_highlight_set: @@ -611,7 +610,7 @@ class BooksModel(QAbstractTableModel): # {{{ data = None try: data = self.db.cover(row_number) - except IndexError: # Happens if database has not yet been refreshed + except IndexError: # Happens if database has not yet been refreshed pass if not data: @@ -673,7 +672,7 @@ class BooksModel(QAbstractTableModel): # {{{ return QVariant(UNDEFINED_QDATETIME) def bool_type(r, idx=-1): - return None # displayed using a decorator + return None # displayed using a decorator def bool_type_decorator(r, idx=-1, bool_cols_are_tristate=True): val = force_to_bool(self.db.data[r][idx]) @@ -884,18 +883,18 @@ class BooksModel(QAbstractTableModel): # {{{ ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname, 'left')] return QVariant(ans) - #elif role == Qt.ToolTipRole and index.isValid(): + # elif role == Qt.ToolTipRole and index.isValid(): # if self.column_map[index.column()] in self.editable_cols: # return QVariant(_("Double click to <b>edit</b> me<br><br>")) return NONE def headerData(self, section, orientation, role): if orientation == Qt.Horizontal: - if section >= len(self.column_map): # same problem as in data, the column_map can be wrong + if section >= len(self.column_map): # same problem as in data, the column_map can be wrong return None if role == Qt.ToolTipRole: ht = self.column_map[section] - if ht == 'timestamp': # change help text because users know this field as 'date' + if ht == 'timestamp': # change help text because users know this field as 'date' ht = 'date' return QVariant(_('The lookup/search name is "{0}"').format(ht)) if role == Qt.DisplayRole: @@ -905,11 +904,10 @@ class BooksModel(QAbstractTableModel): # {{{ col = self.db.field_metadata['uuid']['rec_index'] return QVariant(_('This book\'s UUID is "{0}"').format(self.db.data[section][col])) - if role == Qt.DisplayRole: # orientation is vertical + if role == Qt.DisplayRole: # orientation is vertical return QVariant(section+1) return NONE - def flags(self, index): flags = QAbstractTableModel.flags(self, index) if index.isValid(): @@ -969,7 +967,7 @@ class BooksModel(QAbstractTableModel): # {{{ tmpl = unicode(value.toString()).strip() disp = cc['display'] disp['composite_template'] = tmpl - self.db.set_custom_column_metadata(cc['colnum'], display = disp) + self.db.set_custom_column_metadata(cc['colnum'], display=disp) self.refresh(reset=True) return True @@ -987,7 +985,7 @@ class BooksModel(QAbstractTableModel): # {{{ return self._set_data(index, value) except (IOError, OSError) as err: import traceback - if getattr(err, 'errno', None) == errno.EACCES: # Permission denied + if getattr(err, 'errno', None) == errno.EACCES: # Permission denied fname = getattr(err, 'filename', None) p = 'Locked file: %s\n\n'%fname if fname else '' error_dialog(get_gui(), _('Permission denied'), @@ -1065,7 +1063,7 @@ class BooksModel(QAbstractTableModel): # {{{ # }}} -class OnDeviceSearch(SearchQueryParser): # {{{ +class OnDeviceSearch(SearchQueryParser): # {{{ USABLE_LOCATIONS = [ 'all', @@ -1078,7 +1076,6 @@ class OnDeviceSearch(SearchQueryParser): # {{{ 'inlibrary' ] - def __init__(self, model): SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS) self.model = model @@ -1101,7 +1098,7 @@ class OnDeviceSearch(SearchQueryParser): # {{{ elif query.startswith('~'): matchkind = REGEXP_MATCH query = query[1:] - if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D + if matchkind != REGEXP_MATCH: # leave case in regexps because it can be significant e.g. \S \W \D query = query.lower() if location not in self.USABLE_LOCATIONS: @@ -1133,9 +1130,9 @@ class OnDeviceSearch(SearchQueryParser): # {{{ if locvalue == 'inlibrary': continue # this is bool, so can't match below try: - ### Can't separate authors because comma is used for name sep and author sep - ### Exact match might not get what you want. For that reason, turn author - ### exactmatch searches into contains searches. + # Can't separate authors because comma is used for name sep and author sep + # Exact match might not get what you want. For that reason, turn author + # exactmatch searches into contains searches. if locvalue == 'author' and matchkind == EQUALS_MATCH: m = CONTAINS_MATCH else: @@ -1148,13 +1145,13 @@ class OnDeviceSearch(SearchQueryParser): # {{{ if _match(query, vals, m, use_primary_find_in_search=upf): matches.add(index) break - except ValueError: # Unicode errors + except ValueError: # Unicode errors traceback.print_exc() return matches # }}} -class DeviceDBSortKeyGen(object): # {{{ +class DeviceDBSortKeyGen(object): # {{{ def __init__(self, attr, keyfunc, db): self.attr = attr @@ -1169,7 +1166,7 @@ class DeviceDBSortKeyGen(object): # {{{ return ans # }}} -class DeviceBooksModel(BooksModel): # {{{ +class DeviceBooksModel(BooksModel): # {{{ booklist_dirtied = pyqtSignal() upload_collections = pyqtSignal(object) @@ -1272,9 +1269,9 @@ class DeviceBooksModel(BooksModel): # {{{ if index.isValid(): cname = self.column_map[index.column()] if cname in self.editable and \ - (cname != 'collections' or \ - (callable(getattr(self.db, 'supports_collections', None)) and \ - self.db.supports_collections() and \ + (cname != 'collections' or + (callable(getattr(self.db, 'supports_collections', None)) and + self.db.supports_collections() and device_prefs['manage_device_metadata']=='manual')): flags |= Qt.ItemIsEditable return flags @@ -1432,7 +1429,7 @@ class DeviceBooksModel(BooksModel): # {{{ return data def paths(self, rows): - return [self.db[self.map[r.row()]].path for r in rows ] + return [self.db[self.map[r.row()]].path for r in rows] def paths_for_db_ids(self, db_ids, as_map=False): res = defaultdict(list) if as_map else [] @@ -1517,7 +1514,7 @@ class DeviceBooksModel(BooksModel): # {{{ elif role == Qt.ToolTipRole and index.isValid(): if self.is_row_marked_for_deletion(row): return QVariant(_('Marked for deletion')) - if cname in ['title', 'authors'] or (cname == 'collections' and \ + if cname in ['title', 'authors'] or (cname == 'collections' and self.db.supports_collections()): return QVariant(_("Double click to <b>edit</b> me<br><br>")) elif role == Qt.DecorationRole and cname == 'inlibrary': @@ -1586,3 +1583,4 @@ class DeviceBooksModel(BooksModel): # {{{ # }}} + From 2c12c1aa3db3c5b5a1f8fb2ff0a154c158821405 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 14:25:09 +0530 Subject: [PATCH 10/11] Show the number of currently selected books in the status bar at the bottom of the book list --- src/calibre/gui2/init.py | 64 ++++++++++++++++++++++-------- src/calibre/gui2/library/models.py | 28 +++++++++++-- src/calibre/gui2/ui.py | 6 +++ 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 131adc3216..14f7eec224 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -27,7 +27,7 @@ def partial(*args, **kwargs): _keep_refs.append(ans) return ans -class LibraryViewMixin(object): # {{{ +class LibraryViewMixin(object): # {{{ def __init__(self, db): self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection) @@ -100,7 +100,7 @@ class LibraryViewMixin(object): # {{{ # }}} -class LibraryWidget(Splitter): # {{{ +class LibraryWidget(Splitter): # {{{ def __init__(self, parent): orientation = Qt.Vertical @@ -119,7 +119,7 @@ class LibraryWidget(Splitter): # {{{ self.addWidget(parent.library_view) # }}} -class Stack(QStackedWidget): # {{{ +class Stack(QStackedWidget): # {{{ def __init__(self, parent): QStackedWidget.__init__(self, parent) @@ -147,7 +147,7 @@ class Stack(QStackedWidget): # {{{ # }}} -class UpdateLabel(QLabel): # {{{ +class UpdateLabel(QLabel): # {{{ def __init__(self, *args, **kwargs): QLabel.__init__(self, *args, **kwargs) @@ -157,22 +157,22 @@ class UpdateLabel(QLabel): # {{{ pass # }}} -class StatusBar(QStatusBar): # {{{ +class StatusBar(QStatusBar): # {{{ def __init__(self, parent=None): QStatusBar.__init__(self, parent) - self.default_message = __appname__ + ' ' + _('version') + ' ' + \ - self.get_version() + ' ' + _('created by Kovid Goyal') self.device_string = '' self.update_label = UpdateLabel('') + self.total = self.current = self.selected = 0 self.addPermanentWidget(self.update_label) self.update_label.setVisible(False) self._font = QFont() self._font.setBold(True) self.setFont(self._font) - self.defmsg = QLabel(self.default_message) + self.defmsg = QLabel('') self.defmsg.setFont(self._font) self.addWidget(self.defmsg) + self.set_label() def initialize(self, systray=None): self.systray = systray @@ -180,18 +180,42 @@ class StatusBar(QStatusBar): # {{{ def device_connected(self, devname): self.device_string = _('Connected ') + devname - self.defmsg.setText(self.default_message + ' ..::.. ' + - self.device_string) + self.set_label() + + def update_state(self, total, current, selected): + self.total, self.current, self.selected = total, current, selected + self.set_label() + + def set_label(self): + try: + self._set_label() + except: + import traceback + traceback.print_exc() + + def _set_label(self): + msg = '%s %s %s' % (__appname__, _('version'), get_version()) + if self.device_string: + msg += ' ..::.. ' + self.device_string + else: + msg += _(' %(created)s %(name)s') % dict(created=_('created by'), name='Kovid Goyal') + + if self.total != self.current: + base = _('%(num)d of %(total)d books') % dict(num=self.current, total=self.total) + else: + base = _('%d books') % self.total + if self.selected > 0: + base = _('%(num)s, %(sel)d selected') % dict(num=base, sel=self.selected) + + self.defmsg.setText('%s [%s]' % (msg, base)) self.clearMessage() def device_disconnected(self): self.device_string = '' + self.set_label() self.defmsg.setText(self.default_message) self.clearMessage() - def get_version(self): - return get_version() - def show_message(self, msg, timeout=0): self.showMessage(msg, timeout) if self.notifier is not None and not config['disable_tray_notification']: @@ -207,11 +231,11 @@ class StatusBar(QStatusBar): # {{{ # }}} -class LayoutMixin(object): # {{{ +class LayoutMixin(object): # {{{ def __init__(self): - if config['gui_layout'] == 'narrow': # narrow {{{ + if config['gui_layout'] == 'narrow': # narrow {{{ self.book_details = BookDetails(False, self) self.stack = Stack(self) self.bd_splitter = Splitter('book_details_splitter', @@ -224,7 +248,7 @@ class LayoutMixin(object): # {{{ self.centralwidget.layout().addWidget(self.bd_splitter) button_order = ('tb', 'bd', 'cb') # }}} - else: # wide {{{ + else: # wide {{{ self.bd_splitter = Splitter('book_details_splitter', _('Book Details'), I('book.png'), initial_side_size=200, orientation=Qt.Horizontal, parent=self, side_index=1, @@ -312,9 +336,15 @@ class LayoutMixin(object): # {{{ def read_layout_settings(self): # View states are restored automatically when set_database is called - for x in ('cb', 'tb', 'bd'): getattr(self, x+'_splitter').restore_state() + def update_status_bar(self, *args): + v = self.current_view() + selected = len(v.selectionModel().selectedRows()) + total, current = v.model().counts() + self.status_bar.update_state(total, current, selected) + # }}} + diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 2b0e7d9cc8..86d71dae70 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import functools, re, os, traceback, errno, time -from collections import defaultdict +from collections import defaultdict, namedtuple from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, QModelIndex, QVariant, QDateTime, QColor, QPixmap) @@ -29,6 +29,8 @@ from calibre.gui2.library import DEFAULT_SORT from calibre.utils.localization import calibre_langcode_to_name from calibre.library.coloring import color_row_key +Counts = namedtuple('Counts', 'total current') + def human_readable(size, precision=1): """ Convert a size in bytes into megabytes """ return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),) @@ -46,7 +48,7 @@ def default_image(): _default_image = QImage(I('default_cover.png')) return _default_image -class ColumnColor(object): +class ColumnColor(object): # {{{ def __init__(self, formatter, colors): self.mi = None @@ -70,9 +72,9 @@ class ColumnColor(object): return color except: pass +# }}} - -class ColumnIcon(object): +class ColumnIcon(object): # {{{ def __init__(self, formatter): self.mi = None @@ -108,6 +110,7 @@ class ColumnIcon(object): return icon_bitmap except: pass +# }}} class BooksModel(QAbstractTableModel): # {{{ @@ -281,6 +284,13 @@ class BooksModel(QAbstractTableModel): # {{{ self._clear_caches() self.count_changed_signal.emit(self.db.count()) + def counts(self): + if self.db.data.search_restriction_applied(): + total = self.db.data.get_search_restriction_book_count() + else: + total = self.db.count() + return Counts(total, self.count()) + def row_indices(self, index): ''' Return list indices of all cells in index.row()''' return [self.index(index.row(), c) for c in range(self.columnCount(None))] @@ -1195,6 +1205,12 @@ class DeviceBooksModel(BooksModel): # {{{ self.editable = ['title', 'authors', 'collections'] self.book_in_library = None + def counts(self): + return Counts(len(self.db), len(self.map)) + + def count_changed(self, *args): + self.count_changed_signal.emit(len(self.db)) + def mark_for_deletion(self, job, rows, rows_are_ids=False): db_indices = rows if rows_are_ids else self.indices(rows) db_items = [self.db[i] for i in db_indices if -1 < i < len(self.db)] @@ -1234,11 +1250,13 @@ class DeviceBooksModel(BooksModel): # {{{ if not succeeded: indices = self.row_indices(self.index(row, 0)) self.dataChanged.emit(indices[0], indices[-1]) + self.count_changed() def paths_deleted(self, paths): self.map = list(range(0, len(self.db))) self.resort(False) self.research(True) + self.count_changed() def is_row_marked_for_deletion(self, row): try: @@ -1301,6 +1319,7 @@ class DeviceBooksModel(BooksModel): # {{{ self.last_search = text if self.last_search: self.searched.emit(True) + self.count_changed() def research(self, reset=True): self.search(self.last_search, reset) @@ -1370,6 +1389,7 @@ class DeviceBooksModel(BooksModel): # {{{ self.map = list(range(0, len(db))) self.research(reset=False) self.resort() + self.count_changed() def cover(self, row): item = self.db[self.map[row]] diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index aafea4ef2b..c9f07ef6f6 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -325,6 +325,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if self.library_view.model().rowCount(None) < 3: self.library_view.resizeColumnsToContents() + for view in ('library', 'memory', 'card_a', 'card_b'): + v = getattr(self, '%s_view' % view) + v.selectionModel().selectionChanged.connect(self.update_status_bar) + v.model().count_changed_signal.connect(self.update_status_bar) + self.library_view.model().count_changed() self.bars_manager.database_changed(self.library_view.model().db) self.library_view.model().database_changed.connect(self.bars_manager.database_changed, @@ -661,6 +666,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ # Reset the view in case something changed while it was invisible self.current_view().reset() self.set_number_of_books_shown() + self.update_status_bar() def job_exception(self, job, dialog_title=_('Conversion Error')): if not hasattr(self, '_modeless_dialogs'): From b22ed18e580c5fd62d0dc8ea89d778dde1a371bf Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Sun, 19 May 2013 14:40:43 +0530 Subject: [PATCH 11/11] ... --- src/calibre/gui2/init.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 14f7eec224..ace176bba2 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -213,8 +213,6 @@ class StatusBar(QStatusBar): # {{{ def device_disconnected(self): self.device_string = '' self.set_label() - self.defmsg.setText(self.default_message) - self.clearMessage() def show_message(self, msg, timeout=0): self.showMessage(msg, timeout)