From a8deb0ed7d2b349a82711f96cb0ed28a3db16156 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 May 2011 09:56:27 -0600 Subject: [PATCH 01/12] ... --- src/calibre/devices/android/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index db473a755e..1cdf394c24 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -59,7 +59,7 @@ class ANDROID(USBMS): 0x0489 : { 0xc001 : [0x0226], 0xc004 : [0x0226], }, # Acer - 0x502 : { 0x3203 : [0x0100]}, + 0x502 : { 0x3203 : [0x0100, 0x224]}, # Dell 0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]}, From ce6cedf730c2c59b0cc41bcce7c29a969edf2eaf Mon Sep 17 00:00:00 2001 From: Li Fanxi Date: Thu, 26 May 2011 00:24:04 +0800 Subject: [PATCH 02/12] [Bug] Error in passing API key to douban API. --- src/calibre/ebooks/metadata/sources/douban.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/sources/douban.py b/src/calibre/ebooks/metadata/sources/douban.py index 3c6bb7b6c7..8a95c4ed6b 100644 --- a/src/calibre/ebooks/metadata/sources/douban.py +++ b/src/calibre/ebooks/metadata/sources/douban.py @@ -211,7 +211,7 @@ class Douban(Source): 'q': q, }) if self.DOUBAN_API_KEY and self.DOUBAN_API_KEY != '': - url = url + "?apikey=" + self.DOUBAN_API_KEY + url = url + "&apikey=" + self.DOUBAN_API_KEY return url # }}} From 70d1f3c046913e90cfd649859b2a2d32528a88b9 Mon Sep 17 00:00:00 2001 From: Li Fanxi Date: Thu, 26 May 2011 00:31:47 +0800 Subject: [PATCH 03/12] [Bug] Error in passing API key to douban API when search by isbn or douban id --- src/calibre/ebooks/metadata/sources/douban.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/sources/douban.py b/src/calibre/ebooks/metadata/sources/douban.py index 8a95c4ed6b..70bf01a00e 100644 --- a/src/calibre/ebooks/metadata/sources/douban.py +++ b/src/calibre/ebooks/metadata/sources/douban.py @@ -211,7 +211,10 @@ class Douban(Source): 'q': q, }) if self.DOUBAN_API_KEY and self.DOUBAN_API_KEY != '': - url = url + "&apikey=" + self.DOUBAN_API_KEY + if t == "isbn" or t == "subject": + url = url + "?apikey=" + self.DOUBAN_API_KEY + else: + url = url + "&apikey=" + self.DOUBAN_API_KEY return url # }}} From b707ae28202b550a704c0729b040e254fcaca51e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 25 May 2011 17:44:47 +0100 Subject: [PATCH 04/12] Addition of boolean template functions and, or, not. Change documentation to include them. Add a function classification summary to the documentation. --- src/calibre/manual/template_lang.rst | 20 +++++++++- src/calibre/utils/formatter_functions.py | 51 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 69c77e5bfd..16a90f7531 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -229,13 +229,14 @@ For various values of series_index, the program returns: The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions): + * ``and(value, value, ...)`` -- returns the string "1" if all values are not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers. * ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression * ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats. * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. * ``field(name)`` -- returns the metadata field named by ``name``. - * ``first_non_empty(value, value, ...) -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. + * ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. * ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are:: d : the day as number without a leading zero (1 to 31) @@ -251,7 +252,9 @@ The following functions are available in addition to those described in single-f iso : the date with time and timezone. Must be the only format present. * ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables. + * ``not(value)`` -- returns the string "1" if the value is empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers. + * ``or(value, value, ...)`` -- returns the string "1" if any value is not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``print(a, b, ...)`` -- prints the arguments to standard output. Unless you start calibre from the command line (``calibre-debug -g``), the output will go to a black hole. * ``raw_field(name)`` -- returns the metadata field named by name without applying any formatting. * ``strcat(a, b, ...)`` -- can take any number of arguments. Returns a string formed by concatenating all the arguments. @@ -259,7 +262,22 @@ The following functions are available in addition to those described in single-f * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. * ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers. * ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value. + +Function classification summary: + * Get values from metadata: ``field``. ``raw_field``. In some situations, ``lookup`` can be used in place of ``field``. + * Arithmetic: ``add``, ``subtract``, ``multiply``, ``divide`` + * Boolean: ``and``, ``or``, ``not``. The function ``if_empty`` is similar to ``and`` called with one argument. + * If-then-else: ``contains``, ``test`` + * Iterating over values: ``first_non_empty``, ``lookup``, ``switch`` + * List lookup: ``in_list``, ``list_item``, ``select``, + * List manipulation: ``count``, ``sublist``, ``subitems`` + * Recursion: ``eval``, ``template`` + * Relational: ``cmp`` , ``strcmp`` for strings + * String case changes: ``lowercase``, ``uppercase``, ``titlecase``, ``capitalize`` + * String manipulation: ``re``, ``shorten``, ``substr`` + * Other: ``assign``, ``booksize``, ``print``, ``format_date``, + .. _general_mode: Using general program mode diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index c53277f3ce..a3a156648f 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -594,7 +594,56 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction): i += 1 return '' +class BuiltinAnd(BuiltinFormatterFunction): + name = 'and' + arg_count = -1 + doc = _('and(value, value, ...) -- ' + 'returns the string "1" if all values are not empty, otherwise ' + 'returns the empty string. This function works well with test or ' + 'first_non_empty. You can have as many values as you want.') + + def evaluate(self, formatter, kwargs, mi, locals, *args): + i = 0 + while i < len(args): + if not args[i]: + return '' + i += 1 + return '1' + +class BuiltinOr(BuiltinFormatterFunction): + name = 'or' + arg_count = -1 + doc = _('or(value, value, ...) -- ' + 'returns the string "1" if any value is not empty, otherwise ' + 'returns the empty string. This function works well with test or ' + 'first_non_empty. You can have as many values as you want.') + + def evaluate(self, formatter, kwargs, mi, locals, *args): + i = 0 + while i < len(args): + if args[i]: + return '1' + i += 1 + return '' + +class BuiltinNot(BuiltinFormatterFunction): + name = 'not' + arg_count = 1 + doc = _('not(value) -- ' + 'returns the string "1" if the value is empty, otherwise ' + 'returns the empty string. This function works well with test or ' + 'first_non_empty. You can have as many values as you want.') + + def evaluate(self, formatter, kwargs, mi, locals, *args): + i = 0 + while i < len(args): + if args[i]: + return '1' + i += 1 + return '' + builtin_add = BuiltinAdd() +builtin_and = BuiltinAnd() builtin_assign = BuiltinAssign() builtin_booksize = BuiltinBooksize() builtin_capitalize = BuiltinCapitalize() @@ -612,6 +661,8 @@ builtin_list_item = BuiltinListitem() builtin_lookup = BuiltinLookup() builtin_lowercase = BuiltinLowercase() builtin_multiply = BuiltinMultiply() +builtin_not = BuiltinNot() +builtin_or = BuiltinOr() builtin_print = BuiltinPrint() builtin_raw_field = BuiltinRaw_field() builtin_re = BuiltinRe() From aa30d964db0b8fbacfb963cfce3fd08cb30759cd Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 25 May 2011 18:09:41 +0100 Subject: [PATCH 05/12] Slight improvement to user device faq entry --- src/calibre/manual/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index d3784eda6f..b120fd4a1b 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -138,7 +138,7 @@ Follow these steps to find the problem: My device is non-standard or unusual. What can I do to connect to it? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that presents that shows up as a disk drive in your operating system. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information. +In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that shows up as a disk drive in your operating system. Note: on windows, the device must have a drive letter for calibre to use it. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information. How does |app| manage collections on my SONY reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 7b98249b90366e26900d2b2cc5a86ff54595b083 Mon Sep 17 00:00:00 2001 From: John Schember Date: Wed, 25 May 2011 17:51:29 -0400 Subject: [PATCH 06/12] Store: chooser, fix sorting by drm. --- src/calibre/gui2/store/config/chooser/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/config/chooser/models.py b/src/calibre/gui2/store/config/chooser/models.py index 0c784f6614..3ceed6fb00 100644 --- a/src/calibre/gui2/store/config/chooser/models.py +++ b/src/calibre/gui2/store/config/chooser/models.py @@ -130,7 +130,7 @@ class Matches(QAbstractItemModel): elif col == 1: text = match.name elif col == 2: - text = 'b' if getattr(match, 'drm', True) else 'a' + text = 'a' if getattr(match, 'drm_free_only', True) else 'b' elif col == 3: text = getattr(match, 'headquarters', '') return text From 47cd16b6656e3f830f67fabc36fd3169242293c3 Mon Sep 17 00:00:00 2001 From: John Schember Date: Wed, 25 May 2011 18:53:11 -0400 Subject: [PATCH 07/12] Store: ... --- src/calibre/customize/builtins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 14565da152..bbbb8a738f 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1348,7 +1348,7 @@ class StoreSmashwordsStore(StoreBase): class StoreVirtualoStore(StoreBase): name = 'Virtualo' - author = 'Tomasz Długosz' + author = u'Tomasz Długosz' description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.' actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore' @@ -1386,7 +1386,7 @@ class StoreWizardsTowerBooksStore(StoreBase): class StoreWoblinkStore(StoreBase): name = 'Woblink' - author = 'Tomasz Długosz' + author = u'Tomasz Długosz' description = u'Czytanie zdarza się wszędzie!' actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore' From c6e98f3af75ed803b50a83a4125bd014075a6ef1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 May 2011 19:02:53 -0600 Subject: [PATCH 08/12] Dont allow user to use non email usernames when setting up Hotmail or Gmail accounts --- src/calibre/gui2/wizard/send_email.py | 108 +++++++++++++++----------- src/calibre/manual/faq.rst | 2 +- 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py index 5c7d916e1a..4337e558eb 100644 --- a/src/calibre/gui2/wizard/send_email.py +++ b/src/calibre/gui2/wizard/send_email.py @@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog): finally: self.test_button.setEnabled(True) +class RelaySetup(QDialog): + + def __init__(self, service, parent): + QDialog.__init__(self, parent) + + self.l = l = QGridLayout() + self.setLayout(l) + self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + self.tl = QLabel(('

'+_('Setup sending email using') + + ' {name}

' + + _('If you don\'t have an account, you can sign up for a free {name} email ' + 'account at http://{url}. {extra}')).format( + **service)) + l.addWidget(self.tl, 0, 0, 3, 0) + self.tl.setWordWrap(True) + self.tl.setOpenExternalLinks(True) + for name, label in ( + ['from_', _('Your %s &email address:')], + ['username', _('Your %s &username:')], + ['password', _('Your %s &password:')], + ): + la = QLabel(label%service['name']) + le = QLineEdit(self) + setattr(self, name, le) + setattr(self, name+'_label', la) + r = l.rowCount() + l.addWidget(la, r, 0) + l.addWidget(le, r, 1) + la.setBuddy(le) + if name == 'password': + self.ptoggle = QCheckBox(_('&Show password'), self) + l.addWidget(self.ptoggle, r, 2) + self.ptoggle.stateChanged.connect( + lambda s: self.password.setEchoMode(self.password.Normal if s + == Qt.Checked else self.password.Password)) + self.username.setText(service['username']) + self.password.setEchoMode(self.password.Password) + self.bl = QLabel('

' + _( + 'If you plan to use email to send books to your Kindle, remember to' + ' add the your %s email address to the allowed email addresses in your ' + 'Amazon.com Kindle management page.')%service['name']) + self.bl.setWordWrap(True) + l.addWidget(self.bl, l.rowCount(), 0, 3, 0) + l.addWidget(bb, l.rowCount(), 0, 3, 0) + self.setWindowTitle(_('Setup') + ' ' + service['name']) + self.resize(self.sizeHint()) + self.service = service + + def accept(self): + un = unicode(self.username.text()) + if self.service.get('at_in_username', False) and '@' not in un: + return error_dialog(self, _('Incorrect username'), + _('%s needs the full email address as your username') % + self.service['name'], show=True) + QDialog.accept(self) + class SendEmail(QWidget, Ui_Form): @@ -129,7 +187,8 @@ class SendEmail(QWidget, Ui_Form): 'port': 587, 'username': '@gmail.com', 'url': 'www.gmail.com', - 'extra': '' + 'extra': '', + 'at_in_username': True, }, 'hotmail': { 'name': 'Hotmail', @@ -143,53 +202,10 @@ class SendEmail(QWidget, Ui_Form): ' will let calibre send email. In this case, I' ' strongly suggest you setup a free gmail account' ' instead.'), + 'at_in_username': True, } }[service] - d = QDialog(self) - l = QGridLayout() - d.setLayout(l) - bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) - bb.accepted.connect(d.accept) - bb.rejected.connect(d.reject) - d.tl = QLabel(('

'+_('Setup sending email using') + - ' {name}

' + - _('If you don\'t have an account, you can sign up for a free {name} email ' - 'account at http://{url}. {extra}')).format( - **service)) - l.addWidget(d.tl, 0, 0, 3, 0) - d.tl.setWordWrap(True) - d.tl.setOpenExternalLinks(True) - for name, label in ( - ['from_', _('Your %s &email address:')], - ['username', _('Your %s &username:')], - ['password', _('Your %s &password:')], - ): - la = QLabel(label%service['name']) - le = QLineEdit(d) - setattr(d, name, le) - setattr(d, name+'_label', la) - r = l.rowCount() - l.addWidget(la, r, 0) - l.addWidget(le, r, 1) - la.setBuddy(le) - if name == 'password': - d.ptoggle = QCheckBox(_('&Show password'), d) - l.addWidget(d.ptoggle, r, 2) - d.ptoggle.stateChanged.connect( - lambda s: d.password.setEchoMode(d.password.Normal if s - == Qt.Checked else d.password.Password)) - d.username.setText(service['username']) - d.password.setEchoMode(d.password.Password) - d.bl = QLabel('

' + _( - 'If you plan to use email to send books to your Kindle, remember to' - ' add the your %s email address to the allowed email addresses in your ' - 'Amazon.com Kindle management page.')%service['name']) - d.bl.setWordWrap(True) - l.addWidget(d.bl, l.rowCount(), 0, 3, 0) - l.addWidget(bb, l.rowCount(), 0, 3, 0) - d.setWindowTitle(_('Setup') + ' ' + service['name']) - d.resize(d.sizeHint()) - bb.setVisible(True) + d = RelaySetup(service, self) if d.exec_() != d.Accepted: return self.relay_username.setText(d.username.text()) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index b120fd4a1b..1c0b49f30b 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -587,7 +587,7 @@ You can download news and convert it into an ebook with the command:: /opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub -If you want to generate MOBI, use outputfile.mobi instead. +If you want to generate MOBI, use outputfile.mobi instead and use ``--output-profile kindle``. You can email downloaded news with the command:: From 4e6c543f75bbaf66a3fd46313c3d91762643b511 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 May 2011 19:07:58 -0600 Subject: [PATCH 09/12] Fix #788378 (Add support for nook TSR) --- src/calibre/customize/builtins.py | 7 +++---- src/calibre/devices/nook/driver.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index bbbb8a738f..4a970b4661 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -594,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX -from calibre.devices.nook.driver import NOOK, NOOK_COLOR +from calibre.devices.nook.driver import NOOK, NOOK_COLOR, NOOK_TSR from calibre.devices.prs505.driver import PRS505 from calibre.devices.user_defined.driver import USER_DEFINED from calibre.devices.android.driver import ANDROID, S60 @@ -693,8 +693,7 @@ plugins += [ KINDLE, KINDLE2, KINDLE_DX, - NOOK, - NOOK_COLOR, + NOOK, NOOK_COLOR, NOOK_TSR, PRS505, ANDROID, S60, @@ -1277,7 +1276,7 @@ class StoreLegimiStore(StoreBase): author = u'Tomasz Długosz' description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer' actual_plugin = 'calibre.gui2.store.legimi_plugin:LegimiStore' - + drm_free_only = False headquarters = 'PL' formats = ['EPUB'] diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index 39d0763735..3c30b88568 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK): return filepath +class NOOK_TSR(NOOK): + gui_name = _('Nook Simple') + description = _('Communicate with the Nook TSR eBook reader.') + + PRODUCT_ID = [0x003] + BCD = [0x216] + + EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books' + + From 612c4c36da96e53b772209e29682fdd1cf409b62 Mon Sep 17 00:00:00 2001 From: John Schember Date: Thu, 26 May 2011 07:06:15 -0400 Subject: [PATCH 10/12] Store: chooser, add descriptive tool tips. --- src/calibre/gui2/store/config/chooser/models.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/config/chooser/models.py b/src/calibre/gui2/store/config/chooser/models.py index 2eaa1655ce..6c95d74ffc 100644 --- a/src/calibre/gui2/store/config/chooser/models.py +++ b/src/calibre/gui2/store/config/chooser/models.py @@ -103,7 +103,22 @@ class Matches(QAbstractItemModel): return Qt.Unchecked return Qt.Checked elif role == Qt.ToolTipRole: - return QVariant('

%s

' % result.description) + if col == 0: + if is_disabled(result): + return QVariant(_('

This store is currently diabled and cannot be used in other parts of calibre.

')) + else: + return QVariant(_('

This store is currently enabled and can be used in other parts of calibre.

')) + elif col == 1: + return QVariant('

%s

' % result.description) + elif col == 2: + if result.drm_free_only: + return QVariant(_('

This store only distributes ebooks with DRM.

')) + else: + return QVariant(_('

This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis.

')) + elif col == 3: + return QVariant(_('

This store is headquartered in %s. This is a good indication of what market the store caters to. However, this does not necessarily mean that the store is limited to that market only.

') % result.headquarters) + elif col == 4: + return QVariant(_('

This store distributes ebooks in the following formats: %s

') % ', '.join(result.formats)) return NONE def setData(self, index, data, role): From 86ad6f7787cada352fc33633865389abbc35e32f Mon Sep 17 00:00:00 2001 From: John Schember Date: Thu, 26 May 2011 08:29:27 -0400 Subject: [PATCH 11/12] Store: Search, use the GUI object directly to handle istores instead of passing it to the dialog. This to allow for reloading of enabled stores later. --- src/calibre/gui2/actions/store.py | 2 +- src/calibre/gui2/store/search/search.py | 86 +++++++++++-------------- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index 0fd783f0a3..6d9720548e 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -44,7 +44,7 @@ class StoreAction(InterfaceAction): def search(self, query=''): self.show_disclaimer() from calibre.gui2.store.search.search import SearchDialog - sd = SearchDialog(self.gui.istores, self.gui, query) + sd = SearchDialog(self.gui, self.gui, query) sd.exec_() def _get_selected_row(self): diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 3be39e3e87..fd49ebd67b 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -10,7 +10,7 @@ import re from random import shuffle from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, - QVBoxLayout, QIcon, QWidget) + QVBoxLayout, QIcon, QWidget, QTabWidget) from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2.progress_indicator import ProgressIndicator @@ -22,7 +22,7 @@ from calibre.gui2.store.search.search_ui import Ui_Dialog class SearchDialog(QDialog, Ui_Dialog): - def __init__(self, istores, parent=None, query=''): + def __init__(self, gui, parent=None, query=''): QDialog.__init__(self, parent) self.setupUi(self) @@ -34,8 +34,7 @@ class SearchDialog(QDialog, Ui_Dialog): # the variables it sets up are used later. self.load_settings() - # We keep a cache of store plugins and reference them by name. - self.store_plugins = istores + self.gui = gui # Setup our worker threads. self.search_pool = SearchThreadPool(self.search_thread_count) @@ -49,22 +48,11 @@ class SearchDialog(QDialog, Ui_Dialog): self.hang_check = 0 # Update store caches silently. - for p in self.store_plugins.values(): + for p in self.gui.istores.values(): self.cache_pool.add_task(p, self.timeout) - # Add check boxes for each store so the user - # can disable searching specific stores on a - # per search basis. - stores_check_widget = QWidget() - store_list_layout = QVBoxLayout() - stores_check_widget.setLayout(store_list_layout) - for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()): - cbox = QCheckBox(x) - cbox.setChecked(False) - store_list_layout.addWidget(cbox) - setattr(self, 'store_check_' + x, cbox) - store_list_layout.addStretch() - self.store_list.setWidget(stores_check_widget) + self.store_checks = {} + self.setup_store_checks() # Set the search query self.search_edit.setText(query) @@ -91,6 +79,22 @@ class SearchDialog(QDialog, Ui_Dialog): self.progress_checker.start(100) self.restore_state() + + def setup_store_checks(self): + # Add check boxes for each store so the user + # can disable searching specific stores on a + # per search basis. + stores_check_widget = QWidget() + store_list_layout = QVBoxLayout() + stores_check_widget.setLayout(store_list_layout) + for x in sorted(self.gui.istores.keys(), key=lambda x: x.lower()): + cbox = QCheckBox(x) + cbox.setChecked(False) + store_list_layout.addWidget(cbox) + self.store_checks['store_check_' + x] = cbox + store_list_layout.addStretch() + self.store_list.setWidget(stores_check_widget) + def build_adv_search(self): adv = AdvSearchBuilderDialog(self) @@ -126,11 +130,12 @@ class SearchDialog(QDialog, Ui_Dialog): # futher filtering. self.results_view.model().set_query(query) - # Plugins are in alphebetic order. Randomize the - # order of plugin names. This way plugins closer + # Plugins are in random order that does not change. + # Randomize the ord of the plugin names every time + # there is a search. This way plugins closer # to a don't have an unfair advantage over # plugins further from a. - store_names = self.store_plugins.keys() + store_names = self.store_checks.keys() if not store_names: return # Remove all of our internal filtering logic from the query. @@ -138,8 +143,8 @@ class SearchDialog(QDialog, Ui_Dialog): shuffle(store_names) # Add plugins that the user has checked to the search pool's work queue. for n in store_names: - if getattr(self, 'store_check_' + n).isChecked(): - self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout) + if self.store_checks[n].isChecked(): + self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout) self.hang_check = 0 self.checker.start(100) self.pi.startAnimation() @@ -179,8 +184,8 @@ class SearchDialog(QDialog, Ui_Dialog): self.config['open_external'] = self.open_external.isChecked() store_check = {} - for n in self.store_plugins: - store_check[n] = getattr(self, 'store_check_' + n).isChecked() + for k, v in self.store_checks.items(): + store_check[k] = v.isChecked() self.config['store_checked'] = store_check def restore_state(self): @@ -206,8 +211,8 @@ class SearchDialog(QDialog, Ui_Dialog): store_check = self.config.get('store_checked', None) if store_check: for n in store_check: - if hasattr(self, 'store_check_' + n): - getattr(self, 'store_check_' + n).setChecked(store_check[n]) + if n in self.store_checks: + self.store_checks[n].setChecked(store_check[n]) self.results_view.model().sort_col = self.config.get('sort_col', 2) self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder) @@ -234,7 +239,7 @@ class SearchDialog(QDialog, Ui_Dialog): self.config['open_external'] = self.open_external.isChecked() d = QDialog(self) - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + button_box = QDialogButtonBox(QDialogButtonBox.Close) v = QVBoxLayout(d) button_box.accepted.connect(d.accept) button_box.rejected.connect(d.reject) @@ -244,10 +249,8 @@ class SearchDialog(QDialog, Ui_Dialog): v.addWidget(button_box) d.exec_() - - if d.result() == QDialog.Accepted: - config_widget.save_settings() - self.config_changed() + config_widget.save_settings() + self.config_changed() def config_changed(self): self.load_settings() @@ -283,7 +286,7 @@ class SearchDialog(QDialog, Ui_Dialog): def open_store(self, index): result = self.results_view.model().get_result(index) - self.store_plugins[result.store_name].open(self, result.detail_item, self.open_external.isChecked()) + self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked()) def check_progress(self): if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running(): @@ -292,27 +295,16 @@ class SearchDialog(QDialog, Ui_Dialog): if not self.pi.isAnimated(): self.pi.startAnimation() - def get_store_checks(self): - ''' - Returns a list of QCheckBox's for each store. - ''' - checks = [] - for x in self.store_plugins: - check = getattr(self, 'store_check_' + x, None) - if check: - checks.append(check) - return checks - def stores_select_all(self): - for check in self.get_store_checks(): + for check in self.store_checks.values(): check.setChecked(True) def stores_select_invert(self): - for check in self.get_store_checks(): + for check in self.store_checks.values(): check.setChecked(not check.isChecked()) def stores_select_none(self): - for check in self.get_store_checks(): + for check in self.store_checks.values(): check.setChecked(False) def dialog_closed(self, result): From 0a18bb39060df5c20a93f433e3ddebc6b4766f00 Mon Sep 17 00:00:00 2001 From: John Schember Date: Thu, 26 May 2011 08:45:04 -0400 Subject: [PATCH 12/12] Store: Search, integrate store chooser into config dialog. --- src/calibre/gui2/store/search/search.py | 27 +++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index fd49ebd67b..faeaf507c9 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -14,6 +14,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2.progress_indicator import ProgressIndicator +from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget from calibre.gui2.store.config.search.search_widget import StoreConfigWidget from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.search.download_thread import SearchThreadPool, \ @@ -84,18 +85,23 @@ class SearchDialog(QDialog, Ui_Dialog): # Add check boxes for each store so the user # can disable searching specific stores on a # per search basis. + existing = {} + for n in self.store_checks: + existing[n] = self.store_checks[n].isChecked() + + self.store_checks = {} + stores_check_widget = QWidget() store_list_layout = QVBoxLayout() stores_check_widget.setLayout(store_list_layout) for x in sorted(self.gui.istores.keys(), key=lambda x: x.lower()): cbox = QCheckBox(x) - cbox.setChecked(False) + cbox.setChecked(existing.get(x, False)) store_list_layout.addWidget(cbox) - self.store_checks['store_check_' + x] = cbox + self.store_checks[x] = cbox store_list_layout.addStretch() self.store_list.setWidget(stores_check_widget) - def build_adv_search(self): adv = AdvSearchBuilderDialog(self) if adv.exec_() == QDialog.Accepted: @@ -244,13 +250,22 @@ class SearchDialog(QDialog, Ui_Dialog): button_box.accepted.connect(d.accept) button_box.rejected.connect(d.reject) d.setWindowTitle(_('Customize get books search')) - config_widget = StoreConfigWidget(self.config) - v.addWidget(config_widget) + + tab_widget = QTabWidget(d) + v.addWidget(tab_widget) v.addWidget(button_box) + chooser_config_widget = StoreChooserWidget() + search_config_widget = StoreConfigWidget(self.config) + + tab_widget.addTab(chooser_config_widget, _('Choose stores')) + tab_widget.addTab(search_config_widget, _('Configure search')) + d.exec_() - config_widget.save_settings() + search_config_widget.save_settings() self.config_changed() + self.gui.load_store_plugins() + self.setup_store_checks() def config_changed(self): self.load_settings()