merge with John's branch

This commit is contained in:
Tomasz Długosz 2011-05-27 07:47:12 +02:00
commit 36dc3c3249
12 changed files with 236 additions and 137 deletions

View File

@ -594,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX 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.prs505.driver import PRS505
from calibre.devices.user_defined.driver import USER_DEFINED from calibre.devices.user_defined.driver import USER_DEFINED
from calibre.devices.android.driver import ANDROID, S60 from calibre.devices.android.driver import ANDROID, S60
@ -693,8 +693,7 @@ plugins += [
KINDLE, KINDLE,
KINDLE2, KINDLE2,
KINDLE_DX, KINDLE_DX,
NOOK, NOOK, NOOK_COLOR, NOOK_TSR,
NOOK_COLOR,
PRS505, PRS505,
ANDROID, ANDROID,
S60, S60,
@ -1277,7 +1276,7 @@ class StoreLegimiStore(StoreBase):
author = u'Tomasz Długosz' 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' 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' actual_plugin = 'calibre.gui2.store.legimi_plugin:LegimiStore'
drm_free_only = False drm_free_only = False
headquarters = 'PL' headquarters = 'PL'
formats = ['EPUB'] formats = ['EPUB']
@ -1348,7 +1347,7 @@ class StoreSmashwordsStore(StoreBase):
class StoreVirtualoStore(StoreBase): class StoreVirtualoStore(StoreBase):
name = 'Virtualo' 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.' 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' actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore'
@ -1386,7 +1385,7 @@ class StoreWizardsTowerBooksStore(StoreBase):
class StoreWoblinkStore(StoreBase): class StoreWoblinkStore(StoreBase):
name = 'Woblink' name = 'Woblink'
author = 'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Czytanie zdarza się wszędzie!' description = u'Czytanie zdarza się wszędzie!'
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore' actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'

View File

@ -59,7 +59,7 @@ class ANDROID(USBMS):
0x0489 : { 0xc001 : [0x0226], 0xc004 : [0x0226], }, 0x0489 : { 0xc001 : [0x0226], 0xc004 : [0x0226], },
# Acer # Acer
0x502 : { 0x3203 : [0x0100]}, 0x502 : { 0x3203 : [0x0100, 0x224]},
# Dell # Dell
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]}, 0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]},

View File

@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK):
return filepath 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'

View File

@ -211,7 +211,10 @@ class Douban(Source):
'q': q, 'q': q,
}) })
if self.DOUBAN_API_KEY and self.DOUBAN_API_KEY != '': 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 return url
# }}} # }}}

View File

@ -44,7 +44,7 @@ class StoreAction(InterfaceAction):
def search(self, query=''): def search(self, query=''):
self.show_disclaimer() self.show_disclaimer()
from calibre.gui2.store.search.search import SearchDialog 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_() sd.exec_()
def _get_selected_row(self): def _get_selected_row(self):

View File

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
'''
Config widget access functions for enabling and disabling stores.
'''
def config_widget():
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
return StoreChooserWidget()
def save_settings(config_widget):
pass

View File

@ -33,15 +33,15 @@ class Matches(QAbstractItemModel):
self.sort_col = 1 self.sort_col = 1
self.sort_order = Qt.AscendingOrder self.sort_order = Qt.AscendingOrder
def get_plugin(self, index): def get_plugin(self, index):
row = index.row() row = index.row()
if row < len(self.matches): if row < len(self.matches):
return self.matches[row] return self.matches[row]
else: else:
return None return None
def search(self, filter): def search(self, filter):
self.filter = filter.strip() self.filter = filter.strip()
if not self.filter: if not self.filter:
self.matches = self.all_matches self.matches = self.all_matches
@ -71,7 +71,7 @@ class Matches(QAbstractItemModel):
def columnCount(self, *args): def columnCount(self, *args):
return len(self.HEADERS) return len(self.HEADERS)
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role != Qt.DisplayRole: if role != Qt.DisplayRole:
return NONE return NONE
@ -103,7 +103,22 @@ class Matches(QAbstractItemModel):
return Qt.Unchecked return Qt.Unchecked
return Qt.Checked return Qt.Checked
elif role == Qt.ToolTipRole: elif role == Qt.ToolTipRole:
return QVariant('<p>%s</p>' % result.description) if col == 0:
if is_disabled(result):
return QVariant(_('<p>This store is currently diabled and cannot be used in other parts of calibre.</p>'))
else:
return QVariant(_('<p>This store is currently enabled and can be used in other parts of calibre.</p>'))
elif col == 1:
return QVariant('<p>%s</p>' % result.description)
elif col == 2:
if result.drm_free_only:
return QVariant(_('<p>This store only distributes ebooks with DRM.</p>'))
else:
return QVariant(_('<p>This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis.</p>'))
elif col == 3:
return QVariant(_('<p>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.</p>') % result.headquarters)
elif col == 4:
return QVariant(_('<p>This store distributes ebooks in the following formats: %s</p>') % ', '.join(result.formats))
return NONE return NONE
def setData(self, index, data, role): def setData(self, index, data, role):
@ -114,7 +129,7 @@ class Matches(QAbstractItemModel):
if data.toBool(): if data.toBool():
enable_plugin(self.get_plugin(index)) enable_plugin(self.get_plugin(index))
else: else:
disable_plugin(self.get_plugin(index)) disable_plugin(self.get_plugin(index))
self.dataChanged.emit(self.index(index.row(), 0), self.index(index.row(), self.columnCount() - 1)) self.dataChanged.emit(self.index(index.row(), 0), self.index(index.row(), self.columnCount() - 1))
return True return True
@ -130,9 +145,9 @@ class Matches(QAbstractItemModel):
elif col == 1: elif col == 1:
text = match.name text = match.name
elif col == 2: elif col == 2:
text = 'b' if match.drm else 'a' text = 'a' if getattr(match, 'drm_free_only', True) else 'b'
elif col == 3: elif col == 3:
text = match.headquarters text = getattr(match, 'headquarters', '')
return text return text
def sort(self, col, order, reset=True): def sort(self, col, order, reset=True):
@ -149,7 +164,7 @@ class Matches(QAbstractItemModel):
class SearchFilter(SearchQueryParser): class SearchFilter(SearchQueryParser):
USABLE_LOCATIONS = [ USABLE_LOCATIONS = [
'all', 'all',
'description', 'description',
@ -202,7 +217,7 @@ class SearchFilter(SearchQueryParser):
q['formats'] = q['format'] q['formats'] = q['format']
for sr in self.srs: for sr in self.srs:
for locvalue in locations: for locvalue in locations:
accessor = q[locvalue] accessor = q[locvalue]
if query == 'true': if query == 'true':
if locvalue in ('drm', 'enabled'): if locvalue in ('drm', 'enabled'):
if accessor(sr) == True: if accessor(sr) == True:
@ -240,5 +255,3 @@ class SearchFilter(SearchQueryParser):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return matches return matches

View File

@ -10,10 +10,11 @@ import re
from random import shuffle from random import shuffle
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, 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 import JSONConfig, info_dialog
from calibre.gui2.progress_indicator import ProgressIndicator 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.config.search.search_widget import StoreConfigWidget
from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog
from calibre.gui2.store.search.download_thread import SearchThreadPool, \ from calibre.gui2.store.search.download_thread import SearchThreadPool, \
@ -22,7 +23,7 @@ from calibre.gui2.store.search.search_ui import Ui_Dialog
class SearchDialog(QDialog, 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) QDialog.__init__(self, parent)
self.setupUi(self) self.setupUi(self)
@ -34,8 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
# the variables it sets up are used later. # the variables it sets up are used later.
self.load_settings() self.load_settings()
# We keep a cache of store plugins and reference them by name. self.gui = gui
self.store_plugins = istores
# Setup our worker threads. # Setup our worker threads.
self.search_pool = SearchThreadPool(self.search_thread_count) self.search_pool = SearchThreadPool(self.search_thread_count)
@ -49,22 +49,11 @@ class SearchDialog(QDialog, Ui_Dialog):
self.hang_check = 0 self.hang_check = 0
# Update store caches silently. # 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) self.cache_pool.add_task(p, self.timeout)
# Add check boxes for each store so the user self.store_checks = {}
# can disable searching specific stores on a self.setup_store_checks()
# 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)
# Set the search query # Set the search query
self.search_edit.setText(query) self.search_edit.setText(query)
@ -91,6 +80,27 @@ class SearchDialog(QDialog, Ui_Dialog):
self.progress_checker.start(100) self.progress_checker.start(100)
self.restore_state() 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.
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(existing.get(x, False))
store_list_layout.addWidget(cbox)
self.store_checks[x] = cbox
store_list_layout.addStretch()
self.store_list.setWidget(stores_check_widget)
def build_adv_search(self): def build_adv_search(self):
adv = AdvSearchBuilderDialog(self) adv = AdvSearchBuilderDialog(self)
@ -126,11 +136,12 @@ class SearchDialog(QDialog, Ui_Dialog):
# futher filtering. # futher filtering.
self.results_view.model().set_query(query) self.results_view.model().set_query(query)
# Plugins are in alphebetic order. Randomize the # Plugins are in random order that does not change.
# order of plugin names. This way plugins closer # 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 # to a don't have an unfair advantage over
# plugins further from a. # plugins further from a.
store_names = self.store_plugins.keys() store_names = self.store_checks.keys()
if not store_names: if not store_names:
return return
# Remove all of our internal filtering logic from the query. # Remove all of our internal filtering logic from the query.
@ -138,8 +149,8 @@ class SearchDialog(QDialog, Ui_Dialog):
shuffle(store_names) shuffle(store_names)
# Add plugins that the user has checked to the search pool's work queue. # Add plugins that the user has checked to the search pool's work queue.
for n in store_names: for n in store_names:
if getattr(self, 'store_check_' + n).isChecked(): if self.store_checks[n].isChecked():
self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout) self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
self.hang_check = 0 self.hang_check = 0
self.checker.start(100) self.checker.start(100)
self.pi.startAnimation() self.pi.startAnimation()
@ -179,8 +190,8 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config['open_external'] = self.open_external.isChecked() self.config['open_external'] = self.open_external.isChecked()
store_check = {} store_check = {}
for n in self.store_plugins: for k, v in self.store_checks.items():
store_check[n] = getattr(self, 'store_check_' + n).isChecked() store_check[k] = v.isChecked()
self.config['store_checked'] = store_check self.config['store_checked'] = store_check
def restore_state(self): def restore_state(self):
@ -206,8 +217,8 @@ class SearchDialog(QDialog, Ui_Dialog):
store_check = self.config.get('store_checked', None) store_check = self.config.get('store_checked', None)
if store_check: if store_check:
for n in store_check: for n in store_check:
if hasattr(self, 'store_check_' + n): if n in self.store_checks:
getattr(self, 'store_check_' + n).setChecked(store_check[n]) 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_col = self.config.get('sort_col', 2)
self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder) self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
@ -234,20 +245,27 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config['open_external'] = self.open_external.isChecked() self.config['open_external'] = self.open_external.isChecked()
d = QDialog(self) d = QDialog(self)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box = QDialogButtonBox(QDialogButtonBox.Close)
v = QVBoxLayout(d) v = QVBoxLayout(d)
button_box.accepted.connect(d.accept) button_box.accepted.connect(d.accept)
button_box.rejected.connect(d.reject) button_box.rejected.connect(d.reject)
d.setWindowTitle(_('Customize get books search')) 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) v.addWidget(button_box)
d.exec_() 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'))
if d.result() == QDialog.Accepted: d.exec_()
config_widget.save_settings() search_config_widget.save_settings()
self.config_changed() self.config_changed()
self.gui.load_store_plugins()
self.setup_store_checks()
def config_changed(self): def config_changed(self):
self.load_settings() self.load_settings()
@ -283,7 +301,7 @@ class SearchDialog(QDialog, Ui_Dialog):
def open_store(self, index): def open_store(self, index):
result = self.results_view.model().get_result(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): 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(): 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 +310,16 @@ class SearchDialog(QDialog, Ui_Dialog):
if not self.pi.isAnimated(): if not self.pi.isAnimated():
self.pi.startAnimation() 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): def stores_select_all(self):
for check in self.get_store_checks(): for check in self.store_checks.values():
check.setChecked(True) check.setChecked(True)
def stores_select_invert(self): def stores_select_invert(self):
for check in self.get_store_checks(): for check in self.store_checks.values():
check.setChecked(not check.isChecked()) check.setChecked(not check.isChecked())
def stores_select_none(self): def stores_select_none(self):
for check in self.get_store_checks(): for check in self.store_checks.values():
check.setChecked(False) check.setChecked(False)
def dialog_closed(self, result): def dialog_closed(self, result):

View File

@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog):
finally: finally:
self.test_button.setEnabled(True) 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(('<p>'+_('Setup sending email using') +
' <b>{name}</b><p>' +
_('If you don\'t have an account, you can sign up for a free {name} email '
'account at <a href="http://{url}">http://{url}</a>. {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('<p>' + _(
'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): class SendEmail(QWidget, Ui_Form):
@ -129,7 +187,8 @@ class SendEmail(QWidget, Ui_Form):
'port': 587, 'port': 587,
'username': '@gmail.com', 'username': '@gmail.com',
'url': 'www.gmail.com', 'url': 'www.gmail.com',
'extra': '' 'extra': '',
'at_in_username': True,
}, },
'hotmail': { 'hotmail': {
'name': 'Hotmail', 'name': 'Hotmail',
@ -143,53 +202,10 @@ class SendEmail(QWidget, Ui_Form):
' will let calibre send email. In this case, I' ' will let calibre send email. In this case, I'
' strongly suggest you setup a free gmail account' ' strongly suggest you setup a free gmail account'
' instead.'), ' instead.'),
'at_in_username': True,
} }
}[service] }[service]
d = QDialog(self) d = RelaySetup(service, 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(('<p>'+_('Setup sending email using') +
' <b>{name}</b><p>' +
_('If you don\'t have an account, you can sign up for a free {name} email '
'account at <a href="http://{url}">http://{url}</a>. {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('<p>' + _(
'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)
if d.exec_() != d.Accepted: if d.exec_() != d.Accepted:
return return
self.relay_username.setText(d.username.text()) self.relay_username.setText(d.username.text())

View File

@ -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? 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? How does |app| manage collections on my SONY reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -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 /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:: You can email downloaded news with the command::

View File

@ -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): 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. * ``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 * ``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. * ``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``. * ``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. * ``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``. * ``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:: * ``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) 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. 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. * ``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. * ``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. * ``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. * ``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. * ``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'``. * ``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. * ``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. * ``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: .. _general_mode:
Using general program mode Using general program mode

View File

@ -594,7 +594,56 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
i += 1 i += 1
return '' 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_add = BuiltinAdd()
builtin_and = BuiltinAnd()
builtin_assign = BuiltinAssign() builtin_assign = BuiltinAssign()
builtin_booksize = BuiltinBooksize() builtin_booksize = BuiltinBooksize()
builtin_capitalize = BuiltinCapitalize() builtin_capitalize = BuiltinCapitalize()
@ -612,6 +661,8 @@ builtin_list_item = BuiltinListitem()
builtin_lookup = BuiltinLookup() builtin_lookup = BuiltinLookup()
builtin_lowercase = BuiltinLowercase() builtin_lowercase = BuiltinLowercase()
builtin_multiply = BuiltinMultiply() builtin_multiply = BuiltinMultiply()
builtin_not = BuiltinNot()
builtin_or = BuiltinOr()
builtin_print = BuiltinPrint() builtin_print = BuiltinPrint()
builtin_raw_field = BuiltinRaw_field() builtin_raw_field = BuiltinRaw_field()
builtin_re = BuiltinRe() builtin_re = BuiltinRe()