mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
4a05ff80a7
@ -19,7 +19,7 @@ class Sueddeutsche(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
language = 'de'
|
||||
|
||||
encoding = 'iso-8859-15'
|
||||
encoding = 'utf-8'
|
||||
remove_javascript = True
|
||||
|
||||
|
||||
|
@ -7,16 +7,17 @@ __docformat__ = 'restructuredtext en'
|
||||
Module to implement the Cover Flow feature
|
||||
'''
|
||||
|
||||
import sys, os
|
||||
import sys, os, time
|
||||
|
||||
from PyQt4.QtGui import QImage, QSizePolicy
|
||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
|
||||
from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \
|
||||
QStackedLayout
|
||||
|
||||
from calibre import plugins
|
||||
from calibre.gui2 import config
|
||||
from calibre.gui2 import config, available_height, available_width
|
||||
pictureflow, pictureflowerror = plugins['pictureflow']
|
||||
|
||||
if pictureflow is not None:
|
||||
|
||||
class EmptyImageList(pictureflow.FlowImages):
|
||||
def __init__(self):
|
||||
pictureflow.FlowImages.__init__(self)
|
||||
@ -51,7 +52,7 @@ if pictureflow is not None:
|
||||
def __init__(self, model, buffer=20):
|
||||
pictureflow.FlowImages.__init__(self)
|
||||
self.model = model
|
||||
QObject.connect(self.model, SIGNAL('modelReset()'), self.reset)
|
||||
self.model.modelReset.connect(self.reset)
|
||||
|
||||
def count(self):
|
||||
return self.model.count()
|
||||
@ -66,7 +67,7 @@ if pictureflow is not None:
|
||||
return ans
|
||||
|
||||
def reset(self):
|
||||
self.emit(SIGNAL('dataChanged()'))
|
||||
self.dataChanged.emit()
|
||||
|
||||
def image(self, index):
|
||||
return self.model.cover(index)
|
||||
@ -74,13 +75,14 @@ if pictureflow is not None:
|
||||
|
||||
class CoverFlow(pictureflow.PictureFlow):
|
||||
|
||||
def __init__(self, height=300, parent=None, text_height=25):
|
||||
def __init__(self, parent=None):
|
||||
pictureflow.PictureFlow.__init__(self, parent,
|
||||
config['cover_flow_queue_length']+1)
|
||||
self.setSlideSize(QSize(int(2/3. * height), height))
|
||||
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+text_height))
|
||||
self.setMinimumSize(QSize(10, 10))
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
||||
QSizePolicy.Expanding))
|
||||
self.setZoomFactor(150)
|
||||
|
||||
def wheelEvent(self, ev):
|
||||
ev.accept()
|
||||
@ -95,6 +97,104 @@ else:
|
||||
DatabaseImages = 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/1.5), 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):
|
||||
return 0
|
||||
|
||||
@ -103,12 +203,12 @@ if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
w = QMainWindow()
|
||||
cf = CoverFlow()
|
||||
cf.resize(cf.minimumSize())
|
||||
w.resize(cf.minimumSize()+QSize(30, 20))
|
||||
cf.resize(int(available_width()/1.5), available_height()-60)
|
||||
w.resize(cf.size()+QSize(30, 20))
|
||||
path = sys.argv[1]
|
||||
model = FileSystemImages(sys.argv[1])
|
||||
cf.currentChanged[int].connect(model.currentChanged)
|
||||
cf.setImages(model)
|
||||
cf.connect(cf, SIGNAL('currentChanged(int)'), model.currentChanged)
|
||||
w.setCentralWidget(cf)
|
||||
|
||||
w.show()
|
||||
|
281
src/calibre/gui2/init.py
Normal file
281
src/calibre/gui2/init.py
Normal file
@ -0,0 +1,281 @@
|
||||
#!/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
|
||||
|
||||
for view in ('library', 'memory', 'card_a', 'card_b'):
|
||||
view = getattr(self, view+'_view')
|
||||
view.verticalHeader().sectionDoubleClicked.connect(self.view_specific_book)
|
||||
|
||||
|
||||
|
||||
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.
|
||||
'''
|
||||
|
||||
import re
|
||||
|
||||
from Queue import Empty, Queue
|
||||
|
||||
from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
|
||||
QTimer, SIGNAL, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
|
||||
QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip
|
||||
QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
|
||||
QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \
|
||||
QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication
|
||||
|
||||
from calibre.utils.ipc.server import Server
|
||||
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 import __appname__
|
||||
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
|
||||
class JobManager(QAbstractTableModel):
|
||||
|
||||
job_added = pyqtSignal(int)
|
||||
job_done = pyqtSignal(int)
|
||||
|
||||
def __init__(self):
|
||||
QAbstractTableModel.__init__(self)
|
||||
self.wait_icon = QVariant(QIcon(I('jobs.svg')))
|
||||
@ -37,8 +44,7 @@ class JobManager(QAbstractTableModel):
|
||||
self.changed_queue = Queue()
|
||||
|
||||
self.timer = QTimer(self)
|
||||
self.connect(self.timer, SIGNAL('timeout()'), self.update,
|
||||
Qt.QueuedConnection)
|
||||
self.timer.timeout.connect(self.update, type=Qt.QueuedConnection)
|
||||
self.timer.start(1000)
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
@ -130,8 +136,7 @@ class JobManager(QAbstractTableModel):
|
||||
for i, j in enumerate(self.jobs):
|
||||
if j.run_state == j.RUNNING:
|
||||
idx = self.index(i, 3)
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
||||
idx, idx)
|
||||
self.dataChanged.emit(idx, idx)
|
||||
|
||||
# Update parallel jobs
|
||||
jobs = set([])
|
||||
@ -157,20 +162,19 @@ class JobManager(QAbstractTableModel):
|
||||
self.jobs.sort()
|
||||
self.reset()
|
||||
if job.is_finished:
|
||||
self.emit(SIGNAL('job_done(int)'), len(self.unfinished_jobs()))
|
||||
self.job_done.emit(len(self.unfinished_jobs()))
|
||||
else:
|
||||
for job in jobs:
|
||||
idx = self.jobs.index(job)
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
|
||||
self.dataChanged.emit(
|
||||
self.index(idx, 0), self.index(idx, 3))
|
||||
|
||||
|
||||
def _add_job(self, job):
|
||||
self.emit(SIGNAL('layoutAboutToBeChanged()'))
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.jobs.append(job)
|
||||
self.jobs.sort()
|
||||
self.emit(SIGNAL('job_added(int)'), len(self.unfinished_jobs()))
|
||||
self.emit(SIGNAL('layoutChanged()'))
|
||||
self.job_added.emit(len(self.unfinished_jobs()))
|
||||
|
||||
def done_jobs(self):
|
||||
return [j for j in self.jobs if j.is_finished]
|
||||
@ -266,6 +270,76 @@ class DetailView(QDialog, Ui_Dialog):
|
||||
if more:
|
||||
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):
|
||||
@ -278,14 +352,9 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
||||
self.model = model
|
||||
self.setWindowModality(Qt.NonModal)
|
||||
self.setWindowTitle(__appname__ + _(' - Jobs'))
|
||||
self.connect(self.kill_button, SIGNAL('clicked()'),
|
||||
self.kill_job)
|
||||
self.connect(self.details_button, SIGNAL('clicked()'),
|
||||
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.kill_button.clicked.connect(self.kill_job)
|
||||
self.details_button.clicked.connect(self.show_details)
|
||||
self.stop_all_jobs_button.clicked.connect(self.kill_all_jobs)
|
||||
self.pb_delegate = ProgressBarDelegate(self)
|
||||
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
||||
self.jobs_view.doubleClicked.connect(self.show_job_details)
|
||||
@ -304,18 +373,18 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
||||
d.exec_()
|
||||
d.timer.stop()
|
||||
|
||||
def kill_job(self):
|
||||
def kill_job(self, *args):
|
||||
for index in self.jobs_view.selectedIndexes():
|
||||
row = index.row()
|
||||
self.model.kill_job(row, self)
|
||||
return
|
||||
|
||||
def show_details(self):
|
||||
def show_details(self, *args):
|
||||
for index in self.jobs_view.selectedIndexes():
|
||||
self.show_job_details(index)
|
||||
return
|
||||
|
||||
def kill_all_jobs(self):
|
||||
def kill_all_jobs(self, *args):
|
||||
self.model.kill_all_jobs()
|
||||
|
||||
def closeEvent(self, e):
|
||||
|
@ -426,6 +426,14 @@ class BooksView(QTableView): # {{{
|
||||
if dy != 0:
|
||||
self.column_header.update()
|
||||
|
||||
def scroll_to_row(self, row):
|
||||
if row > -1:
|
||||
h = self.horizontalHeader()
|
||||
for i in range(h.count()):
|
||||
if not h.isSectionHidden(i):
|
||||
self.scrollTo(self.model().index(row, i))
|
||||
break
|
||||
|
||||
def close(self):
|
||||
self._model.close()
|
||||
|
||||
|
@ -329,7 +329,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="library">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="Splitter" name="horizontal_splitter">
|
||||
<property name="orientation">
|
||||
@ -389,37 +389,43 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="BooksView" name="library_view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>100</horstretch>
|
||||
<verstretch>10</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropOverwriteMode">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DragDrop</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="cb_layout">
|
||||
<item>
|
||||
<widget class="BooksView" name="library_view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>100</horstretch>
|
||||
<verstretch>10</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropOverwriteMode">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DragDrop</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -85,6 +85,8 @@ typedef long PFreal;
|
||||
|
||||
typedef unsigned short QRgb565;
|
||||
|
||||
#define FONT_SIZE 18
|
||||
|
||||
#define RGB565_RED_MASK 0xF800
|
||||
#define RGB565_GREEN_MASK 0x07E0
|
||||
#define RGB565_BLUE_MASK 0x001F
|
||||
@ -540,6 +542,8 @@ void PictureFlowPrivate::showSlide(int index)
|
||||
|
||||
void PictureFlowPrivate::resize(int w, int h)
|
||||
{
|
||||
slideHeight = int(float(h)/2.);
|
||||
slideWidth = int(float(slideHeight) * 2/3.);
|
||||
recalc(w, h);
|
||||
resetSlides();
|
||||
triggerRender();
|
||||
@ -709,14 +713,14 @@ void PictureFlowPrivate::render()
|
||||
QPainter painter;
|
||||
painter.begin(&buffer);
|
||||
|
||||
QFont font("Arial", 14);
|
||||
QFont font("Arial", FONT_SIZE);
|
||||
font.setBold(true);
|
||||
painter.setFont(font);
|
||||
painter.setPen(Qt::white);
|
||||
//painter.setPen(QColor(255,255,255,127));
|
||||
|
||||
if (centerIndex < slideCount() && centerIndex > -1)
|
||||
painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2),
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-FONT_SIZE*3),
|
||||
Qt::AlignCenter, slideImages->caption(centerIndex));
|
||||
|
||||
painter.end();
|
||||
@ -759,7 +763,7 @@ void PictureFlowPrivate::render()
|
||||
QPainter painter;
|
||||
painter.begin(&buffer);
|
||||
|
||||
QFont font("Arial", 14);
|
||||
QFont font("Arial", FONT_SIZE);
|
||||
font.setBold(true);
|
||||
painter.setFont(font);
|
||||
|
||||
@ -768,12 +772,12 @@ void PictureFlowPrivate::render()
|
||||
|
||||
painter.setPen(QColor(255,255,255, (255-fade) ));
|
||||
if (leftTextIndex < sc && leftTextIndex > -1)
|
||||
painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2),
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3),
|
||||
Qt::AlignCenter, slideImages->caption(leftTextIndex));
|
||||
|
||||
painter.setPen(QColor(255,255,255, fade));
|
||||
if (leftTextIndex+1 < sc && leftTextIndex > -2)
|
||||
painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2),
|
||||
painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3),
|
||||
Qt::AlignCenter, slideImages->caption(leftTextIndex+1));
|
||||
|
||||
|
||||
|
@ -115,7 +115,8 @@ public:
|
||||
QSize slideSize() const;
|
||||
|
||||
/*!
|
||||
Sets the dimension of each slide (in pixels).
|
||||
Sets the dimension of each slide (in pixels). Do not use this method directly
|
||||
instead use resize which automatically sets an appropriate slide size.
|
||||
*/
|
||||
void setSlideSize(QSize size);
|
||||
|
||||
|
@ -16,6 +16,10 @@ public:
|
||||
virtual int count();
|
||||
virtual QImage image(int index);
|
||||
virtual QString caption(int index);
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -5,78 +5,13 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
from functools import partial
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
|
||||
toggle_texts = {
|
||||
@ -114,8 +49,6 @@ class SideBar(QToolBar):
|
||||
self.spacer = QWidget(self)
|
||||
self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
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.hide_cover_browser = partial(self._toggle_cover_browser,
|
||||
@ -124,9 +57,8 @@ class SideBar(QToolBar):
|
||||
if isinstance(ch, QToolButton):
|
||||
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):
|
||||
self.jobs_button.initialize(jobs_dialog)
|
||||
self.cover_browser, self.do_toggle_cover_browser = cover_browser, \
|
||||
toggle_cover_browser
|
||||
if self.cover_browser is None:
|
||||
@ -166,6 +98,7 @@ class SideBar(QToolBar):
|
||||
'book_info'), type=Qt.QueuedConnection)
|
||||
self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed,
|
||||
'tag_browser'), type=Qt.QueuedConnection)
|
||||
self.addWidget(jobs_button)
|
||||
|
||||
|
||||
|
||||
@ -211,30 +144,4 @@ class SideBar(QToolBar):
|
||||
def _toggle_book_info(self, show=None):
|
||||
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.utils.search_query_parser import saved_searches
|
||||
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): # {{{
|
||||
|
||||
@ -29,12 +31,16 @@ class TagsView(QTreeView): # {{{
|
||||
tag_item_renamed = pyqtSignal()
|
||||
search_item_renamed = pyqtSignal()
|
||||
|
||||
def __init__(self, *args):
|
||||
QTreeView.__init__(self, *args)
|
||||
def __init__(self, parent=None):
|
||||
QTreeView.__init__(self, parent=None)
|
||||
self.tag_match = None
|
||||
self.setUniformRowHeights(True)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
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):
|
||||
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 PyQt4.Qt import Qt, SIGNAL, QObject, QUrl, QTimer, \
|
||||
QModelIndex, QPixmap, QColor, QPainter, QMenu, QIcon, \
|
||||
QToolButton, QDialog, QDesktopServices, \
|
||||
QDialog, QDesktopServices, \
|
||||
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
||||
QMessageBox, QStackedLayout, QHelpEvent, QInputDialog,\
|
||||
QMessageBox, QHelpEvent, QInputDialog,\
|
||||
QThread, pyqtSignal
|
||||
from PyQt4.QtSvg import QSvgRenderer
|
||||
|
||||
@ -35,10 +35,9 @@ from calibre.gui2 import warning_dialog, choose_files, error_dialog, \
|
||||
question_dialog,\
|
||||
pixmap_to_data, choose_dir, \
|
||||
Dispatcher, gprefs, \
|
||||
available_height, \
|
||||
max_available_height, config, info_dialog, \
|
||||
available_width, GetMetadata
|
||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||
GetMetadata
|
||||
from calibre.gui2.cover_flow import pictureflowerror, CoverFlowMixin
|
||||
from calibre.gui2.widgets import ProgressIndicator, IMAGE_EXTENSIONS
|
||||
from calibre.gui2.wizard import move_library
|
||||
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_ui import Ui_MainWindow
|
||||
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_bulk import MetadataBulkDialog
|
||||
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.caches import CoverCache
|
||||
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.tag_view import TagBrowserMixin
|
||||
from calibre.gui2.init import ToolbarMixin, LibraryViewMixin
|
||||
|
||||
class SaveMenu(QMenu):
|
||||
|
||||
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):
|
||||
class Listener(Thread): # {{{
|
||||
|
||||
def __init__(self, listener):
|
||||
Thread.__init__(self)
|
||||
@ -102,7 +88,9 @@ class Listener(Thread):
|
||||
except:
|
||||
pass
|
||||
|
||||
class SystemTrayIcon(QSystemTrayIcon):
|
||||
# }}}
|
||||
|
||||
class SystemTrayIcon(QSystemTrayIcon): # {{{
|
||||
|
||||
def __init__(self, icon, parent):
|
||||
QSystemTrayIcon.__init__(self, icon, parent)
|
||||
@ -115,7 +103,10 @@ class SystemTrayIcon(QSystemTrayIcon):
|
||||
return True
|
||||
return QSystemTrayIcon.event(self, ev)
|
||||
|
||||
class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
# }}}
|
||||
|
||||
class Main(MainWindow, Ui_MainWindow, DeviceGUI, ToolbarMixin,
|
||||
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin):
|
||||
'The main GUI'
|
||||
|
||||
def set_default_thumbnail(self, height):
|
||||
@ -234,7 +225,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.connect(self.system_tray_icon,
|
||||
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
|
||||
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 ########################
|
||||
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)
|
||||
|
||||
####################### Setup Toolbar #####################
|
||||
md = QMenu()
|
||||
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)
|
||||
ToolbarMixin.__init__(self)
|
||||
|
||||
####################### Library view ########################
|
||||
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.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)
|
||||
LibraryViewMixin.__init__(self, db)
|
||||
|
||||
self.show()
|
||||
|
||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||
self.hide_windows()
|
||||
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.cover_cache = CoverCache(self.library_path)
|
||||
self.cover_cache.start()
|
||||
self.library_view.model().cover_cache = self.cover_cache
|
||||
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit)
|
||||
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,
|
||||
self.restriction_count_changed):
|
||||
self.library_view.model().count_changed_signal.connect(x)
|
||||
@ -578,46 +311,29 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
type=Qt.QueuedConnection)
|
||||
|
||||
########################### Tags Browser ##############################
|
||||
TagBrowserMixin.__init__(self, db)
|
||||
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.search_restriction.setMinimumContentsLength(10)
|
||||
|
||||
########################### Cover Flow ################################
|
||||
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
|
||||
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.library.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)
|
||||
|
||||
CoverFlowMixin.__init__(self)
|
||||
|
||||
self._calculated_available_height = min(max_available_height()-15,
|
||||
self.height())
|
||||
self.resize(self.width(), self._calculated_available_height)
|
||||
self.search.setMaximumWidth(self.width()-150)
|
||||
|
||||
# Jobs Button {{{
|
||||
self.jobs_button = JobsButton()
|
||||
self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
|
||||
# }}}
|
||||
|
||||
####################### 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.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']:
|
||||
@ -638,40 +354,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.connect(self.scheduler,
|
||||
SIGNAL('start_recipe_fetch(PyQt_PyObject)'),
|
||||
self.download_scheduled_recipe, Qt.QueuedConnection)
|
||||
self.library_view.verticalHeader().sectionClicked.connect(self.view_specific_book)
|
||||
|
||||
for view in ('library', 'memory', 'card_a', 'card_b'):
|
||||
view = getattr(self, view+'_view')
|
||||
view.verticalHeader().sectionDoubleClicked.connect(self.view_specific_book)
|
||||
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
|
||||
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||
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):
|
||||
d = SavedSearchEditor(self, search)
|
||||
@ -761,81 +449,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
error_dialog(self, _('Failed to start content server'),
|
||||
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.scrollTo(self.library_view.currentIndex())
|
||||
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.scrollTo(self.library_view.currentIndex())
|
||||
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.
|
||||
Adding and deleting books creates a complexity. When added, they are
|
||||
@ -906,32 +519,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.search_restriction.setCurrentIndex(0)
|
||||
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
|
||||
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):
|
||||
try:
|
||||
@ -1309,21 +896,21 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
Dispatcher(self._files_added), spare_server=self.spare_server)
|
||||
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
|
||||
recursively assuming one book per folder.
|
||||
'''
|
||||
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
|
||||
recursively assuming multiple books per folder.
|
||||
'''
|
||||
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
|
||||
from a book file.
|
||||
@ -1383,7 +970,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
def add_filesystem_book(self, paths, allow_device=True):
|
||||
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.
|
||||
'''
|
||||
@ -2298,12 +1885,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
d.exec_()
|
||||
self.content_server = d.server
|
||||
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.tool_bar.setToolButtonStyle(
|
||||
Qt.ToolButtonTextUnderIcon if \
|
||||
config['show_text_in_toolbar'] else \
|
||||
Qt.ToolButtonIconOnly)
|
||||
self.save_menu.actions()[2].setText(
|
||||
_('Save only %s format to disk')%
|
||||
prefs['output_format'].upper())
|
||||
@ -2460,12 +2043,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
geometry = config['main_window_geometry']
|
||||
if geometry is not None:
|
||||
self.restoreGeometry(geometry)
|
||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||
self.tool_bar.setToolButtonStyle(
|
||||
Qt.ToolButtonTextUnderIcon if \
|
||||
config['show_text_in_toolbar'] else \
|
||||
Qt.ToolButtonIconOnly)
|
||||
|
||||
self.read_toolbar_settings()
|
||||
|
||||
def write_settings(self):
|
||||
config.set('main_window_geometry', self.saveGeometry())
|
||||
|
Loading…
x
Reference in New Issue
Block a user