This commit is contained in:
GRiker 2010-07-21 06:23:08 -06:00
commit 621ab85e61
30 changed files with 7416 additions and 4963 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -6,6 +6,7 @@ class AdvancedUserRecipe1257302745(BasicNewsRecipe):
language = 'en'
__author__ = 'onyxrev'
max_articles_per_feed = 100
no_stylesheets = True
remove_tags_before = {'class':'storytitle'}
remove_tags_after = dict(name='div', attrs={'id':'storytext' })

View File

@ -295,11 +295,10 @@ class MetaInformation(object):
if val is not None:
setattr(self, attr, val)
if mi.tags:
if replace_metadata:
self.tags = mi.tags
else:
self.tags += mi.tags
if replace_metadata:
self.tags = mi.tags
elif mi.tags:
self.tags += mi.tags
self.tags = list(set(self.tags))
if mi.author_sort_map:
@ -313,14 +312,17 @@ class MetaInformation(object):
if len(other_cover) > len(self_cover):
self.cover_data = mi.cover_data
my_comments = getattr(self, 'comments', '')
other_comments = getattr(mi, 'comments', '')
if not my_comments:
my_comments = ''
if not other_comments:
other_comments = ''
if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments
if replace_metadata:
self.comments = getattr(mi, 'comments', '')
else:
my_comments = getattr(self, 'comments', '')
other_comments = getattr(mi, 'comments', '')
if not my_comments:
my_comments = ''
if not other_comments:
other_comments = ''
if len(other_comments.strip()) > len(my_comments.strip()):
self.comments = other_comments
other_lang = getattr(mi, 'language', None)
if other_lang and other_lang.lower() != 'und':

View File

@ -578,9 +578,7 @@ class DeleteAction(object): # {{{
if row is not None:
ci = view.model().index(row, 0)
if ci.isValid():
view.setCurrentIndex(ci)
sm = view.selectionModel()
sm.select(ci, sm.Select)
view.set_current_row(row)
else:
if not confirm('<p>'+_('The selected books will be '
'<b>permanently deleted</b> '

View File

@ -395,8 +395,6 @@ class DeviceAction(QAction): # {{{
class DeviceMenu(QMenu): # {{{
fetch_annotations = pyqtSignal()
connect_to_folder = pyqtSignal()
connect_to_itunes = pyqtSignal()
disconnect_mounted_device = pyqtSignal()
def __init__(self, parent=None):
@ -408,26 +406,6 @@ class DeviceMenu(QMenu): # {{{
self.set_default_menu = QMenu(_('Set default send to device action'))
self.set_default_menu.setIcon(QIcon(I('config.svg')))
opts = email_config().parse()
default_account = None
if opts.accounts:
self.email_to_menu = self.addMenu(_('Email to')+'...')
keys = sorted(opts.accounts.keys())
for account in keys:
formats, auto, default = opts.accounts[account]
dest = 'mail:'+account+';'+formats
if default:
default_account = (dest, False, False, I('mail.svg'),
_('Email to')+' '+account)
action1 = DeviceAction(dest, False, False, I('mail.svg'),
_('Email to')+' '+account)
action2 = DeviceAction(dest, True, False, I('mail.svg'),
_('Email to')+' '+account+ _(' and delete from library'))
map(self.email_to_menu.addAction, (action1, action2))
map(self._memory.append, (action1, action2))
self.email_to_menu.addSeparator()
action1.a_s.connect(self.action_triggered)
action2.a_s.connect(self.action_triggered)
basic_actions = [
('main:', False, False, I('reader.svg'),
@ -457,13 +435,6 @@ class DeviceMenu(QMenu): # {{{
]
if default_account is not None:
for x in (basic_actions, delete_actions):
ac = list(default_account)
if x is delete_actions:
ac[1] = True
x.insert(1, tuple(ac))
for menu in (self, self.set_default_menu):
for actions, desc in (
(basic_actions, ''),
@ -502,21 +473,7 @@ class DeviceMenu(QMenu): # {{{
config['default_send_to_device_action'] = repr(action)
self.group.triggered.connect(self.change_default_action)
if opts.accounts:
self.addSeparator()
self.addMenu(self.email_to_menu)
self.addSeparator()
mitem = self.addAction(QIcon(I('document_open.svg')), _('Connect to folder'))
mitem.setEnabled(True)
mitem.triggered.connect(lambda x : self.connect_to_folder.emit())
self.connect_to_folder_action = mitem
mitem = self.addAction(QIcon(I('devices/itunes.png')),
_('Connect to iTunes'))
mitem.setEnabled(True)
mitem.triggered.connect(lambda x : self.connect_to_itunes.emit())
self.connect_to_itunes_action = mitem
mitem = self.addAction(QIcon(I('eject.svg')), _('Eject device'))
mitem.setEnabled(False)
@ -638,6 +595,8 @@ class DeviceMixin(object): # {{{
self.device_error_dialog = error_dialog(self, _('Error'),
_('Error communicating with device'), ' ')
self.device_error_dialog.setModal(Qt.NonModal)
self.share_conn_menu.connect_to_folder.connect(self.connect_to_folder)
self.share_conn_menu.connect_to_itunes.connect(self.connect_to_itunes)
self.emailer = Emailer()
self.emailer.start()
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
@ -675,21 +634,20 @@ class DeviceMixin(object): # {{{
def create_device_menu(self):
self._sync_menu = DeviceMenu(self)
self.share_conn_menu.build_email_entries(self._sync_menu)
self.action_sync.setMenu(self._sync_menu)
self.connect(self._sync_menu,
SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self.dispatch_sync_event)
self._sync_menu.fetch_annotations.connect(self.fetch_annotations)
self._sync_menu.connect_to_folder.connect(self.connect_to_folder)
self._sync_menu.connect_to_itunes.connect(self.connect_to_itunes)
self._sync_menu.disconnect_mounted_device.connect(self.disconnect_mounted_device)
if self.device_connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
self._sync_menu.connect_to_itunes_action.setEnabled(False)
self.share_conn_menu.connect_to_folder_action.setEnabled(False)
self.share_conn_menu.connect_to_itunes_action.setEnabled(False)
self._sync_menu.disconnect_mounted_device_action.setEnabled(True)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.connect_to_itunes_action.setEnabled(True)
self.share_conn_menu.connect_to_folder_action.setEnabled(True)
self.share_conn_menu.connect_to_itunes_action.setEnabled(True)
self._sync_menu.disconnect_mounted_device_action.setEnabled(False)
def device_job_exception(self, job):
@ -726,16 +684,16 @@ class DeviceMixin(object): # {{{
def set_device_menu_items_state(self, connected):
if connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
self._sync_menu.connect_to_itunes_action.setEnabled(False)
self.share_conn_menu.connect_to_folder_action.setEnabled(False)
self.share_conn_menu.connect_to_itunes_action.setEnabled(False)
self._sync_menu.disconnect_mounted_device_action.setEnabled(True)
self._sync_menu.enable_device_actions(True,
self.device_manager.device.card_prefix(),
self.device_manager.device)
self.eject_action.setEnabled(True)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.connect_to_itunes_action.setEnabled(True)
self.share_conn_menu.connect_to_folder_action.setEnabled(True)
self.share_conn_menu.connect_to_itunes_action.setEnabled(True)
self._sync_menu.disconnect_mounted_device_action.setEnabled(False)
self._sync_menu.enable_device_actions(False)
self.eject_action.setEnabled(False)
@ -983,6 +941,8 @@ class DeviceMixin(object): # {{{
else:
self.status_bar.show_message(_('Sent by email:') + ', '.join(good),
5000)
if remove:
self.library_view.model().delete_books_by_id(remove)
def cover_to_thumbnail(self, data):
p = QPixmap()

View File

@ -10,7 +10,7 @@ import os
from PyQt4.Qt import QDialog
from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_dir, warning_dialog
from calibre.gui2 import error_dialog, choose_dir
from calibre.constants import filesystem_encoding
from calibre import isbytestring, patheq
from calibre.utils.config import prefs
@ -62,12 +62,6 @@ class ChooseLibrary(QDialog, Ui_Dialog):
return True
def perform_action(self, ac, loc):
if ac in ('new', 'existing'):
warning_dialog(self.parent(), _('Custom columns'),
_('If you use custom columns and they differ between '
'libraries, you will have various problems. Best '
'to ensure you have the same custom columns in each '
'library.'), show=True)
if ac in ('new', 'existing'):
prefs['library_path'] = loc
self.callback(loc)

View File

@ -195,22 +195,32 @@ class PluginModel(QAbstractItemModel):
class CategoryModel(QStringListModel):
CATEGORIES = [
('general', _('General'), 'dialog_information.svg'),
('interface', _('Interface'), 'lookfeel.svg'),
('conversion', _('Conversion'), 'convert.svg'),
('email', _('Email\nDelivery'), 'mail.svg'),
('add/save', _('Add/Save'), 'save.svg'),
('advanced', _('Advanced'), 'view.svg'),
('server', _('Content\nServer'), 'network-server.svg'),
('plugins', _('Plugins'), 'plugins.svg'),
]
def __init__(self, *args):
QStringListModel.__init__(self, *args)
self.setStringList([_('General'), _('Interface'), _('Conversion'),
_('Email\nDelivery'), _('Add/Save'),
_('Advanced'), _('Content\nServer'), _('Plugins')])
self.icons = list(map(QVariant, map(QIcon,
[I('dialog_information.svg'), I('lookfeel.svg'),
I('convert.svg'),
I('mail.svg'), I('save.svg'), I('view.svg'),
I('network-server.svg'), I('plugins.svg')])))
self.setStringList([x[1] for x in self.CATEGORIES])
def data(self, index, role):
if role == Qt.DecorationRole:
return self.icons[index.row()]
return QVariant(QIcon(I(self.CATEGORIES[index.row()][2])))
return QStringListModel.data(self, index, role)
def index_for_name(self, name):
for i, x in enumerate(self.CATEGORIES):
if x[0] == name:
return self.index(i)
return self.index(0)
class EmailAccounts(QAbstractTableModel):
def __init__(self, accounts):
@ -332,7 +342,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
def category_current_changed(self, n, p):
self.stackedWidget.setCurrentIndex(n.row())
def __init__(self, parent, library_view, server=None):
def __init__(self, parent, library_view, server=None,
initial_category='general'):
ResizableDialog.__init__(self, parent)
self._category_model = CategoryModel()
@ -461,7 +472,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.button_osx_symlinks.setVisible(isosx)
self.separate_cover_flow.setChecked(config['separate_cover_flow'])
self.setup_email_page()
self.category_view.setCurrentIndex(self.category_view.model().index(0))
self.delete_news.setEnabled(bool(self.sync_news.isChecked()))
self.connect(self.sync_news, SIGNAL('toggled(bool)'),
self.delete_news.setEnabled)
@ -488,6 +498,22 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.opt_gui_layout.setCurrentIndex(li)
self.opt_disable_animations.setChecked(config['disable_animations'])
self.opt_show_donate_button.setChecked(config['show_donate_button'])
idx = 0
for i, x in enumerate([(_('Small'), 'small'), (_('Medium'), 'medium'),
(_('Large'), 'large')]):
if x[1] == gprefs.get('toolbar_icon_size', 'medium'):
idx = i
self.opt_toolbar_icon_size.addItem(x[0], x[1])
self.opt_toolbar_icon_size.setCurrentIndex(idx)
idx = 0
for i, x in enumerate([(_('Automatic'), 'auto'), (_('Always'), 'always'),
(_('Never'), 'never')]):
if x[1] == gprefs.get('toolbar_text', 'auto'):
idx = i
self.opt_toolbar_text.addItem(x[0], x[1])
self.opt_toolbar_text.setCurrentIndex(idx)
self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category))
def check_port_value(self, *args):
port = self.port.value()
@ -857,6 +883,10 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
config['disable_animations'] = bool(self.opt_disable_animations.isChecked())
config['show_donate_button'] = bool(self.opt_show_donate_button.isChecked())
gprefs['show_splash_screen'] = bool(self.show_splash_screen.isChecked())
for x in ('toolbar_icon_size', 'toolbar_text'):
w = getattr(self, 'opt_'+x)
data = w.itemData(w.currentIndex()).toString()
gprefs[x] = unicode(data)
fmts = []
for i in range(self.viewer.count()):
if self.viewer.item(i).checkState() == Qt.Checked:
@ -942,6 +972,5 @@ if __name__ == '__main__':
from PyQt4.Qt import QApplication
app = QApplication([])
d=ConfigDialog(None, LibraryDatabase2('/tmp'))
d.category_view.setCurrentIndex(d.category_view.model().index(0))
d.show()
app.exec_()

View File

@ -346,21 +346,21 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="sync_news">
<property name="text">
<string>Automatically send downloaded &amp;news to ebook reader</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<item row="9" column="0" colspan="2">
<widget class="QCheckBox" name="delete_news">
<property name="text">
<string>&amp;Delete news from library when it is automatically sent to reader</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_6">
@ -377,7 +377,7 @@
</item>
</layout>
</item>
<item row="10" column="0" colspan="2">
<item row="11" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QGroupBox" name="groupBox">
@ -580,6 +580,41 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>&amp;Toolbar</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="opt_toolbar_icon_size"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Icon size:</string>
</property>
<property name="buddy">
<cstring>opt_toolbar_icon_size</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opt_toolbar_text"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Show &amp;text under icons:</string>
</property>
<property name="buddy">
<cstring>opt_toolbar_text</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_6">

View File

@ -25,8 +25,8 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor):
self.current_search_name = None
self.searches = {}
self.searches_to_delete = []
for name in saved_searches.names():
self.searches[name] = saved_searches.lookup(name)
for name in saved_searches().names():
self.searches[name] = saved_searches().lookup(name)
self.populate_search_list()
if initial_search is not None and initial_search in self.searches:
@ -78,7 +78,7 @@ class SavedSearchEditor(QDialog, Ui_SavedSearchEditor):
if self.current_search_name:
self.searches[self.current_search_name] = unicode(self.search_text.toPlainText())
for name in self.searches_to_delete:
saved_searches.delete(name)
saved_searches().delete(name)
for name in self.searches:
saved_searches.add(name, self.searches[name])
saved_searches().add(name, self.searches[name])
QDialog.accept(self)

View File

@ -62,6 +62,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.search_done)
self.disconnect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'),
self.search.search_done)
self.search.search.disconnect()
self.recipe_model = None
def search_done(self, *args):

View File

@ -7,7 +7,6 @@ from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QDialog, QIcon, QListWidgetItem
from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories
from calibre.utils.config import prefs
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.constants import islinux
@ -63,7 +62,7 @@ class TagCategories(QDialog, Ui_TagCategories):
self.all_items.append(t)
self.all_items_dict[label+':'+n] = t
self.categories = dict.copy(prefs['user_categories'])
self.categories = dict.copy(db.prefs.get('user_categories', {}))
if self.categories is None:
self.categories = {}
for cat in self.categories:
@ -182,7 +181,7 @@ class TagCategories(QDialog, Ui_TagCategories):
def accept(self):
self.save_category()
prefs['user_categories'] = self.categories
self.db.prefs['user_categories'] = self.categories
QDialog.accept(self)
def save_category(self):

View File

@ -59,6 +59,7 @@ class LibraryViewMixin(object): # {{{
self.action_open_containing_folder,
self.action_show_book_details,
self.action_del,
self.action_conn_share,
add_to_library = None,
edit_device_collections=None,
similar_menu=similar_menu)
@ -67,21 +68,24 @@ class LibraryViewMixin(object): # {{{
edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard=None))
self.memory_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None, self.action_del,
self.action_view, self.action_save, None, None,
self.action_del, None,
add_to_library=add_to_library,
edit_device_collections=edit_device_collections)
edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard='carda'))
self.card_a_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None, self.action_del,
self.action_view, self.action_save, None, None,
self.action_del, None,
add_to_library=add_to_library,
edit_device_collections=edit_device_collections)
edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard='cardb'))
self.card_b_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None, self.action_del,
self.action_view, self.action_save, None, None,
self.action_del, None,
add_to_library=add_to_library,
edit_device_collections=edit_device_collections)

View File

@ -16,14 +16,14 @@ from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, \
from calibre.constants import __appname__, isosx
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
from calibre.gui2.throbber import ThrobbingButton
from calibre.gui2 import config, open_url
from calibre.gui2 import config, open_url, gprefs
from calibre.gui2.widgets import ComboBoxWithHelp
from calibre import human_readable
from calibre.utils.config import prefs
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.utils.smtp import config as email_config
ICON_SIZE = 48
class SaveMenu(QMenu): # {{{
@ -228,12 +228,11 @@ class ToolBar(QToolBar): # {{{
self.setFloatable(False)
self.setOrientation(Qt.Horizontal)
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
self.donate = donate
self.apply_settings()
self.all_actions = actions
self.donate = donate
self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar)
self.d_widget = QWidget()
@ -242,6 +241,17 @@ class ToolBar(QToolBar): # {{{
donate.setAutoRaise(True)
donate.setCursor(Qt.PointingHandCursor)
self.build_bar()
self.preferred_width = self.sizeHint().width()
def apply_settings(self):
sz = gprefs.get('toolbar_icon_size', 'medium')
sz = {'small':24, 'medium':48, 'large':64}[sz]
self.setIconSize(QSize(sz, sz))
style = Qt.ToolButtonTextUnderIcon
if gprefs.get('toolbar_text', 'auto') == 'never':
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
self.donate.set_normal_icon_size(sz, sz)
def contextMenuEvent(self, *args):
pass
@ -262,7 +272,9 @@ class ToolBar(QToolBar): # {{{
ch.setCursor(Qt.PointingHandCursor)
ch.setAutoRaise(True)
if ac.menu() is not None:
ch.setPopupMode(ch.MenuButtonPopup)
name = getattr(ac, 'action_name', None)
ch.setPopupMode(ch.InstantPopup if name == 'conn_share'
else ch.MenuButtonPopup)
for x in actions:
self.addAction(x)
@ -292,11 +304,16 @@ class ToolBar(QToolBar): # {{{
a.setText(text)
def resizeEvent(self, ev):
style = Qt.ToolButtonTextUnderIcon
if self.size().width() < 1260:
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
QToolBar.resizeEvent(self, ev)
style = Qt.ToolButtonTextUnderIcon
p = gprefs.get('toolbar_text', 'auto')
if p == 'never':
style = Qt.ToolButtonIconOnly
if p == 'auto' and self.preferred_width > self.width()+35:
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
def database_changed(self, db):
pass
@ -306,6 +323,62 @@ class ToolBar(QToolBar): # {{{
class Action(QAction):
pass
class ShareConnMenu(QMenu): # {{{
connect_to_folder = pyqtSignal()
connect_to_itunes = pyqtSignal()
config_email = pyqtSignal()
def __init__(self, parent=None):
QMenu.__init__(self, parent)
mitem = self.addAction(QIcon(I('devices/folder.svg')), _('Connect to folder'))
mitem.setEnabled(True)
mitem.triggered.connect(lambda x : self.connect_to_folder.emit())
self.connect_to_folder_action = mitem
mitem = self.addAction(QIcon(I('devices/itunes.png')),
_('Connect to iTunes'))
mitem.setEnabled(True)
mitem.triggered.connect(lambda x : self.connect_to_itunes.emit())
self.connect_to_itunes_action = mitem
self.addSeparator()
self.email_actions = []
def build_email_entries(self, sync_menu):
from calibre.gui2.device import DeviceAction
for ac in self.email_actions:
self.removeAction(ac)
self.email_actions = []
opts = email_config().parse()
if opts.accounts:
self.email_to_menu = QMenu(_('Email to')+'...', self)
keys = sorted(opts.accounts.keys())
for account in keys:
formats, auto, default = opts.accounts[account]
dest = 'mail:'+account+';'+formats
action1 = DeviceAction(dest, False, False, I('mail.svg'),
_('Email to')+' '+account)
action2 = DeviceAction(dest, True, False, I('mail.svg'),
_('Email to')+' '+account+ _(' and delete from library'))
map(self.email_to_menu.addAction, (action1, action2))
if default:
map(self.addAction, (action1, action2))
map(self.email_actions.append, (action1, action2))
self.email_to_menu.addSeparator()
action1.a_s.connect(sync_menu.action_triggered)
action2.a_s.connect(sync_menu.action_triggered)
ac = self.addMenu(self.email_to_menu)
self.email_actions.append(ac)
else:
ac = self.addAction(_('Setup email based sharing of books'))
self.email_actions.append(ac)
ac.triggered.connect(self.setup_email)
def setup_email(self, *args):
self.config_email.emit()
# }}}
class MainWindowMixin(object):
def __init__(self, db):
@ -321,7 +394,6 @@ class MainWindowMixin(object):
self.centralwidget.setLayout(self._central_widget_layout)
self.resize(1012, 740)
self.donate_button = ThrobbingButton(self.centralwidget)
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
self.location_manager = LocationManager(self)
self.init_scheduler(db)
@ -341,7 +413,6 @@ class MainWindowMixin(object):
self.scheduler.start_recipe_fetch.connect(
self.download_scheduled_recipe, type=Qt.QueuedConnection)
def read_toolbar_settings(self):
pass
@ -372,18 +443,19 @@ class MainWindowMixin(object):
setattr(self, 'action_'+name, action)
all_actions.append(action)
ac(0, 7, 0, 'add', _('Add books'), 'add_book.svg', _('A'))
ac(0, 0, 0, 'add', _('Add books'), 'add_book.svg', _('A'))
ac(1, 1, 0, 'edit', _('Edit metadata'), 'edit_input.svg', _('E'))
ac(2, 2, 3, 'convert', _('Convert books'), 'convert.svg', _('C'))
ac(3, 3, 0, 'view', _('View'), 'view.svg', _('V'))
ac(4, 4, 3, 'choose_library', _('%d books')%0, 'lt.png',
ac(-1, 4, 0, 'sync', _('Send to device'), 'sync.svg')
ac(5, 5, 3, 'choose_library', _('%d books')%0, 'lt.png',
tooltip=_('Choose calibre library to work with'))
ac(5, 5, 3, 'news', _('Fetch news'), 'news.svg', _('F'))
ac(6, 6, 0, 'save', _('Save to disk'), 'save.svg', _('S'))
ac(7, 0, 0, 'sync', _('Send to device'), 'sync.svg')
ac(8, 8, 3, 'del', _('Remove books'), 'trash.svg', _('Del'))
ac(9, 9, 3, 'help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual"))
ac(10, 10, 0, 'preferences', _('Preferences'), 'config.svg', _('Ctrl+P'))
ac(6, 6, 3, 'news', _('Fetch news'), 'news.svg', _('F'))
ac(7, 7, 0, 'save', _('Save to disk'), 'save.svg', _('S'))
ac(8, 8, 0, 'conn_share', _('Connect/share'), 'connect_share.svg')
ac(9, 9, 3, 'del', _('Remove books'), 'trash.svg', _('Del'))
ac(10, 10, 3, 'help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual"))
ac(11, 11, 0, 'preferences', _('Preferences'), 'config.svg', _('Ctrl+P'))
ac(-1, -1, 0, 'merge', _('Merge book records'), 'merge_books.svg', _('M'))
ac(-1, -1, 0, 'open_containing_folder', _('Open containing folder'),
@ -402,6 +474,10 @@ class MainWindowMixin(object):
self.action_news.setMenu(self.scheduler.news_menu)
self.action_news.triggered.connect(
self.scheduler.show_dialog)
self.share_conn_menu = ShareConnMenu(self)
self.share_conn_menu.config_email.connect(partial(self.do_config,
initial_category='email'))
self.action_conn_share.setMenu(self.share_conn_menu)
self.action_help.triggered.connect(self.show_help)
md = QMenu()
@ -528,6 +604,7 @@ class MainWindowMixin(object):
for x in (self.preferences_action, self.action_preferences):
x.triggered.connect(self.do_config)
return all_actions
# }}}

View File

@ -214,13 +214,17 @@ class BooksView(QTableView): # {{{
state['column_sizes'][name] = h.sectionSize(i)
return state
def write_state(self, state):
db = getattr(self.model(), 'db', None)
name = unicode(self.objectName())
if name and db is not None:
db.prefs.set(name + ' books view state', state)
def save_state(self):
# Only save if we have been initialized (set_database called)
if len(self.column_map) > 0 and self.was_restored:
state = self.get_state()
name = unicode(self.objectName())
if name:
gprefs.set(name + ' books view state', state)
self.write_state(state)
def cleanup_sort_history(self, sort_history):
history = []
@ -298,11 +302,27 @@ class BooksView(QTableView): # {{{
old_state['column_sizes'][name] += 12
return old_state
def restore_state(self):
def get_old_state(self):
ans = None
name = unicode(self.objectName())
old_state = None
if name:
old_state = gprefs.get(name + ' books view state', None)
name += ' books view state'
db = getattr(self.model(), 'db', None)
if db is not None:
ans = db.prefs.get(name, None)
if ans is None:
ans = gprefs.get(name, None)
try:
del gprefs[name]
except:
pass
if ans is not None:
db.prefs[name] = ans
return ans
def restore_state(self):
old_state = self.get_old_state()
if old_state is None:
old_state = self.get_default_state()
@ -370,7 +390,7 @@ class BooksView(QTableView): # {{{
# Context Menu {{{
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, delete,
save, open_folder, book_details, delete, conn_share,
similar_menu=None, add_to_library=None,
edit_device_collections=None):
self.setContextMenuPolicy(Qt.DefaultContextMenu)
@ -381,6 +401,8 @@ class BooksView(QTableView): # {{{
self.context_menu.addAction(send_to_device)
if convert is not None:
self.context_menu.addAction(convert)
if conn_share is not None:
self.context_menu.addAction(conn_share)
self.context_menu.addAction(view)
self.context_menu.addAction(save)
if open_folder is not None:
@ -507,6 +529,19 @@ class DeviceBooksView(BooksView): # {{{
self.context_menu.popup(event.globalPos())
event.accept()
def get_old_state(self):
ans = None
name = unicode(self.objectName())
if name:
name += ' books view state'
ans = gprefs.get(name, None)
return ans
def write_state(self, state):
name = unicode(self.objectName())
if name:
gprefs.set(name + ' books view state', state)
def set_database(self, db):
self._model.set_database(db)
self.restore_state()

View File

@ -10,13 +10,12 @@ import re
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \
pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \
QAction, QKeySequence
QAction, QKeySequence, QTimer
from calibre.gui2 import config
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
from calibre.gui2.dialogs.search import SearchDialog
from calibre.utils.config import prefs
from calibre.utils.search_query_parser import saved_searches
class SearchLineEdit(QLineEdit):
@ -83,7 +82,9 @@ class SearchBox2(QComboBox):
self.help_state = False
self.as_you_type = True
self.prev_search = ''
self.timer = None
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.timer_event, type=Qt.QueuedConnection)
self.setInsertPolicy(self.NoInsert)
self.setMaxCount(self.MAX_COUNT)
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
@ -117,9 +118,6 @@ class SearchBox2(QComboBox):
self.search.emit('')
self._in_a_search = False
self.setEditText(self.help_text)
if self.timer is not None: # Turn off any timers that got started in setEditText
self.killTimer(self.timer)
self.timer = None
self.line_edit.home(False)
self.line_edit.setStyleSheet(
'QLineEdit { color: gray; background-color: %s; }' %
@ -148,18 +146,15 @@ class SearchBox2(QComboBox):
self._in_a_search = False
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
self.do_search()
self.timer = self.startTimer(self.__class__.INTERVAL)
self.timer.start(1500)
def mouse_released(self, event):
self.normalize_state()
if self.as_you_type:
self.timer = self.startTimer(self.__class__.INTERVAL)
self.timer.start(1500)
def timerEvent(self, event):
self.killTimer(event.timerId())
if event.timerId() == self.timer:
self.timer = None
self.do_search()
def timer_event(self):
self.do_search()
def history_selected(self, text):
self.emit(SIGNAL('changed()'))
@ -213,9 +208,6 @@ class SearchBox2(QComboBox):
return
self.normalize_state()
self.setEditText(txt)
if self.timer is not None: # Turn off any timers that got started in setEditText
self.killTimer(self.timer)
self.timer = None
self.search.emit(txt)
self.line_edit.end(False)
self.initial_state = False
@ -259,8 +251,7 @@ class SavedSearchBox(QComboBox):
self.setMinimumContentsLength(10)
self.tool_tip_text = self.toolTip()
def initialize(self, _saved_searches, _search_box, colorize=False, help_text=_('Search')):
self.saved_searches = _saved_searches
def initialize(self, _search_box, colorize=False, help_text=_('Search')):
self.search_box = _search_box
self.help_text = help_text
self.colorize = colorize
@ -302,11 +293,11 @@ class SavedSearchBox(QComboBox):
self.normalize_state()
self.search_box.set_search_string(u'search:"%s"' % qname)
self.setEditText(qname)
self.setToolTip(self.saved_searches.lookup(qname))
self.setToolTip(saved_searches().lookup(qname))
def initialize_saved_search_names(self):
self.clear()
qnames = self.saved_searches.names()
qnames = saved_searches().names()
self.addItems(qnames)
self.setCurrentIndex(-1)
@ -319,10 +310,10 @@ class SavedSearchBox(QComboBox):
idx = self.currentIndex
if idx < 0:
return
ss = self.saved_searches.lookup(unicode(self.currentText()))
ss = saved_searches().lookup(unicode(self.currentText()))
if ss is None:
return
self.saved_searches.delete(unicode(self.currentText()))
saved_searches().delete(unicode(self.currentText()))
self.clear_to_help()
self.search_box.clear_to_help()
self.emit(SIGNAL('changed()'))
@ -332,8 +323,8 @@ class SavedSearchBox(QComboBox):
name = unicode(self.currentText())
if self.help_state or not name.strip():
name = unicode(self.search_box.text()).replace('"', '')
self.saved_searches.delete(name)
self.saved_searches.add(name, unicode(self.search_box.text()))
saved_searches().delete(name)
saved_searches().add(name, unicode(self.search_box.text()))
# now go through an initialization cycle to ensure that the combobox has
# the new search in it, that it is selected, and that the search box
# references the new search instead of the text in the search.
@ -348,7 +339,7 @@ class SavedSearchBox(QComboBox):
idx = self.currentIndex();
if idx < 0:
return
self.search_box.set_search_string(self.saved_searches.lookup(unicode(self.currentText())))
self.search_box.set_search_string(saved_searches().lookup(unicode(self.currentText())))
class SearchBoxMixin(object):
@ -390,11 +381,12 @@ class SearchBoxMixin(object):
class SavedSearchBoxMixin(object):
def __init__(self):
def __init__(self, db):
self.db = db
self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed)
self.saved_searches_changed()
self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help)
self.saved_search.initialize(saved_searches, self.search, colorize=True,
self.saved_search.initialize(self.search, colorize=True,
help_text=_('Saved Searches'))
self.connect(self.save_search_button, SIGNAL('clicked()'),
self.saved_search.save_search_button_clicked)
@ -409,9 +401,12 @@ class SavedSearchBoxMixin(object):
b = getattr(self, x+'_search_button')
b.setStatusTip(b.toolTip())
def set_database(self, db):
self.db = db
self.saved_searches_changed()
def saved_searches_changed(self):
p = prefs['saved_searches'].keys()
p = saved_searches().names()
p.sort()
t = unicode(self.search_restriction.currentText())
self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches

View File

@ -17,7 +17,6 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
from calibre.ebooks.metadata import title_sort
from calibre.gui2 import config, NONE
from calibre.utils.config import prefs
from calibre.library.field_metadata import TagsIcons
from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog
@ -224,7 +223,7 @@ class TagsView(QTreeView): # {{{
# Always show the user categories editor
self.context_menu.addSeparator()
if category in prefs['user_categories'].keys():
if category in self.db.prefs.get('user_categories', {}).keys():
self.context_menu.addAction(_('Manage User Categories'),
partial(self.context_menu_handler, action='manage_categories',
category=category))
@ -426,10 +425,10 @@ class TagsModel(QAbstractItemModel): # {{{
for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k]
for user_cat in sorted(prefs['user_categories'].keys()):
for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys()):
cat_name = user_cat+':' # add the ':' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat)
if len(saved_searches.names()):
if len(saved_searches().names()):
tb_cats.add_search_category(label='search', name=_('Searches'))
# Now get the categories
@ -507,11 +506,11 @@ class TagsModel(QAbstractItemModel): # {{{
if key not in self.db.field_metadata:
return
if key == 'search':
if val in saved_searches.names():
if val in saved_searches().names():
error_dialog(self.tags_view, _('Duplicate search name'),
_('The saved search name %s is already used.')%val).exec_()
return False
saved_searches.rename(unicode(item.data(role).toString()), val)
saved_searches().rename(unicode(item.data(role).toString()), val)
self.tags_view.search_item_renamed.emit()
else:
if key == 'series':

View File

@ -199,7 +199,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
UpdateMixin.__init__(self, opts)
####################### Search boxes ########################
SavedSearchBoxMixin.__init__(self)
SavedSearchBoxMixin.__init__(self, db)
SearchBoxMixin.__init__(self)
####################### Library view ########################
@ -351,7 +351,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db
def do_config(self, *args):
def do_config(self, checked=False, initial_category='general'):
if self.job_manager.has_jobs():
d = error_dialog(self, _('Cannot configure'),
_('Cannot configure while there are running jobs.'))
@ -363,7 +363,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
d.exec_()
return
d = ConfigDialog(self, self.library_view,
server=self.content_server)
server=self.content_server, initial_category=initial_category)
d.exec_()
self.content_server = d.server
@ -380,6 +380,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.tags_view.recount()
self.create_device_menu()
self.set_device_menu_items_state(bool(self.device_connected))
self.tool_bar.apply_settings()
def library_moved(self, newloc):
if newloc is None: return
@ -392,6 +393,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
self.library_view.model().set_book_on_device_func(self.book_on_device)
self.status_bar.clear_message()
self.search.clear_to_help()
self.saved_search.clear_to_help()
self.book_details.reset_info()
self.library_view.model().count_changed()
self.scheduler.database_changed(db)

View File

@ -448,7 +448,7 @@ class DocumentView(QWebView):
self.unimplemented_actions = list(map(self.pageAction,
[d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk,
d.OpenImageInNewWindow, d.OpenLink]))
self.dictionary_action = QAction(QIcon(I('dictionary.png')),
self.dictionary_action = QAction(QIcon(I('dictionary.svg')),
_('&Lookup in dictionary'), self)
self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L)
self.dictionary_action.triggered.connect(self.lookup)

View File

@ -19,6 +19,7 @@ from calibre.library.schema_upgrades import SchemaUpgrade
from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.library.prefs import DBPrefs
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
@ -29,7 +30,7 @@ from calibre.customize.ui import run_plugins_on_import
from calibre.utils.filenames import ascii_filename
from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
from calibre.utils.config import prefs, tweaks
from calibre.utils.search_query_parser import saved_searches
from calibre.utils.search_query_parser import saved_searches, set_saved_searches
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
from calibre.utils.magick_draw import save_cover_data_to
@ -140,6 +141,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.initialize_dynamic()
def initialize_dynamic(self):
self.prefs = DBPrefs(self)
# Migrate saved search and user categories to db preference scheme
def migrate_preference(key, default):
oldval = prefs[key]
if oldval != default:
self.prefs[key] = oldval
prefs[key] = default
if key not in self.prefs:
self.prefs[key] = default
migrate_preference('user_categories', {})
migrate_preference('saved_searches', {})
set_saved_searches(self, 'saved_searches')
self.conn.executescript('''
DROP TRIGGER IF EXISTS author_insert_trg;
CREATE TEMP TRIGGER author_insert_trg
@ -268,10 +284,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k]
for user_cat in sorted(prefs['user_categories'].keys()):
for user_cat in sorted(self.prefs.get('user_categories', {}).keys()):
cat_name = user_cat+':' # add the ':' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat)
if len(saved_searches.names()):
if len(saved_searches().names()):
tb_cats.add_search_category(label='search', name=_('Searches'))
self.book_on_device_func = None
@ -843,7 +859,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
categories['formats'].sort(key = lambda x:x.name)
#### Now do the user-defined categories. ####
user_categories = prefs['user_categories']
user_categories = self.prefs['user_categories']
# We want to use same node in the user category as in the source
# category. To do that, we need to find the original Tag node. There is
@ -880,8 +896,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
icon = None
if icon_map and 'search' in icon_map:
icon = icon_map['search']
for srch in saved_searches.names():
items.append(Tag(srch, tooltip=saved_searches.lookup(srch), icon=icon))
for srch in saved_searches().names():
items.append(Tag(srch, tooltip=saved_searches().lookup(srch), icon=icon))
if len(items):
if icon_map is not None:
icon_map['search'] = icon_map['search']

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import json
from calibre.constants import preferred_encoding
from calibre.utils.config import to_json, from_json
class DBPrefs(dict):
def __init__(self, db):
dict.__init__(self)
self.db = db
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
val = self.raw_to_object(val)
dict.__setitem__(self, key, val)
def raw_to_object(self, raw):
if not isinstance(raw, unicode):
raw = raw.decode(preferred_encoding)
return json.loads(raw, object_hook=from_json)
def to_raw(self, val):
return json.dumps(val, indent=2, default=to_json)
def __getitem__(self, key):
return dict.__getitem__(self, key)
def __delitem__(self, key):
dict.__delitem__(self, key)
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
self.db.conn.commit()
def __setitem__(self, key, val):
raw = self.to_raw(val)
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,
raw))
self.db.conn.commit()
dict.__setitem__(self, key, val)
def set(self, key, val):
self.__setitem__(key, val)

View File

@ -387,3 +387,13 @@ class SchemaUpgrade(object):
self.conn.execute('UPDATE authors SET sort=author_to_author_sort(name)')
def upgrade_version_12(self):
'DB based preference store'
script = '''
DROP TABLE IF EXISTS preferences;
CREATE TABLE preferences(id INTEGER PRIMARY KEY,
key TEXT NON NULL,
val TEXT NON NULL,
UNIQUE(key));
'''
self.conn.executescript(script)

View File

@ -349,7 +349,7 @@ table of contents, check the :guilabel:`Do not add detected chapters` option.
If less than the :guilabel:`Chapter threshold` number of chapters were detected, |app| will then add any hyperlinks
it finds in the input document to the Table of Contents. This often works well many input documents include a
hyperlinked Table of Contents right at the start. The :guilabel:`Number fo links` option can be used to control
hyperlinked Table of Contents right at the start. The :guilabel:`Number of links` option can be used to control
this behavior. If set to zero, no links are added. If set to a number greater than zero, at most that number of links
is added.

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,7 @@ and save it to a new file.
"""
import ctypes, sys, os
import ctypes, sys, os, glob
from ctypes import util
iswindows = 'win32' in sys.platform or 'win64' in sys.platform
isosx = 'darwin' in sys.platform
@ -85,7 +85,8 @@ elif iswindows:
_lib = flib if isfrozen else 'CORE_RL_wand_'
else:
if isfrozen:
_lib = os.path.join(sys.frozen_path, 'libMagickWand.so.2')
_lib = glob.glob(os.path.join(sys.frozen_path,
'libMagickWand.so.*'))[-1]
else:
_lib = util.find_library('MagickWand')
if _lib is None:

View File

@ -6,15 +6,16 @@ __docformat__ = 'restructuredtext en'
'''
Manage application-wide preferences.
'''
import os, re, cPickle, textwrap, traceback, plistlib, json, base64
import os, re, cPickle, textwrap, traceback, plistlib, json, base64, datetime
from copy import deepcopy
from functools import partial
from optparse import OptionParser as _OptionParser
from optparse import IndentedHelpFormatter
from collections import defaultdict
from calibre.constants import terminal_controller, iswindows, isosx, \
__appname__, __version__, __author__, plugins
from calibre.utils.lock import LockError, ExclusiveFile
from collections import defaultdict
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY'])
@ -632,27 +633,34 @@ class XMLConfig(dict):
f.truncate()
f.write(raw)
def to_json(obj):
if isinstance(obj, bytearray):
return {'__class__': 'bytearray',
'__value__': base64.standard_b64encode(bytes(obj))}
if isinstance(obj, datetime.datetime):
from calibre.utils.date import isoformat
return {'__class__': 'datetime.datetime',
'__value__': isoformat(obj, as_utc=True)}
raise TypeError(repr(obj) + ' is not JSON serializable')
def from_json(obj):
if '__class__' in obj:
if obj['__class__'] == 'bytearray':
return bytearray(base64.standard_b64decode(obj['__value__']))
if obj['__class__'] == 'datetime.datetime':
from calibre.utils.date import parse_date
return parse_date(obj['__value__'], assume_utc=True)
return obj
class JSONConfig(XMLConfig):
EXTENSION = '.json'
def to_json(self, obj):
if isinstance(obj, bytearray):
return {'__class__': 'bytearray',
'__value__': base64.standard_b64encode(bytes(obj))}
raise TypeError(repr(obj) + ' is not JSON serializable')
def from_json(self, obj):
if '__class__' in obj:
if obj['__class__'] == 'bytearray':
return bytearray(base64.standard_b64decode(obj['__value__']))
return obj
def raw_to_object(self, raw):
return json.loads(raw.decode('utf-8'), object_hook=self.from_json)
return json.loads(raw.decode('utf-8'), object_hook=from_json)
def to_raw(self):
return json.dumps(self, indent=2, default=self.to_json)
return json.dumps(self, indent=2, default=to_json)
def __getitem__(self, key):
return dict.__getitem__(self, key)

View File

@ -42,6 +42,8 @@ def get_lang():
lang = match.group()
if lang == 'zh':
lang = 'zh_CN'
if lang is None:
lang = 'en'
return lang
def messages_path(lang):

View File

@ -21,7 +21,6 @@ import sys, string, operator
from calibre.utils.pyparsing import Keyword, Group, Forward, CharsNotIn, Suppress, \
OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException
from calibre.constants import preferred_encoding
from calibre.utils.config import prefs
'''
This class manages access to the preference holding the saved search queries.
@ -32,9 +31,13 @@ class SavedSearchQueries(object):
queries = {}
opt_name = ''
def __init__(self, _opt_name):
def __init__(self, db, _opt_name):
self.opt_name = _opt_name;
self.queries = prefs[self.opt_name]
self.db = db
if db is not None:
self.queries = db.prefs.get(self.opt_name, {})
else:
self.queries = {}
def force_unicode(self, x):
if not isinstance(x, unicode):
@ -43,20 +46,20 @@ class SavedSearchQueries(object):
def add(self, name, value):
self.queries[self.force_unicode(name)] = self.force_unicode(value).strip()
prefs[self.opt_name] = self.queries
self.db.prefs[self.opt_name] = self.queries
def lookup(self, name):
return self.queries.get(self.force_unicode(name), None)
def delete(self, name):
self.queries.pop(self.force_unicode(name), False)
prefs[self.opt_name] = self.queries
self.db.prefs[self.opt_name] = self.queries
def rename(self, old_name, new_name):
self.queries[self.force_unicode(new_name)] = \
self.queries.get(self.force_unicode(old_name), None)
self.queries.pop(self.force_unicode(old_name), False)
prefs[self.opt_name] = self.queries
self.db.prefs[self.opt_name] = self.queries
def names(self):
return sorted(self.queries.keys(),
@ -66,8 +69,15 @@ class SavedSearchQueries(object):
Create a global instance of the saved searches. It is global so that the searches
are common across all instances of the parser (devices, library, etc).
'''
saved_searches = SavedSearchQueries('saved_searches')
ss = SavedSearchQueries(None, None)
def set_saved_searches(db, opt_name):
global ss
ss = SavedSearchQueries(db, opt_name)
def saved_searches():
global ss
return ss
class SearchQueryParser(object):
'''
@ -209,7 +219,7 @@ class SearchQueryParser(object):
raise ParseException(query, len(query), 'undefined saved search', self)
if self.recurse_level > 5:
self.searches_seen.add(query)
return self._parse(saved_searches.lookup(query))
return self._parse(saved_searches().lookup(query))
except: # convert all exceptions (e.g., missing key) to a parse error
raise ParseException(query, len(query), 'undefined saved search', self)
return self.get_matches(location, query)