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'
|
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' })
|
||||||
|
@ -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:
|
||||||
|
@ -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> '
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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_()
|
||||||
|
@ -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 &news to ebook reader</string>
|
<string>Automatically send downloaded &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>&Delete news from library when it is automatically sent to reader</string>
|
<string>&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>&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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="page_6">
|
<widget class="QWidget" name="page_6">
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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':
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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']
|
||||||
|
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)')
|
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
|
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
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user