mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Rationalize various pieces of GUI initialization, making the jobs button a self contained widget.Note cover browser is currently broken.
This commit is contained in:
parent
e2c2067095
commit
8a88c2a620
@ -7,16 +7,17 @@ __docformat__ = 'restructuredtext en'
|
|||||||
Module to implement the Cover Flow feature
|
Module to implement the Cover Flow feature
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, os
|
import sys, os, time
|
||||||
|
|
||||||
from PyQt4.QtGui import QImage, QSizePolicy
|
from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \
|
||||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
|
QStackedLayout
|
||||||
|
|
||||||
from calibre import plugins
|
from calibre import plugins
|
||||||
from calibre.gui2 import config
|
from calibre.gui2 import config, available_height, available_width
|
||||||
pictureflow, pictureflowerror = plugins['pictureflow']
|
pictureflow, pictureflowerror = plugins['pictureflow']
|
||||||
|
|
||||||
if pictureflow is not None:
|
if pictureflow is not None:
|
||||||
|
|
||||||
class EmptyImageList(pictureflow.FlowImages):
|
class EmptyImageList(pictureflow.FlowImages):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pictureflow.FlowImages.__init__(self)
|
pictureflow.FlowImages.__init__(self)
|
||||||
@ -51,7 +52,7 @@ if pictureflow is not None:
|
|||||||
def __init__(self, model, buffer=20):
|
def __init__(self, model, buffer=20):
|
||||||
pictureflow.FlowImages.__init__(self)
|
pictureflow.FlowImages.__init__(self)
|
||||||
self.model = model
|
self.model = model
|
||||||
QObject.connect(self.model, SIGNAL('modelReset()'), self.reset)
|
self.model.modelReset.connect(self.reset)
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
return self.model.count()
|
return self.model.count()
|
||||||
@ -66,7 +67,7 @@ if pictureflow is not None:
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.emit(SIGNAL('dataChanged()'))
|
self.dataChanged.emit()
|
||||||
|
|
||||||
def image(self, index):
|
def image(self, index):
|
||||||
return self.model.cover(index)
|
return self.model.cover(index)
|
||||||
@ -74,13 +75,14 @@ if pictureflow is not None:
|
|||||||
|
|
||||||
class CoverFlow(pictureflow.PictureFlow):
|
class CoverFlow(pictureflow.PictureFlow):
|
||||||
|
|
||||||
def __init__(self, height=300, parent=None, text_height=25):
|
def __init__(self, parent=None):
|
||||||
pictureflow.PictureFlow.__init__(self, parent,
|
pictureflow.PictureFlow.__init__(self, parent,
|
||||||
config['cover_flow_queue_length']+1)
|
config['cover_flow_queue_length']+1)
|
||||||
self.setSlideSize(QSize(int(2/3. * height), height))
|
self.setSlideSize(QSize(int(2/3. * 10), 10))
|
||||||
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+text_height))
|
self.setMinimumSize(QSize(10, 10))
|
||||||
self.setFocusPolicy(Qt.WheelFocus)
|
self.setFocusPolicy(Qt.WheelFocus)
|
||||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
|
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
||||||
|
QSizePolicy.Expanding))
|
||||||
|
|
||||||
def wheelEvent(self, ev):
|
def wheelEvent(self, ev):
|
||||||
ev.accept()
|
ev.accept()
|
||||||
@ -95,6 +97,104 @@ else:
|
|||||||
DatabaseImages = None
|
DatabaseImages = None
|
||||||
FileSystemImages = None
|
FileSystemImages = None
|
||||||
|
|
||||||
|
class CoverFlowMixin(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.cover_flow = None
|
||||||
|
if CoverFlow is not None:
|
||||||
|
self.cf_last_updated_at = None
|
||||||
|
self.cover_flow_sync_timer = QTimer(self)
|
||||||
|
self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync)
|
||||||
|
self.cover_flow_sync_flag = True
|
||||||
|
self.cover_flow = CoverFlow(parent=self)
|
||||||
|
self.cover_flow.setVisible(False)
|
||||||
|
if not config['separate_cover_flow']:
|
||||||
|
self.cb_layout.addWidget(self.cover_flow)
|
||||||
|
self.cover_flow.currentChanged.connect(self.sync_listview_to_cf)
|
||||||
|
self.library_view.selectionModel().currentRowChanged.connect(
|
||||||
|
self.sync_cf_to_listview)
|
||||||
|
self.db_images = DatabaseImages(self.library_view.model())
|
||||||
|
self.cover_flow.setImages(self.db_images)
|
||||||
|
ah, aw = available_height(), available_width()
|
||||||
|
self._cb_layout_is_horizontal = float(aw)/ah >= 1.4
|
||||||
|
self.cb_layout.setDirection(self.cb_layout.LeftToRight if
|
||||||
|
self._cb_layout_is_horizontal else
|
||||||
|
self.cb_layout.TopToBottom)
|
||||||
|
|
||||||
|
def toggle_cover_flow_visibility(self, show):
|
||||||
|
if config['separate_cover_flow']:
|
||||||
|
if show:
|
||||||
|
d = QDialog(self)
|
||||||
|
ah, aw = available_height(), available_width()
|
||||||
|
d.resize(int(aw/2.), ah-60)
|
||||||
|
d._layout = QStackedLayout()
|
||||||
|
d.setLayout(d._layout)
|
||||||
|
d.setWindowTitle(_('Browse by covers'))
|
||||||
|
d.layout().addWidget(self.cover_flow)
|
||||||
|
self.cover_flow.setVisible(True)
|
||||||
|
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
||||||
|
d.show()
|
||||||
|
d.finished.connect(self.sidebar.external_cover_flow_finished)
|
||||||
|
self.cf_dialog = d
|
||||||
|
else:
|
||||||
|
cfd = getattr(self, 'cf_dialog', None)
|
||||||
|
if cfd is not None:
|
||||||
|
self.cover_flow.setVisible(False)
|
||||||
|
cfd.hide()
|
||||||
|
self.cf_dialog = None
|
||||||
|
else:
|
||||||
|
if show:
|
||||||
|
self.cover_flow.setVisible(True)
|
||||||
|
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
||||||
|
else:
|
||||||
|
self.cover_flow.setVisible(False)
|
||||||
|
|
||||||
|
def toggle_cover_flow(self, show):
|
||||||
|
if show:
|
||||||
|
self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
|
||||||
|
self.library_view.setCurrentIndex(
|
||||||
|
self.library_view.currentIndex())
|
||||||
|
self.cover_flow_sync_timer.start(500)
|
||||||
|
self.library_view.scroll_to_row(self.library_view.currentIndex().row())
|
||||||
|
else:
|
||||||
|
self.cover_flow_sync_timer.stop()
|
||||||
|
idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0)
|
||||||
|
if idx.isValid():
|
||||||
|
sm = self.library_view.selectionModel()
|
||||||
|
sm.select(idx, sm.ClearAndSelect|sm.Rows)
|
||||||
|
self.library_view.setCurrentIndex(idx)
|
||||||
|
self.library_view.scroll_to_row(idx.row())
|
||||||
|
self.toggle_cover_flow_visibility(show)
|
||||||
|
|
||||||
|
def sync_cf_to_listview(self, current, previous):
|
||||||
|
if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
|
||||||
|
self.cover_flow.currentSlide() != current.row():
|
||||||
|
self.cover_flow.setCurrentSlide(current.row())
|
||||||
|
self.cover_flow_sync_flag = True
|
||||||
|
|
||||||
|
def cover_flow_do_sync(self):
|
||||||
|
self.cover_flow_sync_flag = True
|
||||||
|
try:
|
||||||
|
if self.cover_flow.isVisible() and self.cf_last_updated_at is not None and \
|
||||||
|
time.time() - self.cf_last_updated_at > 0.5:
|
||||||
|
self.cf_last_updated_at = None
|
||||||
|
row = self.cover_flow.currentSlide()
|
||||||
|
m = self.library_view.model()
|
||||||
|
index = m.index(row, 0)
|
||||||
|
if self.library_view.currentIndex().row() != row and index.isValid():
|
||||||
|
self.cover_flow_sync_flag = False
|
||||||
|
self.library_view.scroll_to_row(index.row())
|
||||||
|
sm = self.library_view.selectionModel()
|
||||||
|
sm.select(index, sm.ClearAndSelect|sm.Rows)
|
||||||
|
self.library_view.setCurrentIndex(index)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def sync_listview_to_cf(self, row):
|
||||||
|
self.cf_last_updated_at = time.time()
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -108,7 +208,7 @@ if __name__ == '__main__':
|
|||||||
path = sys.argv[1]
|
path = sys.argv[1]
|
||||||
model = FileSystemImages(sys.argv[1])
|
model = FileSystemImages(sys.argv[1])
|
||||||
cf.setImages(model)
|
cf.setImages(model)
|
||||||
cf.connect(cf, SIGNAL('currentChanged(int)'), model.currentChanged)
|
cf.currentChanged[int].connect(model.currentChanged)
|
||||||
w.setCentralWidget(cf)
|
w.setCentralWidget(cf)
|
||||||
|
|
||||||
w.show()
|
w.show()
|
||||||
|
275
src/calibre/gui2/init.py
Normal file
275
src/calibre/gui2/init.py
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
#!/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 functools
|
||||||
|
|
||||||
|
from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon
|
||||||
|
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
from calibre.constants import isosx
|
||||||
|
from calibre.gui2 import config
|
||||||
|
|
||||||
|
_keep_refs = []
|
||||||
|
|
||||||
|
def partial(*args, **kwargs):
|
||||||
|
ans = functools.partial(*args, **kwargs)
|
||||||
|
_keep_refs.append(ans)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
class SaveMenu(QMenu): # {{{
|
||||||
|
|
||||||
|
save_fmt = pyqtSignal(object)
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
||||||
|
for ext in sorted(BOOK_EXTENSIONS):
|
||||||
|
action = self.addAction(ext.upper())
|
||||||
|
setattr(self, 'do_'+ext, partial(self.do, ext))
|
||||||
|
action.triggered.connect(
|
||||||
|
getattr(self, 'do_'+ext))
|
||||||
|
|
||||||
|
def do(self, ext, *args):
|
||||||
|
self.save_fmt.emit(ext)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ToolbarMixin(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
md = QMenu()
|
||||||
|
md.addAction(_('Edit metadata individually'),
|
||||||
|
partial(self.edit_metadata, False))
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(_('Edit metadata in bulk'),
|
||||||
|
partial(self.edit_metadata, False, bulk=True))
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(_('Download metadata and covers'),
|
||||||
|
partial(self.download_metadata, False, covers=True))
|
||||||
|
md.addAction(_('Download only metadata'),
|
||||||
|
partial(self.download_metadata, False, covers=False))
|
||||||
|
md.addAction(_('Download only covers'),
|
||||||
|
partial(self.download_metadata, False, covers=True,
|
||||||
|
set_metadata=False, set_social_metadata=False))
|
||||||
|
md.addAction(_('Download only social metadata'),
|
||||||
|
partial(self.download_metadata, False, covers=False,
|
||||||
|
set_metadata=False, set_social_metadata=True))
|
||||||
|
self.metadata_menu = md
|
||||||
|
|
||||||
|
mb = QMenu()
|
||||||
|
mb.addAction(_('Merge into first selected book - delete others'),
|
||||||
|
self.merge_books)
|
||||||
|
mb.addSeparator()
|
||||||
|
mb.addAction(_('Merge into first selected book - keep others'),
|
||||||
|
partial(self.merge_books, safe_merge=True))
|
||||||
|
self.merge_menu = mb
|
||||||
|
self.action_merge.setMenu(mb)
|
||||||
|
md.addSeparator()
|
||||||
|
md.addAction(self.action_merge)
|
||||||
|
|
||||||
|
self.add_menu = QMenu()
|
||||||
|
self.add_menu.addAction(_('Add books from a single directory'),
|
||||||
|
self.add_books)
|
||||||
|
self.add_menu.addAction(_('Add books from directories, including '
|
||||||
|
'sub-directories (One book per directory, assumes every ebook '
|
||||||
|
'file is the same book in a different format)'),
|
||||||
|
self.add_recursive_single)
|
||||||
|
self.add_menu.addAction(_('Add books from directories, including '
|
||||||
|
'sub directories (Multiple books per directory, assumes every '
|
||||||
|
'ebook file is a different book)'), self.add_recursive_multiple)
|
||||||
|
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
||||||
|
'formats)'), self.add_empty)
|
||||||
|
self.action_add.setMenu(self.add_menu)
|
||||||
|
self.action_add.triggered.connect(self.add_books)
|
||||||
|
self.action_del.triggered.connect(self.delete_books)
|
||||||
|
self.action_edit.triggered.connect(self.edit_metadata)
|
||||||
|
self.action_merge.triggered.connect(self.merge_books)
|
||||||
|
|
||||||
|
self.action_save.triggered.connect(self.save_to_disk)
|
||||||
|
self.save_menu = QMenu()
|
||||||
|
self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
|
||||||
|
False))
|
||||||
|
self.save_menu.addAction(_('Save to disk in a single directory'),
|
||||||
|
partial(self.save_to_single_dir, False))
|
||||||
|
self.save_menu.addAction(_('Save only %s format to disk')%
|
||||||
|
prefs['output_format'].upper(),
|
||||||
|
partial(self.save_single_format_to_disk, False))
|
||||||
|
self.save_menu.addAction(
|
||||||
|
_('Save only %s format to disk in a single directory')%
|
||||||
|
prefs['output_format'].upper(),
|
||||||
|
partial(self.save_single_fmt_to_single_dir, False))
|
||||||
|
self.save_sub_menu = SaveMenu(self)
|
||||||
|
self.save_menu.addMenu(self.save_sub_menu)
|
||||||
|
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
|
||||||
|
|
||||||
|
self.action_view.triggered.connect(self.view_book)
|
||||||
|
self.view_menu = QMenu()
|
||||||
|
self.view_menu.addAction(_('View'), partial(self.view_book, False))
|
||||||
|
ac = self.view_menu.addAction(_('View specific format'))
|
||||||
|
ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
|
||||||
|
self.action_view.setMenu(self.view_menu)
|
||||||
|
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
self.delete_menu = QMenu()
|
||||||
|
self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove files of a specific format from selected books..'),
|
||||||
|
self.delete_selected_formats)
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove all formats from selected books, except...'),
|
||||||
|
self.delete_all_but_selected_formats)
|
||||||
|
self.delete_menu.addAction(
|
||||||
|
_('Remove covers from selected books'), self.delete_covers)
|
||||||
|
self.action_del.setMenu(self.delete_menu)
|
||||||
|
|
||||||
|
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
||||||
|
self.addAction(self.action_open_containing_folder)
|
||||||
|
self.action_sync.setShortcut(Qt.Key_D)
|
||||||
|
self.action_sync.setEnabled(True)
|
||||||
|
self.create_device_menu()
|
||||||
|
self.action_sync.triggered.connect(
|
||||||
|
self._sync_action_triggered)
|
||||||
|
|
||||||
|
self.action_edit.setMenu(md)
|
||||||
|
self.action_save.setMenu(self.save_menu)
|
||||||
|
|
||||||
|
cm = QMenu()
|
||||||
|
cm.addAction(_('Convert individually'), partial(self.convert_ebook,
|
||||||
|
False, bulk=False))
|
||||||
|
cm.addAction(_('Bulk convert'),
|
||||||
|
partial(self.convert_ebook, False, bulk=True))
|
||||||
|
cm.addSeparator()
|
||||||
|
ac = cm.addAction(
|
||||||
|
_('Create catalog of books in your calibre library'))
|
||||||
|
ac.triggered.connect(self.generate_catalog)
|
||||||
|
self.action_convert.setMenu(cm)
|
||||||
|
self.action_convert.triggered.connect(self.convert_ebook)
|
||||||
|
self.convert_menu = cm
|
||||||
|
|
||||||
|
pm = QMenu()
|
||||||
|
ap = self.action_preferences
|
||||||
|
pm.addAction(ap)
|
||||||
|
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
||||||
|
self.run_wizard)
|
||||||
|
self.action_preferences.setMenu(pm)
|
||||||
|
self.preferences_menu = pm
|
||||||
|
for x in (self.preferences_action, self.action_preferences):
|
||||||
|
x.triggered.connect(self.do_config)
|
||||||
|
|
||||||
|
for x in ('news', 'edit', 'sync', 'convert', 'save', 'add', 'view',
|
||||||
|
'del', 'preferences'):
|
||||||
|
w = self.tool_bar.widgetForAction(getattr(self, 'action_'+x))
|
||||||
|
w.setPopupMode(w.MenuButtonPopup)
|
||||||
|
|
||||||
|
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
|
|
||||||
|
for ch in self.tool_bar.children():
|
||||||
|
if isinstance(ch, QToolButton):
|
||||||
|
ch.setCursor(Qt.PointingHandCursor)
|
||||||
|
|
||||||
|
self.tool_bar.contextMenuEvent = self.no_op
|
||||||
|
|
||||||
|
def read_toolbar_settings(self):
|
||||||
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
|
self.tool_bar.setToolButtonStyle(
|
||||||
|
Qt.ToolButtonTextUnderIcon if \
|
||||||
|
config['show_text_in_toolbar'] else \
|
||||||
|
Qt.ToolButtonIconOnly)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class LibraryViewMixin(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self, db):
|
||||||
|
similar_menu = QMenu(_('Similar books...'))
|
||||||
|
similar_menu.addAction(self.action_books_by_same_author)
|
||||||
|
similar_menu.addAction(self.action_books_in_this_series)
|
||||||
|
similar_menu.addAction(self.action_books_with_the_same_tags)
|
||||||
|
similar_menu.addAction(self.action_books_by_this_publisher)
|
||||||
|
self.action_books_by_same_author.setShortcut(Qt.ALT + Qt.Key_A)
|
||||||
|
self.action_books_in_this_series.setShortcut(Qt.ALT + Qt.Key_S)
|
||||||
|
self.action_books_by_this_publisher.setShortcut(Qt.ALT + Qt.Key_P)
|
||||||
|
self.action_books_with_the_same_tags.setShortcut(Qt.ALT+Qt.Key_T)
|
||||||
|
self.addAction(self.action_books_by_same_author)
|
||||||
|
self.addAction(self.action_books_by_this_publisher)
|
||||||
|
self.addAction(self.action_books_in_this_series)
|
||||||
|
self.addAction(self.action_books_with_the_same_tags)
|
||||||
|
self.similar_menu = similar_menu
|
||||||
|
self.action_books_by_same_author.triggered.connect(
|
||||||
|
partial(self.show_similar_books, 'authors'))
|
||||||
|
self.action_books_in_this_series.triggered.connect(
|
||||||
|
partial(self.show_similar_books, 'series'))
|
||||||
|
self.action_books_with_the_same_tags.triggered.connect(
|
||||||
|
partial(self.show_similar_books, 'tag'))
|
||||||
|
self.action_books_by_this_publisher.triggered.connect(
|
||||||
|
partial(self.show_similar_books, 'publisher'))
|
||||||
|
self.library_view.set_context_menu(self.action_edit, self.action_sync,
|
||||||
|
self.action_convert, self.action_view,
|
||||||
|
self.action_save,
|
||||||
|
self.action_open_containing_folder,
|
||||||
|
self.action_show_book_details,
|
||||||
|
self.action_del,
|
||||||
|
similar_menu=similar_menu)
|
||||||
|
|
||||||
|
self.memory_view.set_context_menu(None, None, None,
|
||||||
|
self.action_view, self.action_save, None, None, self.action_del)
|
||||||
|
self.card_a_view.set_context_menu(None, None, None,
|
||||||
|
self.action_view, self.action_save, None, None, self.action_del)
|
||||||
|
self.card_b_view.set_context_menu(None, None, None,
|
||||||
|
self.action_view, self.action_save, None, None, self.action_del)
|
||||||
|
|
||||||
|
self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
|
||||||
|
for func, args in [
|
||||||
|
('connect_to_search_box', (self.search,
|
||||||
|
self.search_done)),
|
||||||
|
('connect_to_book_display',
|
||||||
|
(self.status_bar.book_info.show_data,)),
|
||||||
|
]:
|
||||||
|
for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
|
||||||
|
getattr(view, func)(*args)
|
||||||
|
|
||||||
|
self.memory_view.connect_dirtied_signal(self.upload_booklists)
|
||||||
|
self.card_a_view.connect_dirtied_signal(self.upload_booklists)
|
||||||
|
self.card_b_view.connect_dirtied_signal(self.upload_booklists)
|
||||||
|
|
||||||
|
self.book_on_device(None, reset=True)
|
||||||
|
db.set_book_on_device_func(self.book_on_device)
|
||||||
|
self.library_view.set_database(db)
|
||||||
|
self.library_view.model().set_book_on_device_func(self.book_on_device)
|
||||||
|
prefs['library_path'] = self.library_path
|
||||||
|
|
||||||
|
def show_similar_books(self, type, *args):
|
||||||
|
search, join = [], ' '
|
||||||
|
idx = self.library_view.currentIndex()
|
||||||
|
if not idx.isValid():
|
||||||
|
return
|
||||||
|
row = idx.row()
|
||||||
|
if type == 'series':
|
||||||
|
series = idx.model().db.series(row)
|
||||||
|
if series:
|
||||||
|
search = ['series:"'+series+'"']
|
||||||
|
elif type == 'publisher':
|
||||||
|
publisher = idx.model().db.publisher(row)
|
||||||
|
if publisher:
|
||||||
|
search = ['publisher:"'+publisher+'"']
|
||||||
|
elif type == 'tag':
|
||||||
|
tags = idx.model().db.tags(row)
|
||||||
|
if tags:
|
||||||
|
search = ['tag:"='+t+'"' for t in tags.split(',')]
|
||||||
|
elif type in ('author', 'authors'):
|
||||||
|
authors = idx.model().db.authors(row)
|
||||||
|
if authors:
|
||||||
|
search = ['author:"='+a.strip().replace('|', ',')+'"' \
|
||||||
|
for a in authors.split(',')]
|
||||||
|
join = ' or '
|
||||||
|
if search:
|
||||||
|
self.search.set_search_string(join.join(search))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
@ -7,11 +7,14 @@ __docformat__ = 'restructuredtext en'
|
|||||||
Job management.
|
Job management.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from Queue import Empty, Queue
|
from Queue import Empty, Queue
|
||||||
|
|
||||||
from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
|
from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
|
||||||
QTimer, SIGNAL, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
|
QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
|
||||||
QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip
|
QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \
|
||||||
|
QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication
|
||||||
|
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.utils.ipc.job import ParallelJob
|
from calibre.utils.ipc.job import ParallelJob
|
||||||
@ -20,9 +23,13 @@ from calibre.gui2.device import DeviceJob
|
|||||||
from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog
|
from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog
|
||||||
from calibre import __appname__
|
from calibre import __appname__
|
||||||
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
|
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
|
||||||
|
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||||
|
|
||||||
class JobManager(QAbstractTableModel):
|
class JobManager(QAbstractTableModel):
|
||||||
|
|
||||||
|
job_added = pyqtSignal(int)
|
||||||
|
job_done = pyqtSignal(int)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QAbstractTableModel.__init__(self)
|
QAbstractTableModel.__init__(self)
|
||||||
self.wait_icon = QVariant(QIcon(I('jobs.svg')))
|
self.wait_icon = QVariant(QIcon(I('jobs.svg')))
|
||||||
@ -37,8 +44,7 @@ class JobManager(QAbstractTableModel):
|
|||||||
self.changed_queue = Queue()
|
self.changed_queue = Queue()
|
||||||
|
|
||||||
self.timer = QTimer(self)
|
self.timer = QTimer(self)
|
||||||
self.connect(self.timer, SIGNAL('timeout()'), self.update,
|
self.timer.timeout.connect(self.update, type=Qt.QueuedConnection)
|
||||||
Qt.QueuedConnection)
|
|
||||||
self.timer.start(1000)
|
self.timer.start(1000)
|
||||||
|
|
||||||
def columnCount(self, parent=QModelIndex()):
|
def columnCount(self, parent=QModelIndex()):
|
||||||
@ -130,8 +136,7 @@ class JobManager(QAbstractTableModel):
|
|||||||
for i, j in enumerate(self.jobs):
|
for i, j in enumerate(self.jobs):
|
||||||
if j.run_state == j.RUNNING:
|
if j.run_state == j.RUNNING:
|
||||||
idx = self.index(i, 3)
|
idx = self.index(i, 3)
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
self.dataChanged.emit(idx, idx)
|
||||||
idx, idx)
|
|
||||||
|
|
||||||
# Update parallel jobs
|
# Update parallel jobs
|
||||||
jobs = set([])
|
jobs = set([])
|
||||||
@ -157,20 +162,19 @@ class JobManager(QAbstractTableModel):
|
|||||||
self.jobs.sort()
|
self.jobs.sort()
|
||||||
self.reset()
|
self.reset()
|
||||||
if job.is_finished:
|
if job.is_finished:
|
||||||
self.emit(SIGNAL('job_done(int)'), len(self.unfinished_jobs()))
|
self.job_done.emit(len(self.unfinished_jobs()))
|
||||||
else:
|
else:
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
idx = self.jobs.index(job)
|
idx = self.jobs.index(job)
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
self.dataChanged.emit(
|
||||||
self.index(idx, 0), self.index(idx, 3))
|
self.index(idx, 0), self.index(idx, 3))
|
||||||
|
|
||||||
|
|
||||||
def _add_job(self, job):
|
def _add_job(self, job):
|
||||||
self.emit(SIGNAL('layoutAboutToBeChanged()'))
|
self.layoutAboutToBeChanged.emit()
|
||||||
self.jobs.append(job)
|
self.jobs.append(job)
|
||||||
self.jobs.sort()
|
self.jobs.sort()
|
||||||
self.emit(SIGNAL('job_added(int)'), len(self.unfinished_jobs()))
|
self.job_added.emit(len(self.unfinished_jobs()))
|
||||||
self.emit(SIGNAL('layoutChanged()'))
|
|
||||||
|
|
||||||
def done_jobs(self):
|
def done_jobs(self):
|
||||||
return [j for j in self.jobs if j.is_finished]
|
return [j for j in self.jobs if j.is_finished]
|
||||||
@ -266,6 +270,76 @@ class DetailView(QDialog, Ui_Dialog):
|
|||||||
if more:
|
if more:
|
||||||
self.log.appendPlainText(more.decode('utf-8', 'replace'))
|
self.log.appendPlainText(more.decode('utf-8', 'replace'))
|
||||||
|
|
||||||
|
class JobsButton(QFrame):
|
||||||
|
|
||||||
|
def __init__(self, horizontal=False, size=48, parent=None):
|
||||||
|
QFrame.__init__(self, parent)
|
||||||
|
self.pi = ProgressIndicator(self, size)
|
||||||
|
self._jobs = QLabel('<b>'+_('Jobs:')+' 0')
|
||||||
|
|
||||||
|
if horizontal:
|
||||||
|
self.setLayout(QHBoxLayout())
|
||||||
|
else:
|
||||||
|
self.setLayout(QVBoxLayout())
|
||||||
|
self._jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
|
||||||
|
|
||||||
|
self.layout().addWidget(self.pi)
|
||||||
|
self.layout().addWidget(self._jobs)
|
||||||
|
if not horizontal:
|
||||||
|
self.layout().setAlignment(self._jobs, Qt.AlignHCenter)
|
||||||
|
self._jobs.setMargin(0)
|
||||||
|
self.layout().setMargin(0)
|
||||||
|
self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
||||||
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
|
self.setToolTip(_('Click to see list of active jobs.'))
|
||||||
|
|
||||||
|
def initialize(self, jobs_dialog, job_manager):
|
||||||
|
self.jobs_dialog = jobs_dialog
|
||||||
|
job_manager.job_added.connect(self.job_added)
|
||||||
|
job_manager.job_done.connect(self.job_done)
|
||||||
|
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if self.jobs_dialog.isVisible():
|
||||||
|
self.jobs_dialog.hide()
|
||||||
|
else:
|
||||||
|
self.jobs_dialog.show()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_running(self):
|
||||||
|
return self.pi.isAnimated()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.pi.startAnimation()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.pi.stopAnimation()
|
||||||
|
|
||||||
|
def jobs(self):
|
||||||
|
src = unicode(self._jobs.text())
|
||||||
|
return int(re.search(r'\d+', src).group())
|
||||||
|
|
||||||
|
def job_added(self, nnum):
|
||||||
|
jobs = self._jobs
|
||||||
|
src = unicode(jobs.text())
|
||||||
|
num = self.jobs()
|
||||||
|
text = src.replace(str(num), str(nnum))
|
||||||
|
jobs.setText(text)
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def job_done(self, nnum):
|
||||||
|
jobs = self._jobs
|
||||||
|
src = unicode(jobs.text())
|
||||||
|
num = self.jobs()
|
||||||
|
text = src.replace(str(num), str(nnum))
|
||||||
|
jobs.setText(text)
|
||||||
|
if nnum == 0:
|
||||||
|
self.no_more_jobs()
|
||||||
|
|
||||||
|
def no_more_jobs(self):
|
||||||
|
if self.is_running:
|
||||||
|
self.stop()
|
||||||
|
QCoreApplication.instance().alert(self, 5000)
|
||||||
|
|
||||||
|
|
||||||
class JobsDialog(QDialog, Ui_JobsDialog):
|
class JobsDialog(QDialog, Ui_JobsDialog):
|
||||||
@ -278,14 +352,9 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.model = model
|
self.model = model
|
||||||
self.setWindowModality(Qt.NonModal)
|
self.setWindowModality(Qt.NonModal)
|
||||||
self.setWindowTitle(__appname__ + _(' - Jobs'))
|
self.setWindowTitle(__appname__ + _(' - Jobs'))
|
||||||
self.connect(self.kill_button, SIGNAL('clicked()'),
|
self.kill_button.clicked.connect(self.kill_job)
|
||||||
self.kill_job)
|
self.details_button.clicked.connect(self.show_details)
|
||||||
self.connect(self.details_button, SIGNAL('clicked()'),
|
self.stop_all_jobs_button.clicked.connect(self.kill_all_jobs)
|
||||||
self.show_details)
|
|
||||||
self.connect(self.stop_all_jobs_button, SIGNAL('clicked()'),
|
|
||||||
self.kill_all_jobs)
|
|
||||||
self.connect(self, SIGNAL('kill_job(int, PyQt_PyObject)'),
|
|
||||||
self.jobs_view.model().kill_job)
|
|
||||||
self.pb_delegate = ProgressBarDelegate(self)
|
self.pb_delegate = ProgressBarDelegate(self)
|
||||||
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
||||||
self.jobs_view.doubleClicked.connect(self.show_job_details)
|
self.jobs_view.doubleClicked.connect(self.show_job_details)
|
||||||
@ -304,18 +373,18 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
d.timer.stop()
|
d.timer.stop()
|
||||||
|
|
||||||
def kill_job(self):
|
def kill_job(self, *args):
|
||||||
for index in self.jobs_view.selectedIndexes():
|
for index in self.jobs_view.selectedIndexes():
|
||||||
row = index.row()
|
row = index.row()
|
||||||
self.model.kill_job(row, self)
|
self.model.kill_job(row, self)
|
||||||
return
|
return
|
||||||
|
|
||||||
def show_details(self):
|
def show_details(self, *args):
|
||||||
for index in self.jobs_view.selectedIndexes():
|
for index in self.jobs_view.selectedIndexes():
|
||||||
self.show_job_details(index)
|
self.show_job_details(index)
|
||||||
return
|
return
|
||||||
|
|
||||||
def kill_all_jobs(self):
|
def kill_all_jobs(self, *args):
|
||||||
self.model.kill_all_jobs()
|
self.model.kill_all_jobs()
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
|
@ -16,6 +16,10 @@ public:
|
|||||||
virtual int count();
|
virtual int count();
|
||||||
virtual QImage image(int index);
|
virtual QImage image(int index);
|
||||||
virtual QString caption(int index);
|
virtual QString caption(int index);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void dataChanged();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,78 +5,13 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import re
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \
|
from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \
|
||||||
QFrame, QVBoxLayout, QLabel, QSize, QCoreApplication, QToolButton
|
QSize, QToolButton
|
||||||
|
|
||||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
|
||||||
from calibre.gui2 import dynamic
|
from calibre.gui2 import dynamic
|
||||||
|
|
||||||
class JobsButton(QFrame):
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
QFrame.__init__(self, parent)
|
|
||||||
self.setLayout(QVBoxLayout())
|
|
||||||
self.pi = ProgressIndicator(self)
|
|
||||||
self.layout().addWidget(self.pi)
|
|
||||||
self.jobs = QLabel('<b>'+_('Jobs:')+' 0')
|
|
||||||
self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
|
|
||||||
self.layout().addWidget(self.jobs)
|
|
||||||
self.layout().setAlignment(self.jobs, Qt.AlignHCenter)
|
|
||||||
self.jobs.setMargin(0)
|
|
||||||
self.layout().setMargin(0)
|
|
||||||
self.jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
|
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
|
||||||
self.setToolTip(_('Click to see list of active jobs.'))
|
|
||||||
|
|
||||||
def initialize(self, jobs_dialog):
|
|
||||||
self.jobs_dialog = jobs_dialog
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
|
||||||
if self.jobs_dialog.isVisible():
|
|
||||||
self.jobs_dialog.hide()
|
|
||||||
else:
|
|
||||||
self.jobs_dialog.show()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_running(self):
|
|
||||||
return self.pi.isAnimated()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.pi.startAnimation()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.pi.stopAnimation()
|
|
||||||
|
|
||||||
|
|
||||||
class Jobs(ProgressIndicator):
|
|
||||||
|
|
||||||
def initialize(self, jobs_dialog):
|
|
||||||
self.jobs_dialog = jobs_dialog
|
|
||||||
|
|
||||||
def mouseClickEvent(self, event):
|
|
||||||
if self.jobs_dialog.isVisible():
|
|
||||||
self.jobs_dialog.jobs_view.write_settings()
|
|
||||||
self.jobs_dialog.hide()
|
|
||||||
else:
|
|
||||||
self.jobs_dialog.jobs_view.read_settings()
|
|
||||||
self.jobs_dialog.show()
|
|
||||||
self.jobs_dialog.jobs_view.restore_column_widths()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_running(self):
|
|
||||||
return self.isAnimated()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.startAnimation()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.stopAnimation()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SideBar(QToolBar):
|
class SideBar(QToolBar):
|
||||||
|
|
||||||
toggle_texts = {
|
toggle_texts = {
|
||||||
@ -114,8 +49,6 @@ class SideBar(QToolBar):
|
|||||||
self.spacer = QWidget(self)
|
self.spacer = QWidget(self)
|
||||||
self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
|
self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||||
self.addWidget(self.spacer)
|
self.addWidget(self.spacer)
|
||||||
self.jobs_button = JobsButton(self)
|
|
||||||
self.addWidget(self.jobs_button)
|
|
||||||
|
|
||||||
self.show_cover_browser = partial(self._toggle_cover_browser, show=True)
|
self.show_cover_browser = partial(self._toggle_cover_browser, show=True)
|
||||||
self.hide_cover_browser = partial(self._toggle_cover_browser,
|
self.hide_cover_browser = partial(self._toggle_cover_browser,
|
||||||
@ -124,9 +57,8 @@ class SideBar(QToolBar):
|
|||||||
if isinstance(ch, QToolButton):
|
if isinstance(ch, QToolButton):
|
||||||
ch.setCursor(Qt.PointingHandCursor)
|
ch.setCursor(Qt.PointingHandCursor)
|
||||||
|
|
||||||
def initialize(self, jobs_dialog, cover_browser, toggle_cover_browser,
|
def initialize(self, jobs_button, cover_browser, toggle_cover_browser,
|
||||||
cover_browser_error, vertical_splitter, horizontal_splitter):
|
cover_browser_error, vertical_splitter, horizontal_splitter):
|
||||||
self.jobs_button.initialize(jobs_dialog)
|
|
||||||
self.cover_browser, self.do_toggle_cover_browser = cover_browser, \
|
self.cover_browser, self.do_toggle_cover_browser = cover_browser, \
|
||||||
toggle_cover_browser
|
toggle_cover_browser
|
||||||
if self.cover_browser is None:
|
if self.cover_browser is None:
|
||||||
@ -166,6 +98,7 @@ class SideBar(QToolBar):
|
|||||||
'book_info'), type=Qt.QueuedConnection)
|
'book_info'), type=Qt.QueuedConnection)
|
||||||
self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed,
|
self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed,
|
||||||
'tag_browser'), type=Qt.QueuedConnection)
|
'tag_browser'), type=Qt.QueuedConnection)
|
||||||
|
self.addWidget(jobs_button)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -211,30 +144,4 @@ class SideBar(QToolBar):
|
|||||||
def _toggle_book_info(self, show=None):
|
def _toggle_book_info(self, show=None):
|
||||||
self.vertical_splitter.toggle_side_index()
|
self.vertical_splitter.toggle_side_index()
|
||||||
|
|
||||||
def jobs(self):
|
|
||||||
src = unicode(self.jobs_button.jobs.text())
|
|
||||||
return int(re.search(r'\d+', src).group())
|
|
||||||
|
|
||||||
def job_added(self, nnum):
|
|
||||||
jobs = self.jobs_button.jobs
|
|
||||||
src = unicode(jobs.text())
|
|
||||||
num = self.jobs()
|
|
||||||
text = src.replace(str(num), str(nnum))
|
|
||||||
jobs.setText(text)
|
|
||||||
self.jobs_button.start()
|
|
||||||
|
|
||||||
def job_done(self, nnum):
|
|
||||||
jobs = self.jobs_button.jobs
|
|
||||||
src = unicode(jobs.text())
|
|
||||||
num = self.jobs()
|
|
||||||
text = src.replace(str(num), str(nnum))
|
|
||||||
jobs.setText(text)
|
|
||||||
if nnum == 0:
|
|
||||||
self.no_more_jobs()
|
|
||||||
|
|
||||||
def no_more_jobs(self):
|
|
||||||
if self.jobs_button.is_running:
|
|
||||||
self.jobs_button.stop()
|
|
||||||
QCoreApplication.instance().alert(self, 5000)
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ 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
|
||||||
|
from calibre.gui2.dialogs.tag_categories import TagCategories
|
||||||
|
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
||||||
|
|
||||||
class TagsView(QTreeView): # {{{
|
class TagsView(QTreeView): # {{{
|
||||||
|
|
||||||
@ -29,12 +31,16 @@ class TagsView(QTreeView): # {{{
|
|||||||
tag_item_renamed = pyqtSignal()
|
tag_item_renamed = pyqtSignal()
|
||||||
search_item_renamed = pyqtSignal()
|
search_item_renamed = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, parent=None):
|
||||||
QTreeView.__init__(self, *args)
|
QTreeView.__init__(self, parent=None)
|
||||||
|
self.tag_match = None
|
||||||
self.setUniformRowHeights(True)
|
self.setUniformRowHeights(True)
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
self.setIconSize(QSize(30, 30))
|
self.setIconSize(QSize(30, 30))
|
||||||
self.tag_match = None
|
self.setTabKeyNavigation(True)
|
||||||
|
self.setAlternatingRowColors(True)
|
||||||
|
self.setAnimated(True)
|
||||||
|
self.setHeaderHidden(True)
|
||||||
|
|
||||||
def set_database(self, db, tag_match, popularity):
|
def set_database(self, db, tag_match, popularity):
|
||||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||||
@ -588,3 +594,42 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class TagBrowserMixin(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self, db):
|
||||||
|
self.tags_view.set_database(self.library_view.model().db,
|
||||||
|
self.tag_match, self.popularity)
|
||||||
|
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
||||||
|
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
|
||||||
|
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
||||||
|
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
||||||
|
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
||||||
|
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
|
||||||
|
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
|
||||||
|
|
||||||
|
def do_user_categories_edit(self, on_category=None):
|
||||||
|
d = TagCategories(self, self.library_view.model().db, on_category)
|
||||||
|
d.exec_()
|
||||||
|
if d.result() == d.Accepted:
|
||||||
|
self.tags_view.set_new_model()
|
||||||
|
self.tags_view.recount()
|
||||||
|
|
||||||
|
def do_tags_list_edit(self, tag, category):
|
||||||
|
d = TagListEditor(self, self.library_view.model().db, tag, category)
|
||||||
|
d.exec_()
|
||||||
|
if d.result() == d.Accepted:
|
||||||
|
# Clean up everything, as information could have changed for many books.
|
||||||
|
self.library_view.model().refresh()
|
||||||
|
self.tags_view.set_new_model()
|
||||||
|
self.tags_view.recount()
|
||||||
|
self.saved_search.clear_to_help()
|
||||||
|
self.search.clear_to_help()
|
||||||
|
|
||||||
|
def do_tag_item_renamed(self):
|
||||||
|
# Clean up library view and search
|
||||||
|
self.library_view.model().refresh()
|
||||||
|
self.saved_search.clear_to_help()
|
||||||
|
self.search.clear_to_help()
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ from threading import Thread
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from PyQt4.Qt import Qt, SIGNAL, QObject, QUrl, QTimer, \
|
from PyQt4.Qt import Qt, SIGNAL, QObject, QUrl, QTimer, \
|
||||||
QModelIndex, QPixmap, QColor, QPainter, QMenu, QIcon, \
|
QModelIndex, QPixmap, QColor, QPainter, QMenu, QIcon, \
|
||||||
QToolButton, QDialog, QDesktopServices, \
|
QDialog, QDesktopServices, \
|
||||||
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
||||||
QMessageBox, QStackedLayout, QHelpEvent, QInputDialog,\
|
QMessageBox, QHelpEvent, QInputDialog,\
|
||||||
QThread, pyqtSignal
|
QThread, pyqtSignal
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
|
|
||||||
@ -35,10 +35,9 @@ from calibre.gui2 import warning_dialog, choose_files, error_dialog, \
|
|||||||
question_dialog,\
|
question_dialog,\
|
||||||
pixmap_to_data, choose_dir, \
|
pixmap_to_data, choose_dir, \
|
||||||
Dispatcher, gprefs, \
|
Dispatcher, gprefs, \
|
||||||
available_height, \
|
|
||||||
max_available_height, config, info_dialog, \
|
max_available_height, config, info_dialog, \
|
||||||
available_width, GetMetadata
|
GetMetadata
|
||||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
from calibre.gui2.cover_flow import pictureflowerror, CoverFlowMixin
|
||||||
from calibre.gui2.widgets import ProgressIndicator, IMAGE_EXTENSIONS
|
from calibre.gui2.widgets import ProgressIndicator, IMAGE_EXTENSIONS
|
||||||
from calibre.gui2.wizard import move_library
|
from calibre.gui2.wizard import move_library
|
||||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||||
@ -46,7 +45,7 @@ from calibre.gui2.update import CheckForUpdates
|
|||||||
from calibre.gui2.main_window import MainWindow
|
from calibre.gui2.main_window import MainWindow
|
||||||
from calibre.gui2.main_ui import Ui_MainWindow
|
from calibre.gui2.main_ui import Ui_MainWindow
|
||||||
from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceGUI, Emailer
|
from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceGUI, Emailer
|
||||||
from calibre.gui2.jobs import JobManager, JobsDialog
|
from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
|
||||||
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||||
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
||||||
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \
|
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \
|
||||||
@ -60,24 +59,11 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
|
|||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.library.caches import CoverCache
|
from calibre.library.caches import CoverCache
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.gui2.dialogs.tag_categories import TagCategories
|
|
||||||
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
|
|
||||||
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
|
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
|
||||||
|
from calibre.gui2.tag_view import TagBrowserMixin
|
||||||
|
from calibre.gui2.init import ToolbarMixin, LibraryViewMixin
|
||||||
|
|
||||||
class SaveMenu(QMenu):
|
class Listener(Thread): # {{{
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
|
||||||
for ext in sorted(BOOK_EXTENSIONS):
|
|
||||||
action = self.addAction(ext.upper())
|
|
||||||
setattr(self, 'do_'+ext, partial(self.do, ext))
|
|
||||||
self.connect(action, SIGNAL('triggered(bool)'),
|
|
||||||
getattr(self, 'do_'+ext))
|
|
||||||
|
|
||||||
def do(self, ext, *args):
|
|
||||||
self.emit(SIGNAL('save_fmt(PyQt_PyObject)'), ext)
|
|
||||||
|
|
||||||
class Listener(Thread):
|
|
||||||
|
|
||||||
def __init__(self, listener):
|
def __init__(self, listener):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
@ -102,7 +88,9 @@ class Listener(Thread):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class SystemTrayIcon(QSystemTrayIcon):
|
# }}}
|
||||||
|
|
||||||
|
class SystemTrayIcon(QSystemTrayIcon): # {{{
|
||||||
|
|
||||||
def __init__(self, icon, parent):
|
def __init__(self, icon, parent):
|
||||||
QSystemTrayIcon.__init__(self, icon, parent)
|
QSystemTrayIcon.__init__(self, icon, parent)
|
||||||
@ -115,7 +103,10 @@ class SystemTrayIcon(QSystemTrayIcon):
|
|||||||
return True
|
return True
|
||||||
return QSystemTrayIcon.event(self, ev)
|
return QSystemTrayIcon.event(self, ev)
|
||||||
|
|
||||||
class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
# }}}
|
||||||
|
|
||||||
|
class Main(MainWindow, Ui_MainWindow, DeviceGUI, ToolbarMixin,
|
||||||
|
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin):
|
||||||
'The main GUI'
|
'The main GUI'
|
||||||
|
|
||||||
def set_default_thumbnail(self, height):
|
def set_default_thumbnail(self, height):
|
||||||
@ -234,7 +225,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.connect(self.system_tray_icon,
|
self.connect(self.system_tray_icon,
|
||||||
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
|
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
|
||||||
self.system_tray_icon_activated)
|
self.system_tray_icon_activated)
|
||||||
self.tool_bar.contextMenuEvent = self.no_op
|
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'),
|
||||||
|
self.do_advanced_search)
|
||||||
|
|
||||||
####################### Start spare job server ########################
|
####################### Start spare job server ########################
|
||||||
QTimer.singleShot(1000, self.add_spare_server)
|
QTimer.singleShot(1000, self.add_spare_server)
|
||||||
@ -277,281 +269,22 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.status_bar.files_dropped.connect(self.files_dropped_on_book)
|
self.status_bar.files_dropped.connect(self.files_dropped_on_book)
|
||||||
|
|
||||||
####################### Setup Toolbar #####################
|
####################### Setup Toolbar #####################
|
||||||
md = QMenu()
|
ToolbarMixin.__init__(self)
|
||||||
md.addAction(_('Edit metadata individually'))
|
|
||||||
md.addSeparator()
|
|
||||||
md.addAction(_('Edit metadata in bulk'))
|
|
||||||
md.addSeparator()
|
|
||||||
md.addAction(_('Download metadata and covers'))
|
|
||||||
md.addAction(_('Download only metadata'))
|
|
||||||
md.addAction(_('Download only covers'))
|
|
||||||
md.addAction(_('Download only social metadata'))
|
|
||||||
self.metadata_menu = md
|
|
||||||
|
|
||||||
mb = QMenu()
|
|
||||||
mb.addAction(_('Merge into first selected book - delete others'))
|
|
||||||
mb.addSeparator()
|
|
||||||
mb.addAction(_('Merge into first selected book - keep others'))
|
|
||||||
self.merge_menu = mb
|
|
||||||
self.action_merge.setMenu(mb)
|
|
||||||
md.addSeparator()
|
|
||||||
md.addAction(self.action_merge)
|
|
||||||
|
|
||||||
self.add_menu = QMenu()
|
|
||||||
self.add_menu.addAction(_('Add books from a single directory'))
|
|
||||||
self.add_menu.addAction(_('Add books from directories, including '
|
|
||||||
'sub-directories (One book per directory, assumes every ebook '
|
|
||||||
'file is the same book in a different format)'))
|
|
||||||
self.add_menu.addAction(_('Add books from directories, including '
|
|
||||||
'sub directories (Multiple books per directory, assumes every '
|
|
||||||
'ebook file is a different book)'))
|
|
||||||
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
|
||||||
'formats)'))
|
|
||||||
self.action_add.setMenu(self.add_menu)
|
|
||||||
QObject.connect(self.action_add, SIGNAL("triggered(bool)"),
|
|
||||||
self.add_books)
|
|
||||||
QObject.connect(self.add_menu.actions()[0], SIGNAL("triggered(bool)"),
|
|
||||||
self.add_books)
|
|
||||||
QObject.connect(self.add_menu.actions()[1], SIGNAL("triggered(bool)"),
|
|
||||||
self.add_recursive_single)
|
|
||||||
QObject.connect(self.add_menu.actions()[2], SIGNAL("triggered(bool)"),
|
|
||||||
self.add_recursive_multiple)
|
|
||||||
QObject.connect(self.add_menu.actions()[3], SIGNAL('triggered(bool)'),
|
|
||||||
self.add_empty)
|
|
||||||
QObject.connect(self.action_del, SIGNAL("triggered(bool)"),
|
|
||||||
self.delete_books)
|
|
||||||
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"),
|
|
||||||
self.edit_metadata)
|
|
||||||
self.__em1__ = partial(self.edit_metadata, bulk=False)
|
|
||||||
QObject.connect(md.actions()[0], SIGNAL('triggered(bool)'),
|
|
||||||
self.__em1__)
|
|
||||||
self.__em2__ = partial(self.edit_metadata, bulk=True)
|
|
||||||
QObject.connect(md.actions()[2], SIGNAL('triggered(bool)'),
|
|
||||||
self.__em2__)
|
|
||||||
self.__em3__ = partial(self.download_metadata, covers=True)
|
|
||||||
QObject.connect(md.actions()[4], SIGNAL('triggered(bool)'),
|
|
||||||
self.__em3__)
|
|
||||||
self.__em4__ = partial(self.download_metadata, covers=False)
|
|
||||||
QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'),
|
|
||||||
self.__em4__)
|
|
||||||
self.__em5__ = partial(self.download_metadata, covers=True,
|
|
||||||
set_metadata=False, set_social_metadata=False)
|
|
||||||
QObject.connect(md.actions()[6], SIGNAL('triggered(bool)'),
|
|
||||||
self.__em5__)
|
|
||||||
self.__em6__ = partial(self.download_metadata, covers=False,
|
|
||||||
set_metadata=False, set_social_metadata=True)
|
|
||||||
QObject.connect(md.actions()[7], SIGNAL('triggered(bool)'),
|
|
||||||
self.__em6__)
|
|
||||||
|
|
||||||
QObject.connect(self.action_merge, SIGNAL("triggered(bool)"),
|
|
||||||
self.merge_books)
|
|
||||||
QObject.connect(mb.actions()[0], SIGNAL('triggered(bool)'),
|
|
||||||
self.merge_books)
|
|
||||||
self.__mb1__ = partial(self.merge_books, safe_merge=True)
|
|
||||||
QObject.connect(mb.actions()[2], SIGNAL('triggered(bool)'),
|
|
||||||
self.__mb1__)
|
|
||||||
|
|
||||||
self.save_menu = QMenu()
|
|
||||||
self.save_menu.addAction(_('Save to disk'))
|
|
||||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
|
||||||
self.save_menu.addAction(_('Save only %s format to disk')%
|
|
||||||
prefs['output_format'].upper())
|
|
||||||
self.save_menu.addAction(
|
|
||||||
_('Save only %s format to disk in a single directory')%
|
|
||||||
prefs['output_format'].upper())
|
|
||||||
|
|
||||||
self.save_sub_menu = SaveMenu(self)
|
|
||||||
self.save_menu.addMenu(self.save_sub_menu)
|
|
||||||
self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'),
|
|
||||||
self.save_specific_format_disk)
|
|
||||||
|
|
||||||
self.view_menu = QMenu()
|
|
||||||
self.view_menu.addAction(_('View'))
|
|
||||||
ac = self.view_menu.addAction(_('View specific format'))
|
|
||||||
ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
|
|
||||||
self.action_view.setMenu(self.view_menu)
|
|
||||||
|
|
||||||
self.delete_menu = QMenu()
|
|
||||||
self.delete_menu.addAction(_('Remove selected books'))
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove files of a specific format from selected books..'))
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove all formats from selected books, except...'))
|
|
||||||
self.delete_menu.addAction(
|
|
||||||
_('Remove covers from selected books'))
|
|
||||||
self.action_del.setMenu(self.delete_menu)
|
|
||||||
QObject.connect(self.action_save, SIGNAL("triggered(bool)"),
|
|
||||||
self.save_to_disk)
|
|
||||||
QObject.connect(self.save_menu.actions()[0], SIGNAL("triggered(bool)"),
|
|
||||||
self.save_to_disk)
|
|
||||||
QObject.connect(self.save_menu.actions()[1], SIGNAL("triggered(bool)"),
|
|
||||||
self.save_to_single_dir)
|
|
||||||
QObject.connect(self.save_menu.actions()[2], SIGNAL("triggered(bool)"),
|
|
||||||
self.save_single_format_to_disk)
|
|
||||||
QObject.connect(self.save_menu.actions()[3], SIGNAL("triggered(bool)"),
|
|
||||||
self.save_single_fmt_to_single_dir)
|
|
||||||
QObject.connect(self.action_view, SIGNAL("triggered(bool)"),
|
|
||||||
self.view_book)
|
|
||||||
QObject.connect(self.view_menu.actions()[0],
|
|
||||||
SIGNAL("triggered(bool)"), self.view_book)
|
|
||||||
QObject.connect(self.view_menu.actions()[1],
|
|
||||||
SIGNAL("triggered(bool)"), self.view_specific_format,
|
|
||||||
Qt.QueuedConnection)
|
|
||||||
self.connect(self.action_open_containing_folder,
|
|
||||||
SIGNAL('triggered(bool)'), self.view_folder)
|
|
||||||
|
|
||||||
self.delete_menu.actions()[0].triggered.connect(self.delete_books)
|
|
||||||
self.delete_menu.actions()[1].triggered.connect(self.delete_selected_formats)
|
|
||||||
self.delete_menu.actions()[2].triggered.connect(self.delete_all_but_selected_formats)
|
|
||||||
self.delete_menu.actions()[3].triggered.connect(self.delete_covers)
|
|
||||||
|
|
||||||
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
|
||||||
self.addAction(self.action_open_containing_folder)
|
|
||||||
self.action_sync.setShortcut(Qt.Key_D)
|
|
||||||
self.action_sync.setEnabled(True)
|
|
||||||
self.create_device_menu()
|
|
||||||
self.connect(self.action_sync, SIGNAL('triggered(bool)'),
|
|
||||||
self._sync_action_triggered)
|
|
||||||
|
|
||||||
self.action_edit.setMenu(md)
|
|
||||||
self.action_save.setMenu(self.save_menu)
|
|
||||||
|
|
||||||
cm = QMenu()
|
|
||||||
cm.addAction(_('Convert individually'))
|
|
||||||
cm.addAction(_('Bulk convert'))
|
|
||||||
cm.addSeparator()
|
|
||||||
ac = cm.addAction(
|
|
||||||
_('Create catalog of books in your calibre library'))
|
|
||||||
ac.triggered.connect(self.generate_catalog)
|
|
||||||
self.action_convert.setMenu(cm)
|
|
||||||
self._convert_single_hook = partial(self.convert_ebook, bulk=False)
|
|
||||||
QObject.connect(cm.actions()[0],
|
|
||||||
SIGNAL('triggered(bool)'), self._convert_single_hook)
|
|
||||||
self._convert_bulk_hook = partial(self.convert_ebook, bulk=True)
|
|
||||||
QObject.connect(cm.actions()[1],
|
|
||||||
SIGNAL('triggered(bool)'), self._convert_bulk_hook)
|
|
||||||
QObject.connect(self.action_convert,
|
|
||||||
SIGNAL('triggered(bool)'), self.convert_ebook)
|
|
||||||
self.convert_menu = cm
|
|
||||||
|
|
||||||
pm = QMenu()
|
|
||||||
ap = self.action_preferences
|
|
||||||
pm.addAction(ap.icon(), ap.text())
|
|
||||||
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'))
|
|
||||||
self.connect(pm.actions()[0], SIGNAL('triggered(bool)'),
|
|
||||||
self.do_config)
|
|
||||||
self.connect(pm.actions()[1], SIGNAL('triggered(bool)'),
|
|
||||||
self.run_wizard)
|
|
||||||
self.action_preferences.setMenu(pm)
|
|
||||||
self.preferences_menu = pm
|
|
||||||
|
|
||||||
self.tool_bar.widgetForAction(self.action_news).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_edit).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_sync).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_convert).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_save).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_add).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_view).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_del).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.widgetForAction(self.action_preferences).\
|
|
||||||
setPopupMode(QToolButton.MenuButtonPopup)
|
|
||||||
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
|
|
||||||
|
|
||||||
self.connect(self.preferences_action, SIGNAL('triggered(bool)'),
|
|
||||||
self.do_config)
|
|
||||||
self.connect(self.action_preferences, SIGNAL('triggered(bool)'),
|
|
||||||
self.do_config)
|
|
||||||
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'),
|
|
||||||
self.do_advanced_search)
|
|
||||||
|
|
||||||
for ch in self.tool_bar.children():
|
|
||||||
if isinstance(ch, QToolButton):
|
|
||||||
ch.setCursor(Qt.PointingHandCursor)
|
|
||||||
|
|
||||||
####################### Library view ########################
|
####################### Library view ########################
|
||||||
similar_menu = QMenu(_('Similar books...'))
|
LibraryViewMixin.__init__(self, db)
|
||||||
similar_menu.addAction(self.action_books_by_same_author)
|
|
||||||
similar_menu.addAction(self.action_books_in_this_series)
|
|
||||||
similar_menu.addAction(self.action_books_with_the_same_tags)
|
|
||||||
similar_menu.addAction(self.action_books_by_this_publisher)
|
|
||||||
self.action_books_by_same_author.setShortcut(Qt.ALT + Qt.Key_A)
|
|
||||||
self.action_books_in_this_series.setShortcut(Qt.ALT + Qt.Key_S)
|
|
||||||
self.action_books_by_this_publisher.setShortcut(Qt.ALT + Qt.Key_P)
|
|
||||||
self.action_books_with_the_same_tags.setShortcut(Qt.ALT+Qt.Key_T)
|
|
||||||
self.addAction(self.action_books_by_same_author)
|
|
||||||
self.addAction(self.action_books_by_this_publisher)
|
|
||||||
self.addAction(self.action_books_in_this_series)
|
|
||||||
self.addAction(self.action_books_with_the_same_tags)
|
|
||||||
self.similar_menu = similar_menu
|
|
||||||
self.connect(self.action_books_by_same_author, SIGNAL('triggered()'),
|
|
||||||
lambda : self.show_similar_books('author'))
|
|
||||||
self.connect(self.action_books_in_this_series, SIGNAL('triggered()'),
|
|
||||||
lambda : self.show_similar_books('series'))
|
|
||||||
self.connect(self.action_books_with_the_same_tags,
|
|
||||||
SIGNAL('triggered()'),
|
|
||||||
lambda : self.show_similar_books('tag'))
|
|
||||||
self.connect(self.action_books_by_this_publisher, SIGNAL('triggered()'),
|
|
||||||
lambda : self.show_similar_books('publisher'))
|
|
||||||
self.library_view.set_context_menu(self.action_edit, self.action_sync,
|
|
||||||
self.action_convert, self.action_view,
|
|
||||||
self.action_save,
|
|
||||||
self.action_open_containing_folder,
|
|
||||||
self.action_show_book_details,
|
|
||||||
self.action_del,
|
|
||||||
similar_menu=similar_menu)
|
|
||||||
|
|
||||||
self.memory_view.set_context_menu(None, None, None,
|
|
||||||
self.action_view, self.action_save, None, None, self.action_del)
|
|
||||||
self.card_a_view.set_context_menu(None, None, None,
|
|
||||||
self.action_view, self.action_save, None, None, self.action_del)
|
|
||||||
self.card_b_view.set_context_menu(None, None, None,
|
|
||||||
self.action_view, self.action_save, None, None, self.action_del)
|
|
||||||
|
|
||||||
self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
|
|
||||||
for func, args in [
|
|
||||||
('connect_to_search_box', (self.search,
|
|
||||||
self.search_done)),
|
|
||||||
('connect_to_book_display',
|
|
||||||
(self.status_bar.book_info.show_data,)),
|
|
||||||
]:
|
|
||||||
for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
|
|
||||||
getattr(view, func)(*args)
|
|
||||||
|
|
||||||
self.memory_view.connect_dirtied_signal(self.upload_booklists)
|
|
||||||
self.card_a_view.connect_dirtied_signal(self.upload_booklists)
|
|
||||||
self.card_b_view.connect_dirtied_signal(self.upload_booklists)
|
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||||
self.hide_windows()
|
self.hide_windows()
|
||||||
self.stack.setCurrentIndex(0)
|
self.stack.setCurrentIndex(0)
|
||||||
self.book_on_device(None, reset=True)
|
|
||||||
db.set_book_on_device_func(self.book_on_device)
|
|
||||||
self.library_view.set_database(db)
|
|
||||||
self.library_view.model().set_book_on_device_func(self.book_on_device)
|
|
||||||
prefs['library_path'] = self.library_path
|
|
||||||
self.search.setFocus(Qt.OtherFocusReason)
|
self.search.setFocus(Qt.OtherFocusReason)
|
||||||
self.cover_cache = CoverCache(self.library_path)
|
self.cover_cache = CoverCache(self.library_path)
|
||||||
self.cover_cache.start()
|
self.cover_cache.start()
|
||||||
self.library_view.model().cover_cache = self.cover_cache
|
self.library_view.model().cover_cache = self.cover_cache
|
||||||
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit)
|
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit)
|
||||||
self.search_restriction.activated[str].connect(self.apply_search_restriction)
|
self.search_restriction.activated[str].connect(self.apply_search_restriction)
|
||||||
self.tags_view.set_database(db, self.tag_match, self.popularity)
|
|
||||||
self.tags_view.tags_marked.connect(self.search.search_from_tags)
|
|
||||||
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
|
|
||||||
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
|
|
||||||
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
|
|
||||||
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
|
|
||||||
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
|
|
||||||
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
|
|
||||||
for x in (self.location_view.count_changed, self.tags_view.recount,
|
for x in (self.location_view.count_changed, self.tags_view.recount,
|
||||||
self.restriction_count_changed):
|
self.restriction_count_changed):
|
||||||
self.library_view.model().count_changed_signal.connect(x)
|
self.library_view.model().count_changed_signal.connect(x)
|
||||||
@ -578,46 +311,29 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
|
|
||||||
########################### Tags Browser ##############################
|
########################### Tags Browser ##############################
|
||||||
|
TagBrowserMixin.__init__(self, db)
|
||||||
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
|
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.search_restriction.setMinimumContentsLength(10)
|
self.search_restriction.setMinimumContentsLength(10)
|
||||||
|
|
||||||
########################### Cover Flow ################################
|
########################### Cover Flow ################################
|
||||||
self.cover_flow = None
|
|
||||||
if CoverFlow is not None:
|
CoverFlowMixin.__init__(self)
|
||||||
self.cf_last_updated_at = None
|
|
||||||
self.cover_flow_sync_timer = QTimer(self)
|
|
||||||
self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync)
|
|
||||||
self.cover_flow_sync_flag = True
|
|
||||||
text_height = 40 if config['separate_cover_flow'] else 25
|
|
||||||
ah = available_height()
|
|
||||||
cfh = ah-100
|
|
||||||
cfh = 3./5 * cfh - text_height
|
|
||||||
if not config['separate_cover_flow']:
|
|
||||||
cfh = 220 if ah > 950 else 170 if ah > 850 else 140
|
|
||||||
self.cover_flow = CoverFlow(height=cfh, text_height=text_height)
|
|
||||||
self.cover_flow.setVisible(False)
|
|
||||||
if not config['separate_cover_flow']:
|
|
||||||
self.cb_layout.addWidget(self.cover_flow)
|
|
||||||
self.cover_flow.currentChanged.connect(self.sync_listview_to_cf)
|
|
||||||
self.library_view.selectionModel().currentRowChanged.connect(
|
|
||||||
self.sync_cf_to_listview)
|
|
||||||
self.db_images = DatabaseImages(self.library_view.model())
|
|
||||||
self.cover_flow.setImages(self.db_images)
|
|
||||||
|
|
||||||
self._calculated_available_height = min(max_available_height()-15,
|
self._calculated_available_height = min(max_available_height()-15,
|
||||||
self.height())
|
self.height())
|
||||||
self.resize(self.width(), self._calculated_available_height)
|
self.resize(self.width(), self._calculated_available_height)
|
||||||
self.search.setMaximumWidth(self.width()-150)
|
self.search.setMaximumWidth(self.width()-150)
|
||||||
|
|
||||||
|
# Jobs Button {{{
|
||||||
|
self.jobs_button = JobsButton()
|
||||||
|
self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
|
||||||
|
# }}}
|
||||||
|
|
||||||
####################### Side Bar ###############################
|
####################### Side Bar ###############################
|
||||||
|
|
||||||
self.sidebar.initialize(self.jobs_dialog, self.cover_flow,
|
self.sidebar.initialize(self.jobs_button, self.cover_flow,
|
||||||
self.toggle_cover_flow, pictureflowerror,
|
self.toggle_cover_flow, pictureflowerror,
|
||||||
self.vertical_splitter, self.horizontal_splitter)
|
self.vertical_splitter, self.horizontal_splitter)
|
||||||
QObject.connect(self.job_manager, SIGNAL('job_added(int)'),
|
|
||||||
self.sidebar.job_added, Qt.QueuedConnection)
|
|
||||||
QObject.connect(self.job_manager, SIGNAL('job_done(int)'),
|
|
||||||
self.sidebar.job_done, Qt.QueuedConnection)
|
|
||||||
|
|
||||||
|
|
||||||
if config['autolaunch_server']:
|
if config['autolaunch_server']:
|
||||||
@ -648,29 +364,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
def do_user_categories_edit(self, on_category=None):
|
|
||||||
d = TagCategories(self, self.library_view.model().db, on_category)
|
|
||||||
d.exec_()
|
|
||||||
if d.result() == d.Accepted:
|
|
||||||
self.tags_view.set_new_model()
|
|
||||||
self.tags_view.recount()
|
|
||||||
|
|
||||||
def do_tags_list_edit(self, tag, category):
|
|
||||||
d = TagListEditor(self, self.library_view.model().db, tag, category)
|
|
||||||
d.exec_()
|
|
||||||
if d.result() == d.Accepted:
|
|
||||||
# Clean up everything, as information could have changed for many books.
|
|
||||||
self.library_view.model().refresh()
|
|
||||||
self.tags_view.set_new_model()
|
|
||||||
self.tags_view.recount()
|
|
||||||
self.saved_search.clear_to_help()
|
|
||||||
self.search.clear_to_help()
|
|
||||||
|
|
||||||
def do_tag_item_renamed(self):
|
|
||||||
# Clean up library view and search
|
|
||||||
self.library_view.model().refresh()
|
|
||||||
self.saved_search.clear_to_help()
|
|
||||||
self.search.clear_to_help()
|
|
||||||
|
|
||||||
def do_saved_search_edit(self, search):
|
def do_saved_search_edit(self, search):
|
||||||
d = SavedSearchEditor(self, search)
|
d = SavedSearchEditor(self, search)
|
||||||
@ -760,81 +453,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
error_dialog(self, _('Failed to start content server'),
|
error_dialog(self, _('Failed to start content server'),
|
||||||
unicode(self.content_server.exception)).exec_()
|
unicode(self.content_server.exception)).exec_()
|
||||||
|
|
||||||
def show_similar_books(self, type):
|
|
||||||
search, join = [], ' '
|
|
||||||
idx = self.library_view.currentIndex()
|
|
||||||
if not idx.isValid():
|
|
||||||
return
|
|
||||||
row = idx.row()
|
|
||||||
if type == 'series':
|
|
||||||
series = idx.model().db.series(row)
|
|
||||||
if series:
|
|
||||||
search = ['series:"'+series+'"']
|
|
||||||
elif type == 'publisher':
|
|
||||||
publisher = idx.model().db.publisher(row)
|
|
||||||
if publisher:
|
|
||||||
search = ['publisher:"'+publisher+'"']
|
|
||||||
elif type == 'tag':
|
|
||||||
tags = idx.model().db.tags(row)
|
|
||||||
if tags:
|
|
||||||
search = ['tag:"='+t+'"' for t in tags.split(',')]
|
|
||||||
elif type == 'author':
|
|
||||||
authors = idx.model().db.authors(row)
|
|
||||||
if authors:
|
|
||||||
search = ['author:"='+a.strip().replace('|', ',')+'"' \
|
|
||||||
for a in authors.split(',')]
|
|
||||||
join = ' or '
|
|
||||||
if search:
|
|
||||||
self.search.set_search_string(join.join(search))
|
|
||||||
|
|
||||||
def toggle_cover_flow(self, show):
|
|
||||||
if config['separate_cover_flow']:
|
|
||||||
if show:
|
|
||||||
self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
|
|
||||||
d = QDialog(self)
|
|
||||||
ah, aw = available_height(), available_width()
|
|
||||||
d.resize(int(aw/2.), ah-60)
|
|
||||||
d._layout = QStackedLayout()
|
|
||||||
d.setLayout(d._layout)
|
|
||||||
d.setWindowTitle(_('Browse by covers'))
|
|
||||||
d.layout().addWidget(self.cover_flow)
|
|
||||||
self.cover_flow.setVisible(True)
|
|
||||||
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
|
||||||
self.library_view.scroll_to_row(self.library_view.currentIndex().row())
|
|
||||||
d.show()
|
|
||||||
d.finished.connect(self.sidebar.external_cover_flow_finished)
|
|
||||||
self.cf_dialog = d
|
|
||||||
self.cover_flow_sync_timer.start(500)
|
|
||||||
else:
|
|
||||||
self.cover_flow_sync_timer.stop()
|
|
||||||
idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0)
|
|
||||||
if idx.isValid():
|
|
||||||
sm = self.library_view.selectionModel()
|
|
||||||
sm.select(idx, sm.ClearAndSelect|sm.Rows)
|
|
||||||
self.library_view.setCurrentIndex(idx)
|
|
||||||
cfd = getattr(self, 'cf_dialog', None)
|
|
||||||
if cfd is not None:
|
|
||||||
self.cover_flow.setVisible(False)
|
|
||||||
cfd.hide()
|
|
||||||
self.cf_dialog = None
|
|
||||||
else:
|
|
||||||
if show:
|
|
||||||
self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
|
|
||||||
self.library_view.setCurrentIndex(
|
|
||||||
self.library_view.currentIndex())
|
|
||||||
self.cover_flow.setVisible(True)
|
|
||||||
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
|
||||||
self.library_view.scroll_to_row(self.library_view.currentIndex().row())
|
|
||||||
self.cover_flow_sync_timer.start(500)
|
|
||||||
else:
|
|
||||||
self.cover_flow_sync_timer.stop()
|
|
||||||
self.cover_flow.setVisible(False)
|
|
||||||
idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0)
|
|
||||||
if idx.isValid():
|
|
||||||
sm = self.library_view.selectionModel()
|
|
||||||
sm.select(idx, sm.ClearAndSelect|sm.Rows)
|
|
||||||
self.library_view.setCurrentIndex(idx)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Restrictions.
|
Restrictions.
|
||||||
Adding and deleting books creates a complexity. When added, they are
|
Adding and deleting books creates a complexity. When added, they are
|
||||||
@ -905,33 +523,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.search_restriction.setCurrentIndex(0)
|
self.search_restriction.setCurrentIndex(0)
|
||||||
self.apply_search_restriction('')
|
self.apply_search_restriction('')
|
||||||
|
|
||||||
def sync_cf_to_listview(self, current, previous):
|
|
||||||
if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
|
|
||||||
self.cover_flow.currentSlide() != current.row():
|
|
||||||
self.cover_flow.setCurrentSlide(current.row())
|
|
||||||
self.cover_flow_sync_flag = True
|
|
||||||
|
|
||||||
def cover_flow_do_sync(self):
|
|
||||||
self.cover_flow_sync_flag = True
|
|
||||||
try:
|
|
||||||
if self.cover_flow.isVisible() and self.cf_last_updated_at is not None and \
|
|
||||||
time.time() - self.cf_last_updated_at > 0.5:
|
|
||||||
self.cf_last_updated_at = None
|
|
||||||
row = self.cover_flow.currentSlide()
|
|
||||||
m = self.library_view.model()
|
|
||||||
index = m.index(row, 0)
|
|
||||||
if self.library_view.currentIndex().row() != row and index.isValid():
|
|
||||||
self.cover_flow_sync_flag = False
|
|
||||||
self.library_view.scroll_to_row(index.row())
|
|
||||||
sm = self.library_view.selectionModel()
|
|
||||||
sm.select(index, sm.ClearAndSelect|sm.Rows)
|
|
||||||
self.library_view.setCurrentIndex(index)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def sync_listview_to_cf(self, row):
|
|
||||||
self.cf_last_updated_at = time.time()
|
|
||||||
|
|
||||||
def another_instance_wants_to_talk(self):
|
def another_instance_wants_to_talk(self):
|
||||||
try:
|
try:
|
||||||
@ -1309,21 +900,21 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
Dispatcher(self._files_added), spare_server=self.spare_server)
|
Dispatcher(self._files_added), spare_server=self.spare_server)
|
||||||
self._adder.add_recursive(root, single)
|
self._adder.add_recursive(root, single)
|
||||||
|
|
||||||
def add_recursive_single(self, checked):
|
def add_recursive_single(self, *args):
|
||||||
'''
|
'''
|
||||||
Add books from the local filesystem to either the library or the device
|
Add books from the local filesystem to either the library or the device
|
||||||
recursively assuming one book per folder.
|
recursively assuming one book per folder.
|
||||||
'''
|
'''
|
||||||
self.add_recursive(True)
|
self.add_recursive(True)
|
||||||
|
|
||||||
def add_recursive_multiple(self, checked):
|
def add_recursive_multiple(self, *args):
|
||||||
'''
|
'''
|
||||||
Add books from the local filesystem to either the library or the device
|
Add books from the local filesystem to either the library or the device
|
||||||
recursively assuming multiple books per folder.
|
recursively assuming multiple books per folder.
|
||||||
'''
|
'''
|
||||||
self.add_recursive(False)
|
self.add_recursive(False)
|
||||||
|
|
||||||
def add_empty(self, checked):
|
def add_empty(self, *args):
|
||||||
'''
|
'''
|
||||||
Add an empty book item to the library. This does not import any formats
|
Add an empty book item to the library. This does not import any formats
|
||||||
from a book file.
|
from a book file.
|
||||||
@ -1383,7 +974,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
def add_filesystem_book(self, paths, allow_device=True):
|
def add_filesystem_book(self, paths, allow_device=True):
|
||||||
self._add_filesystem_book(paths, allow_device=allow_device)
|
self._add_filesystem_book(paths, allow_device=allow_device)
|
||||||
|
|
||||||
def add_books(self, checked):
|
def add_books(self, *args):
|
||||||
'''
|
'''
|
||||||
Add books from the local filesystem to either the library or the device.
|
Add books from the local filesystem to either the library or the device.
|
||||||
'''
|
'''
|
||||||
@ -2289,12 +1880,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
self.content_server = d.server
|
self.content_server = d.server
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
self.read_toolbar_settings()
|
||||||
self.search.search_as_you_type(config['search_as_you_type'])
|
self.search.search_as_you_type(config['search_as_you_type'])
|
||||||
self.tool_bar.setToolButtonStyle(
|
|
||||||
Qt.ToolButtonTextUnderIcon if \
|
|
||||||
config['show_text_in_toolbar'] else \
|
|
||||||
Qt.ToolButtonIconOnly)
|
|
||||||
self.save_menu.actions()[2].setText(
|
self.save_menu.actions()[2].setText(
|
||||||
_('Save only %s format to disk')%
|
_('Save only %s format to disk')%
|
||||||
prefs['output_format'].upper())
|
prefs['output_format'].upper())
|
||||||
@ -2451,12 +2038,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
geometry = config['main_window_geometry']
|
geometry = config['main_window_geometry']
|
||||||
if geometry is not None:
|
if geometry is not None:
|
||||||
self.restoreGeometry(geometry)
|
self.restoreGeometry(geometry)
|
||||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
self.read_toolbar_settings()
|
||||||
self.tool_bar.setToolButtonStyle(
|
|
||||||
Qt.ToolButtonTextUnderIcon if \
|
|
||||||
config['show_text_in_toolbar'] else \
|
|
||||||
Qt.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
|
|
||||||
def write_settings(self):
|
def write_settings(self):
|
||||||
config.set('main_window_geometry', self.saveGeometry())
|
config.set('main_window_geometry', self.saveGeometry())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user