mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG wip
This commit is contained in:
commit
621ab85e61
5123
resources/images/connect_share.svg
Normal file
5123
resources/images/connect_share.svg
Normal file
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 |
1009
resources/images/dictionary.svg
Normal file
1009
resources/images/dictionary.svg
Normal file
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 |
@ -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' })
|
||||
|
@ -295,10 +295,9 @@ class MetaInformation(object):
|
||||
if val is not None:
|
||||
setattr(self, attr, val)
|
||||
|
||||
if mi.tags:
|
||||
if replace_metadata:
|
||||
self.tags = mi.tags
|
||||
else:
|
||||
elif mi.tags:
|
||||
self.tags += mi.tags
|
||||
self.tags = list(set(self.tags))
|
||||
|
||||
@ -313,6 +312,9 @@ class MetaInformation(object):
|
||||
if len(other_cover) > len(self_cover):
|
||||
self.cover_data = mi.cover_data
|
||||
|
||||
if replace_metadata:
|
||||
self.comments = getattr(mi, 'comments', '')
|
||||
else:
|
||||
my_comments = getattr(self, 'comments', '')
|
||||
other_comments = getattr(mi, 'comments', '')
|
||||
if not my_comments:
|
||||
|
@ -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> '
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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_()
|
||||
|
@ -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 &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>&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>&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>&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 &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">
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
# }}}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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,17 +146,14 @@ 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
|
||||
def timer_event(self):
|
||||
self.do_search()
|
||||
|
||||
def history_selected(self, text):
|
||||
@ -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
|
||||
|
@ -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':
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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']
|
||||
|
49
src/calibre/library/prefs.py
Normal file
49
src/calibre/library/prefs.py
Normal 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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user