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' language = 'en'
__author__ = 'onyxrev' __author__ = 'onyxrev'
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True
remove_tags_before = {'class':'storytitle'} remove_tags_before = {'class':'storytitle'}
remove_tags_after = dict(name='div', attrs={'id':'storytext' }) remove_tags_after = dict(name='div', attrs={'id':'storytext' })

View File

@ -295,10 +295,9 @@ class MetaInformation(object):
if val is not None: if val is not None:
setattr(self, attr, val) setattr(self, attr, val)
if mi.tags:
if replace_metadata: if replace_metadata:
self.tags = mi.tags self.tags = mi.tags
else: elif mi.tags:
self.tags += mi.tags self.tags += mi.tags
self.tags = list(set(self.tags)) self.tags = list(set(self.tags))
@ -313,6 +312,9 @@ class MetaInformation(object):
if len(other_cover) > len(self_cover): if len(other_cover) > len(self_cover):
self.cover_data = mi.cover_data self.cover_data = mi.cover_data
if replace_metadata:
self.comments = getattr(mi, 'comments', '')
else:
my_comments = getattr(self, 'comments', '') my_comments = getattr(self, 'comments', '')
other_comments = getattr(mi, 'comments', '') other_comments = getattr(mi, 'comments', '')
if not my_comments: if not my_comments:

View File

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

View File

@ -395,8 +395,6 @@ class DeviceAction(QAction): # {{{
class DeviceMenu(QMenu): # {{{ class DeviceMenu(QMenu): # {{{
fetch_annotations = pyqtSignal() fetch_annotations = pyqtSignal()
connect_to_folder = pyqtSignal()
connect_to_itunes = pyqtSignal()
disconnect_mounted_device = pyqtSignal() disconnect_mounted_device = pyqtSignal()
def __init__(self, parent=None): 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 = QMenu(_('Set default send to device action'))
self.set_default_menu.setIcon(QIcon(I('config.svg'))) 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 = [ basic_actions = [
('main:', False, False, I('reader.svg'), ('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 menu in (self, self.set_default_menu):
for actions, desc in ( for actions, desc in (
(basic_actions, ''), (basic_actions, ''),
@ -502,21 +473,7 @@ class DeviceMenu(QMenu): # {{{
config['default_send_to_device_action'] = repr(action) config['default_send_to_device_action'] = repr(action)
self.group.triggered.connect(self.change_default_action) self.group.triggered.connect(self.change_default_action)
if opts.accounts:
self.addSeparator() 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 = self.addAction(QIcon(I('eject.svg')), _('Eject device'))
mitem.setEnabled(False) mitem.setEnabled(False)
@ -638,6 +595,8 @@ class DeviceMixin(object): # {{{
self.device_error_dialog = error_dialog(self, _('Error'), self.device_error_dialog = error_dialog(self, _('Error'),
_('Error communicating with device'), ' ') _('Error communicating with device'), ' ')
self.device_error_dialog.setModal(Qt.NonModal) 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 = Emailer()
self.emailer.start() self.emailer.start()
self.device_manager = DeviceManager(Dispatcher(self.device_detected), self.device_manager = DeviceManager(Dispatcher(self.device_detected),
@ -675,21 +634,20 @@ class DeviceMixin(object): # {{{
def create_device_menu(self): def create_device_menu(self):
self._sync_menu = DeviceMenu(self) self._sync_menu = DeviceMenu(self)
self.share_conn_menu.build_email_entries(self._sync_menu)
self.action_sync.setMenu(self._sync_menu) self.action_sync.setMenu(self._sync_menu)
self.connect(self._sync_menu, self.connect(self._sync_menu,
SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self.dispatch_sync_event) self.dispatch_sync_event)
self._sync_menu.fetch_annotations.connect(self.fetch_annotations) 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) self._sync_menu.disconnect_mounted_device.connect(self.disconnect_mounted_device)
if self.device_connected: if self.device_connected:
self._sync_menu.connect_to_folder_action.setEnabled(False) self.share_conn_menu.connect_to_folder_action.setEnabled(False)
self._sync_menu.connect_to_itunes_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.disconnect_mounted_device_action.setEnabled(True)
else: else:
self._sync_menu.connect_to_folder_action.setEnabled(True) self.share_conn_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.connect_to_itunes_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.disconnect_mounted_device_action.setEnabled(False)
def device_job_exception(self, job): def device_job_exception(self, job):
@ -726,16 +684,16 @@ class DeviceMixin(object): # {{{
def set_device_menu_items_state(self, connected): def set_device_menu_items_state(self, connected):
if connected: if connected:
self._sync_menu.connect_to_folder_action.setEnabled(False) self.share_conn_menu.connect_to_folder_action.setEnabled(False)
self._sync_menu.connect_to_itunes_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.disconnect_mounted_device_action.setEnabled(True)
self._sync_menu.enable_device_actions(True, self._sync_menu.enable_device_actions(True,
self.device_manager.device.card_prefix(), self.device_manager.device.card_prefix(),
self.device_manager.device) self.device_manager.device)
self.eject_action.setEnabled(True) self.eject_action.setEnabled(True)
else: else:
self._sync_menu.connect_to_folder_action.setEnabled(True) self.share_conn_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.connect_to_itunes_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.disconnect_mounted_device_action.setEnabled(False)
self._sync_menu.enable_device_actions(False) self._sync_menu.enable_device_actions(False)
self.eject_action.setEnabled(False) self.eject_action.setEnabled(False)
@ -983,6 +941,8 @@ class DeviceMixin(object): # {{{
else: else:
self.status_bar.show_message(_('Sent by email:') + ', '.join(good), self.status_bar.show_message(_('Sent by email:') + ', '.join(good),
5000) 5000)
if remove:
self.library_view.model().delete_books_by_id(remove)
def cover_to_thumbnail(self, data): def cover_to_thumbnail(self, data):
p = QPixmap() p = QPixmap()

View File

@ -10,7 +10,7 @@ import os
from PyQt4.Qt import QDialog from PyQt4.Qt import QDialog
from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog 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.constants import filesystem_encoding
from calibre import isbytestring, patheq from calibre import isbytestring, patheq
from calibre.utils.config import prefs from calibre.utils.config import prefs
@ -62,12 +62,6 @@ class ChooseLibrary(QDialog, Ui_Dialog):
return True return True
def perform_action(self, ac, loc): 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'): if ac in ('new', 'existing'):
prefs['library_path'] = loc prefs['library_path'] = loc
self.callback(loc) self.callback(loc)

View File

@ -195,22 +195,32 @@ class PluginModel(QAbstractItemModel):
class CategoryModel(QStringListModel): 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): def __init__(self, *args):
QStringListModel.__init__(self, *args) QStringListModel.__init__(self, *args)
self.setStringList([_('General'), _('Interface'), _('Conversion'), self.setStringList([x[1] for x in self.CATEGORIES])
_('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')])))
def data(self, index, role): def data(self, index, role):
if role == Qt.DecorationRole: if role == Qt.DecorationRole:
return self.icons[index.row()] return QVariant(QIcon(I(self.CATEGORIES[index.row()][2])))
return QStringListModel.data(self, index, role) 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): class EmailAccounts(QAbstractTableModel):
def __init__(self, accounts): def __init__(self, accounts):
@ -332,7 +342,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
def category_current_changed(self, n, p): def category_current_changed(self, n, p):
self.stackedWidget.setCurrentIndex(n.row()) 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) ResizableDialog.__init__(self, parent)
self._category_model = CategoryModel() self._category_model = CategoryModel()
@ -461,7 +472,6 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.button_osx_symlinks.setVisible(isosx) self.button_osx_symlinks.setVisible(isosx)
self.separate_cover_flow.setChecked(config['separate_cover_flow']) self.separate_cover_flow.setChecked(config['separate_cover_flow'])
self.setup_email_page() self.setup_email_page()
self.category_view.setCurrentIndex(self.category_view.model().index(0))
self.delete_news.setEnabled(bool(self.sync_news.isChecked())) self.delete_news.setEnabled(bool(self.sync_news.isChecked()))
self.connect(self.sync_news, SIGNAL('toggled(bool)'), self.connect(self.sync_news, SIGNAL('toggled(bool)'),
self.delete_news.setEnabled) self.delete_news.setEnabled)
@ -488,6 +498,22 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.opt_gui_layout.setCurrentIndex(li) self.opt_gui_layout.setCurrentIndex(li)
self.opt_disable_animations.setChecked(config['disable_animations']) self.opt_disable_animations.setChecked(config['disable_animations'])
self.opt_show_donate_button.setChecked(config['show_donate_button']) 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): def check_port_value(self, *args):
port = self.port.value() port = self.port.value()
@ -857,6 +883,10 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
config['disable_animations'] = bool(self.opt_disable_animations.isChecked()) config['disable_animations'] = bool(self.opt_disable_animations.isChecked())
config['show_donate_button'] = bool(self.opt_show_donate_button.isChecked()) config['show_donate_button'] = bool(self.opt_show_donate_button.isChecked())
gprefs['show_splash_screen'] = bool(self.show_splash_screen.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 = [] fmts = []
for i in range(self.viewer.count()): for i in range(self.viewer.count()):
if self.viewer.item(i).checkState() == Qt.Checked: if self.viewer.item(i).checkState() == Qt.Checked:
@ -942,6 +972,5 @@ if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication
app = QApplication([]) app = QApplication([])
d=ConfigDialog(None, LibraryDatabase2('/tmp')) d=ConfigDialog(None, LibraryDatabase2('/tmp'))
d.category_view.setCurrentIndex(d.category_view.model().index(0))
d.show() d.show()
app.exec_() app.exec_()

View File

@ -346,21 +346,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2"> <item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="sync_news"> <widget class="QCheckBox" name="sync_news">
<property name="text"> <property name="text">
<string>Automatically send downloaded &amp;news to ebook reader</string> <string>Automatically send downloaded &amp;news to ebook reader</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="0" colspan="2"> <item row="9" column="0" colspan="2">
<widget class="QCheckBox" name="delete_news"> <widget class="QCheckBox" name="delete_news">
<property name="text"> <property name="text">
<string>&amp;Delete news from library when it is automatically sent to reader</string> <string>&amp;Delete news from library when it is automatically sent to reader</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0" colspan="2"> <item row="10" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
@ -377,7 +377,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="10" column="0" colspan="2"> <item row="11" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_7"> <layout class="QHBoxLayout" name="horizontalLayout_7">
<item> <item>
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
@ -580,6 +580,41 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
<widget class="QWidget" name="page_6"> <widget class="QWidget" name="page_6">

View File

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

View File

@ -62,6 +62,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.search_done) self.search_done)
self.disconnect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), self.disconnect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'),
self.search.search_done) self.search.search_done)
self.search.search.disconnect()
self.recipe_model = None self.recipe_model = None
def search_done(self, *args): 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 PyQt4.QtGui import QDialog, QIcon, QListWidgetItem
from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories 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.gui2.dialogs.confirm_delete import confirm
from calibre.constants import islinux from calibre.constants import islinux
@ -63,7 +62,7 @@ class TagCategories(QDialog, Ui_TagCategories):
self.all_items.append(t) self.all_items.append(t)
self.all_items_dict[label+':'+n] = 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: if self.categories is None:
self.categories = {} self.categories = {}
for cat in self.categories: for cat in self.categories:
@ -182,7 +181,7 @@ class TagCategories(QDialog, Ui_TagCategories):
def accept(self): def accept(self):
self.save_category() self.save_category()
prefs['user_categories'] = self.categories self.db.prefs['user_categories'] = self.categories
QDialog.accept(self) QDialog.accept(self)
def save_category(self): def save_category(self):

View File

@ -59,6 +59,7 @@ class LibraryViewMixin(object): # {{{
self.action_open_containing_folder, self.action_open_containing_folder,
self.action_show_book_details, self.action_show_book_details,
self.action_del, self.action_del,
self.action_conn_share,
add_to_library = None, add_to_library = None,
edit_device_collections=None, edit_device_collections=None,
similar_menu=similar_menu) similar_menu=similar_menu)
@ -67,21 +68,24 @@ class LibraryViewMixin(object): # {{{
edit_device_collections = (_('Manage collections'), edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard=None)) partial(self.edit_device_collections, oncard=None))
self.memory_view.set_context_menu(None, None, 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, add_to_library=add_to_library,
edit_device_collections=edit_device_collections) edit_device_collections=edit_device_collections)
edit_device_collections = (_('Manage collections'), edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard='carda')) partial(self.edit_device_collections, oncard='carda'))
self.card_a_view.set_context_menu(None, None, None, 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, add_to_library=add_to_library,
edit_device_collections=edit_device_collections) edit_device_collections=edit_device_collections)
edit_device_collections = (_('Manage collections'), edit_device_collections = (_('Manage collections'),
partial(self.edit_device_collections, oncard='cardb')) partial(self.edit_device_collections, oncard='cardb'))
self.card_b_view.set_context_menu(None, None, None, 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, add_to_library=add_to_library,
edit_device_collections=edit_device_collections) 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.constants import __appname__, isosx
from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.search_box import SearchBox2, SavedSearchBox
from calibre.gui2.throbber import ThrobbingButton 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.gui2.widgets import ComboBoxWithHelp
from calibre import human_readable from calibre import human_readable
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.utils.smtp import config as email_config
ICON_SIZE = 48
class SaveMenu(QMenu): # {{{ class SaveMenu(QMenu): # {{{
@ -228,12 +228,11 @@ class ToolBar(QToolBar): # {{{
self.setFloatable(False) self.setFloatable(False)
self.setOrientation(Qt.Horizontal) self.setOrientation(Qt.Horizontal)
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) 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.setStyleSheet('QToolButton:checked { font-weight: bold }')
self.donate = donate
self.apply_settings()
self.all_actions = actions self.all_actions = actions
self.donate = donate
self.location_manager = location_manager self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar) self.location_manager.locations_changed.connect(self.build_bar)
self.d_widget = QWidget() self.d_widget = QWidget()
@ -242,6 +241,17 @@ class ToolBar(QToolBar): # {{{
donate.setAutoRaise(True) donate.setAutoRaise(True)
donate.setCursor(Qt.PointingHandCursor) donate.setCursor(Qt.PointingHandCursor)
self.build_bar() 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): def contextMenuEvent(self, *args):
pass pass
@ -262,7 +272,9 @@ class ToolBar(QToolBar): # {{{
ch.setCursor(Qt.PointingHandCursor) ch.setCursor(Qt.PointingHandCursor)
ch.setAutoRaise(True) ch.setAutoRaise(True)
if ac.menu() is not None: 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: for x in actions:
self.addAction(x) self.addAction(x)
@ -292,11 +304,16 @@ class ToolBar(QToolBar): # {{{
a.setText(text) a.setText(text)
def resizeEvent(self, ev): def resizeEvent(self, ev):
style = Qt.ToolButtonTextUnderIcon
if self.size().width() < 1260:
style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style)
QToolBar.resizeEvent(self, ev) 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): def database_changed(self, db):
pass pass
@ -306,6 +323,62 @@ class ToolBar(QToolBar): # {{{
class Action(QAction): class Action(QAction):
pass 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): class MainWindowMixin(object):
def __init__(self, db): def __init__(self, db):
@ -321,7 +394,6 @@ class MainWindowMixin(object):
self.centralwidget.setLayout(self._central_widget_layout) self.centralwidget.setLayout(self._central_widget_layout)
self.resize(1012, 740) self.resize(1012, 740)
self.donate_button = ThrobbingButton(self.centralwidget) self.donate_button = ThrobbingButton(self.centralwidget)
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
self.location_manager = LocationManager(self) self.location_manager = LocationManager(self)
self.init_scheduler(db) self.init_scheduler(db)
@ -341,7 +413,6 @@ class MainWindowMixin(object):
self.scheduler.start_recipe_fetch.connect( self.scheduler.start_recipe_fetch.connect(
self.download_scheduled_recipe, type=Qt.QueuedConnection) self.download_scheduled_recipe, type=Qt.QueuedConnection)
def read_toolbar_settings(self): def read_toolbar_settings(self):
pass pass
@ -372,18 +443,19 @@ class MainWindowMixin(object):
setattr(self, 'action_'+name, action) setattr(self, 'action_'+name, action)
all_actions.append(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(1, 1, 0, 'edit', _('Edit metadata'), 'edit_input.svg', _('E'))
ac(2, 2, 3, 'convert', _('Convert books'), 'convert.svg', _('C')) ac(2, 2, 3, 'convert', _('Convert books'), 'convert.svg', _('C'))
ac(3, 3, 0, 'view', _('View'), 'view.svg', _('V')) 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')) tooltip=_('Choose calibre library to work with'))
ac(5, 5, 3, 'news', _('Fetch news'), 'news.svg', _('F')) ac(6, 6, 3, 'news', _('Fetch news'), 'news.svg', _('F'))
ac(6, 6, 0, 'save', _('Save to disk'), 'save.svg', _('S')) ac(7, 7, 0, 'save', _('Save to disk'), 'save.svg', _('S'))
ac(7, 0, 0, 'sync', _('Send to device'), 'sync.svg') ac(8, 8, 0, 'conn_share', _('Connect/share'), 'connect_share.svg')
ac(8, 8, 3, 'del', _('Remove books'), 'trash.svg', _('Del')) ac(9, 9, 3, 'del', _('Remove books'), 'trash.svg', _('Del'))
ac(9, 9, 3, 'help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual")) ac(10, 10, 3, 'help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual"))
ac(10, 10, 0, 'preferences', _('Preferences'), 'config.svg', _('Ctrl+P')) 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, 'merge', _('Merge book records'), 'merge_books.svg', _('M'))
ac(-1, -1, 0, 'open_containing_folder', _('Open containing folder'), 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.setMenu(self.scheduler.news_menu)
self.action_news.triggered.connect( self.action_news.triggered.connect(
self.scheduler.show_dialog) 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) self.action_help.triggered.connect(self.show_help)
md = QMenu() md = QMenu()
@ -528,6 +604,7 @@ class MainWindowMixin(object):
for x in (self.preferences_action, self.action_preferences): for x in (self.preferences_action, self.action_preferences):
x.triggered.connect(self.do_config) x.triggered.connect(self.do_config)
return all_actions return all_actions
# }}} # }}}

View File

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

View File

@ -10,13 +10,12 @@ import re
from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \ from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \
pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \ pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \
QAction, QKeySequence QAction, QKeySequence, QTimer
from calibre.gui2 import config from calibre.gui2 import config
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
from calibre.gui2.dialogs.search import SearchDialog from calibre.gui2.dialogs.search import SearchDialog
from calibre.utils.config import prefs
from calibre.utils.search_query_parser import saved_searches from calibre.utils.search_query_parser import saved_searches
class SearchLineEdit(QLineEdit): class SearchLineEdit(QLineEdit):
@ -83,7 +82,9 @@ class SearchBox2(QComboBox):
self.help_state = False self.help_state = False
self.as_you_type = True self.as_you_type = True
self.prev_search = '' 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.setInsertPolicy(self.NoInsert)
self.setMaxCount(self.MAX_COUNT) self.setMaxCount(self.MAX_COUNT)
self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon)
@ -117,9 +118,6 @@ class SearchBox2(QComboBox):
self.search.emit('') self.search.emit('')
self._in_a_search = False self._in_a_search = False
self.setEditText(self.help_text) 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.home(False)
self.line_edit.setStyleSheet( self.line_edit.setStyleSheet(
'QLineEdit { color: gray; background-color: %s; }' % 'QLineEdit { color: gray; background-color: %s; }' %
@ -148,17 +146,14 @@ class SearchBox2(QComboBox):
self._in_a_search = False self._in_a_search = False
if event.key() in (Qt.Key_Return, Qt.Key_Enter): if event.key() in (Qt.Key_Return, Qt.Key_Enter):
self.do_search() self.do_search()
self.timer = self.startTimer(self.__class__.INTERVAL) self.timer.start(1500)
def mouse_released(self, event): def mouse_released(self, event):
self.normalize_state() self.normalize_state()
if self.as_you_type: if self.as_you_type:
self.timer = self.startTimer(self.__class__.INTERVAL) self.timer.start(1500)
def timerEvent(self, event): def timer_event(self):
self.killTimer(event.timerId())
if event.timerId() == self.timer:
self.timer = None
self.do_search() self.do_search()
def history_selected(self, text): def history_selected(self, text):
@ -213,9 +208,6 @@ class SearchBox2(QComboBox):
return return
self.normalize_state() self.normalize_state()
self.setEditText(txt) 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.search.emit(txt)
self.line_edit.end(False) self.line_edit.end(False)
self.initial_state = False self.initial_state = False
@ -259,8 +251,7 @@ class SavedSearchBox(QComboBox):
self.setMinimumContentsLength(10) self.setMinimumContentsLength(10)
self.tool_tip_text = self.toolTip() self.tool_tip_text = self.toolTip()
def initialize(self, _saved_searches, _search_box, colorize=False, help_text=_('Search')): def initialize(self, _search_box, colorize=False, help_text=_('Search')):
self.saved_searches = _saved_searches
self.search_box = _search_box self.search_box = _search_box
self.help_text = help_text self.help_text = help_text
self.colorize = colorize self.colorize = colorize
@ -302,11 +293,11 @@ class SavedSearchBox(QComboBox):
self.normalize_state() self.normalize_state()
self.search_box.set_search_string(u'search:"%s"' % qname) self.search_box.set_search_string(u'search:"%s"' % qname)
self.setEditText(qname) self.setEditText(qname)
self.setToolTip(self.saved_searches.lookup(qname)) self.setToolTip(saved_searches().lookup(qname))
def initialize_saved_search_names(self): def initialize_saved_search_names(self):
self.clear() self.clear()
qnames = self.saved_searches.names() qnames = saved_searches().names()
self.addItems(qnames) self.addItems(qnames)
self.setCurrentIndex(-1) self.setCurrentIndex(-1)
@ -319,10 +310,10 @@ class SavedSearchBox(QComboBox):
idx = self.currentIndex idx = self.currentIndex
if idx < 0: if idx < 0:
return return
ss = self.saved_searches.lookup(unicode(self.currentText())) ss = saved_searches().lookup(unicode(self.currentText()))
if ss is None: if ss is None:
return return
self.saved_searches.delete(unicode(self.currentText())) saved_searches().delete(unicode(self.currentText()))
self.clear_to_help() self.clear_to_help()
self.search_box.clear_to_help() self.search_box.clear_to_help()
self.emit(SIGNAL('changed()')) self.emit(SIGNAL('changed()'))
@ -332,8 +323,8 @@ class SavedSearchBox(QComboBox):
name = unicode(self.currentText()) name = unicode(self.currentText())
if self.help_state or not name.strip(): if self.help_state or not name.strip():
name = unicode(self.search_box.text()).replace('"', '') name = unicode(self.search_box.text()).replace('"', '')
self.saved_searches.delete(name) saved_searches().delete(name)
self.saved_searches.add(name, unicode(self.search_box.text())) saved_searches().add(name, unicode(self.search_box.text()))
# now go through an initialization cycle to ensure that the combobox has # 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 # 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. # references the new search instead of the text in the search.
@ -348,7 +339,7 @@ class SavedSearchBox(QComboBox):
idx = self.currentIndex(); idx = self.currentIndex();
if idx < 0: if idx < 0:
return 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): class SearchBoxMixin(object):
@ -390,11 +381,12 @@ class SearchBoxMixin(object):
class SavedSearchBoxMixin(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.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed)
self.saved_searches_changed() self.saved_searches_changed()
self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help) 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')) help_text=_('Saved Searches'))
self.connect(self.save_search_button, SIGNAL('clicked()'), self.connect(self.save_search_button, SIGNAL('clicked()'),
self.saved_search.save_search_button_clicked) self.saved_search.save_search_button_clicked)
@ -409,9 +401,12 @@ class SavedSearchBoxMixin(object):
b = getattr(self, x+'_search_button') b = getattr(self, x+'_search_button')
b.setStatusTip(b.toolTip()) b.setStatusTip(b.toolTip())
def set_database(self, db):
self.db = db
self.saved_searches_changed()
def saved_searches_changed(self): def saved_searches_changed(self):
p = prefs['saved_searches'].keys() p = saved_searches().names()
p.sort() p.sort()
t = unicode(self.search_restriction.currentText()) t = unicode(self.search_restriction.currentText())
self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches 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.ebooks.metadata import title_sort
from calibre.gui2 import config, NONE from calibre.gui2 import config, NONE
from calibre.utils.config import prefs
from calibre.library.field_metadata import TagsIcons from calibre.library.field_metadata import TagsIcons
from calibre.utils.search_query_parser import saved_searches from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
@ -224,7 +223,7 @@ class TagsView(QTreeView): # {{{
# Always show the user categories editor # Always show the user categories editor
self.context_menu.addSeparator() 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'), self.context_menu.addAction(_('Manage User Categories'),
partial(self.context_menu_handler, action='manage_categories', partial(self.context_menu_handler, action='manage_categories',
category=category)) category=category))
@ -426,10 +425,10 @@ class TagsModel(QAbstractItemModel): # {{{
for k in tb_cats.keys(): for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']: if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k] 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 cat_name = user_cat+':' # add the ':' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat) 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')) tb_cats.add_search_category(label='search', name=_('Searches'))
# Now get the categories # Now get the categories
@ -507,11 +506,11 @@ class TagsModel(QAbstractItemModel): # {{{
if key not in self.db.field_metadata: if key not in self.db.field_metadata:
return return
if key == 'search': if key == 'search':
if val in saved_searches.names(): if val in saved_searches().names():
error_dialog(self.tags_view, _('Duplicate search name'), error_dialog(self.tags_view, _('Duplicate search name'),
_('The saved search name %s is already used.')%val).exec_() _('The saved search name %s is already used.')%val).exec_()
return False 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() self.tags_view.search_item_renamed.emit()
else: else:
if key == 'series': if key == 'series':

View File

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

View File

@ -448,7 +448,7 @@ class DocumentView(QWebView):
self.unimplemented_actions = list(map(self.pageAction, self.unimplemented_actions = list(map(self.pageAction,
[d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk, [d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk,
d.OpenImageInNewWindow, d.OpenLink])) d.OpenImageInNewWindow, d.OpenLink]))
self.dictionary_action = QAction(QIcon(I('dictionary.png')), self.dictionary_action = QAction(QIcon(I('dictionary.svg')),
_('&Lookup in dictionary'), self) _('&Lookup in dictionary'), self)
self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L) self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L)
self.dictionary_action.triggered.connect(self.lookup) 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.caches import ResultCache
from calibre.library.custom_columns import CustomColumns from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread 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, \ from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation MetaInformation
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats 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.filenames import ascii_filename
from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
from calibre.utils.config import prefs, tweaks 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.ebooks import BOOK_EXTENSIONS, check_ebook_format
from calibre.utils.magick_draw import save_cover_data_to from calibre.utils.magick_draw import save_cover_data_to
@ -140,6 +141,21 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.initialize_dynamic() self.initialize_dynamic()
def initialize_dynamic(self): 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(''' self.conn.executescript('''
DROP TRIGGER IF EXISTS author_insert_trg; DROP TRIGGER IF EXISTS author_insert_trg;
CREATE TEMP TRIGGER author_insert_trg CREATE TEMP TRIGGER author_insert_trg
@ -268,10 +284,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for k in tb_cats.keys(): for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']: if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k] 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 cat_name = user_cat+':' # add the ':' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat) 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')) tb_cats.add_search_category(label='search', name=_('Searches'))
self.book_on_device_func = None self.book_on_device_func = None
@ -843,7 +859,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
categories['formats'].sort(key = lambda x:x.name) categories['formats'].sort(key = lambda x:x.name)
#### Now do the user-defined categories. #### #### 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 # 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 # 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 icon = None
if icon_map and 'search' in icon_map: if icon_map and 'search' in icon_map:
icon = icon_map['search'] icon = icon_map['search']
for srch in saved_searches.names(): for srch in saved_searches().names():
items.append(Tag(srch, tooltip=saved_searches.lookup(srch), icon=icon)) items.append(Tag(srch, tooltip=saved_searches().lookup(srch), icon=icon))
if len(items): if len(items):
if icon_map is not None: if icon_map is not None:
icon_map['search'] = icon_map['search'] 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)') 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 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 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 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. 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 from ctypes import util
iswindows = 'win32' in sys.platform or 'win64' in sys.platform iswindows = 'win32' in sys.platform or 'win64' in sys.platform
isosx = 'darwin' in sys.platform isosx = 'darwin' in sys.platform
@ -85,7 +85,8 @@ elif iswindows:
_lib = flib if isfrozen else 'CORE_RL_wand_' _lib = flib if isfrozen else 'CORE_RL_wand_'
else: else:
if isfrozen: 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: else:
_lib = util.find_library('MagickWand') _lib = util.find_library('MagickWand')
if _lib is None: if _lib is None:

View File

@ -6,15 +6,16 @@ __docformat__ = 'restructuredtext en'
''' '''
Manage application-wide preferences. 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 copy import deepcopy
from functools import partial from functools import partial
from optparse import OptionParser as _OptionParser from optparse import OptionParser as _OptionParser
from optparse import IndentedHelpFormatter from optparse import IndentedHelpFormatter
from collections import defaultdict
from calibre.constants import terminal_controller, iswindows, isosx, \ from calibre.constants import terminal_controller, iswindows, isosx, \
__appname__, __version__, __author__, plugins __appname__, __version__, __author__, plugins
from calibre.utils.lock import LockError, ExclusiveFile from calibre.utils.lock import LockError, ExclusiveFile
from collections import defaultdict
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'): if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY']) config_dir = os.path.abspath(os.environ['CALIBRE_CONFIG_DIRECTORY'])
@ -632,27 +633,34 @@ class XMLConfig(dict):
f.truncate() f.truncate()
f.write(raw) 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): class JSONConfig(XMLConfig):
EXTENSION = '.json' 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): 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): 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): def __getitem__(self, key):
return dict.__getitem__(self, key) return dict.__getitem__(self, key)

View File

@ -42,6 +42,8 @@ def get_lang():
lang = match.group() lang = match.group()
if lang == 'zh': if lang == 'zh':
lang = 'zh_CN' lang = 'zh_CN'
if lang is None:
lang = 'en'
return lang return lang
def messages_path(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, \ from calibre.utils.pyparsing import Keyword, Group, Forward, CharsNotIn, Suppress, \
OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException
from calibre.constants import preferred_encoding from calibre.constants import preferred_encoding
from calibre.utils.config import prefs
''' '''
This class manages access to the preference holding the saved search queries. This class manages access to the preference holding the saved search queries.
@ -32,9 +31,13 @@ class SavedSearchQueries(object):
queries = {} queries = {}
opt_name = '' opt_name = ''
def __init__(self, _opt_name): def __init__(self, db, _opt_name):
self.opt_name = _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): def force_unicode(self, x):
if not isinstance(x, unicode): if not isinstance(x, unicode):
@ -43,20 +46,20 @@ class SavedSearchQueries(object):
def add(self, name, value): def add(self, name, value):
self.queries[self.force_unicode(name)] = self.force_unicode(value).strip() 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): def lookup(self, name):
return self.queries.get(self.force_unicode(name), None) return self.queries.get(self.force_unicode(name), None)
def delete(self, name): def delete(self, name):
self.queries.pop(self.force_unicode(name), False) 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): def rename(self, old_name, new_name):
self.queries[self.force_unicode(new_name)] = \ self.queries[self.force_unicode(new_name)] = \
self.queries.get(self.force_unicode(old_name), None) self.queries.get(self.force_unicode(old_name), None)
self.queries.pop(self.force_unicode(old_name), False) 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): def names(self):
return sorted(self.queries.keys(), 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 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). 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): class SearchQueryParser(object):
''' '''
@ -209,7 +219,7 @@ class SearchQueryParser(object):
raise ParseException(query, len(query), 'undefined saved search', self) raise ParseException(query, len(query), 'undefined saved search', self)
if self.recurse_level > 5: if self.recurse_level > 5:
self.searches_seen.add(query) 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 except: # convert all exceptions (e.g., missing key) to a parse error
raise ParseException(query, len(query), 'undefined saved search', self) raise ParseException(query, len(query), 'undefined saved search', self)
return self.get_matches(location, query) return self.get_matches(location, query)