mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from custcol trunk
This commit is contained in:
commit
6f9063705e
@ -18,7 +18,8 @@ class ANDROID(USBMS):
|
||||
FORMATS = ['epub', 'pdf']
|
||||
|
||||
VENDOR_ID = {
|
||||
0x0bb4 : { 0x0c02 : [0x100], 0x0c01 : [0x100]},
|
||||
# HTC
|
||||
0x0bb4 : { 0x0c02 : [0x100], 0x0c01 : [0x100], 0x0ff9 : [0x0100]},
|
||||
|
||||
# Motorola
|
||||
0x22b8 : { 0x41d9 : [0x216], 0x2d67 : [0x100], 0x41db : [0x216]},
|
||||
|
@ -69,13 +69,15 @@ class PRS505(CLI, Device):
|
||||
|
||||
def write_cache(prefix):
|
||||
try:
|
||||
cachep = os.path.join(prefix, self.CACHE_XML)
|
||||
cachep = os.path.join(prefix, *(self.CACHE_XML.split('/')))
|
||||
if not os.path.exists(cachep):
|
||||
dname = os.path.dirname(cachep)
|
||||
if not os.path.exists(dname):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(cachep), mode=0777)
|
||||
os.makedirs(dname, mode=0777)
|
||||
except:
|
||||
time.sleep(5)
|
||||
os.makedirs(os.path.dirname(cachep), mode=0777)
|
||||
os.makedirs(dname, mode=0777)
|
||||
with open(cachep, 'wb') as f:
|
||||
f.write(u'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<cache xmlns="http://www.kinoma.com/FskCache/1">
|
||||
|
@ -1190,6 +1190,7 @@ class Manifest(object):
|
||||
if item in self.ids:
|
||||
item = self.ids[item]
|
||||
del self.ids[item.id]
|
||||
if item.href in self.hrefs:
|
||||
del self.hrefs[item.href]
|
||||
self.items.remove(item)
|
||||
if item in self.oeb.spine:
|
||||
|
@ -184,11 +184,14 @@ class MessageBox(QMessageBox):
|
||||
|
||||
|
||||
|
||||
def warning_dialog(parent, title, msg, det_msg='', show=False):
|
||||
def warning_dialog(parent, title, msg, det_msg='', show=False,
|
||||
show_copy_button=True):
|
||||
d = MessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok,
|
||||
parent, det_msg)
|
||||
d.setEscapeButton(QMessageBox.Ok)
|
||||
d.setIconPixmap(QPixmap(I('dialog_warning.svg')))
|
||||
if not show_copy_button:
|
||||
d.cb.setVisible(False)
|
||||
if show:
|
||||
return d.exec_()
|
||||
return d
|
||||
@ -205,11 +208,14 @@ def error_dialog(parent, title, msg, det_msg='', show=False,
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def question_dialog(parent, title, msg, det_msg=''):
|
||||
def question_dialog(parent, title, msg, det_msg='', show_copy_button=True):
|
||||
d = MessageBox(QMessageBox.Question, title, msg, QMessageBox.Yes|QMessageBox.No,
|
||||
parent, det_msg)
|
||||
d.setIconPixmap(QPixmap(I('dialog_information.svg')))
|
||||
d.setEscapeButton(QMessageBox.No)
|
||||
if not show_copy_button:
|
||||
d.cb.setVisible(False)
|
||||
|
||||
return d.exec_() == QMessageBox.Yes
|
||||
|
||||
def info_dialog(parent, title, msg, det_msg='', show=False):
|
||||
|
@ -656,7 +656,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
_('The selected column is not a custom column'), show=True)
|
||||
if not question_dialog(self, _('Are you sure?'),
|
||||
_('Do you really want to delete column %s and all its data?') %
|
||||
self.custcols[col]['name']):
|
||||
self.custcols[col]['name'], show_copy_button=False):
|
||||
return
|
||||
self.columns.item(idx).setCheckState(False)
|
||||
self.columns.takeItem(idx)
|
||||
@ -831,7 +831,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
warning_dialog(self, _('Must restart'),
|
||||
_('The changes you made require that Calibre be '
|
||||
'restarted. Please restart as soon as practical.'),
|
||||
show=True)
|
||||
show=True, show_copy_button=False)
|
||||
self.parent.must_restart_before_config = True
|
||||
QDialog.accept(self)
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create a custom column</string>
|
||||
<string>Create or edit custom columns</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
@ -126,7 +126,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create and edit custom columns</string>
|
||||
<string>Create or edit custom columns</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -180,6 +180,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
self.formats_changed = True
|
||||
|
||||
def get_selected_format_metadata(self):
|
||||
old = prefs['read_file_metadata']
|
||||
if not old:
|
||||
prefs['read_file_metadata'] = True
|
||||
try:
|
||||
row = self.formats.currentRow()
|
||||
fmt = self.formats.item(row)
|
||||
if fmt is None:
|
||||
@ -201,6 +205,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
error_dialog(self, _('Could not read metadata'),
|
||||
_('Could not read metadata from %s format')%ext).exec_()
|
||||
return None, None
|
||||
finally:
|
||||
if old != prefs['read_file_metadata']:
|
||||
prefs['read_file_metadata'] = old
|
||||
|
||||
def set_metadata_from_format(self):
|
||||
mi, ext = self.get_selected_format_metadata()
|
||||
|
@ -150,7 +150,7 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<layout class="QHBoxLayout" name="hl234">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
@ -287,10 +287,12 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="Splitter" name="vertical_splitter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>100</verstretch>
|
||||
</sizepolicy>
|
||||
@ -554,6 +556,18 @@
|
||||
<widget class="StatusBar" name="status_bar" native="true"/>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SideBar" name="sidebar" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="tool_bar">
|
||||
@ -832,6 +846,12 @@
|
||||
<header>calibre/gui2/widgets.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SideBar</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>calibre/gui2/sidebar.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../resources/images.qrc"/>
|
||||
|
235
src/calibre/gui2/sidebar.py
Normal file
235
src/calibre/gui2/sidebar.py
Normal file
@ -0,0 +1,235 @@
|
||||
#!/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 re
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \
|
||||
QFrame, QVBoxLayout, QLabel, QSize, QCoreApplication, 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
|
||||
self.jobs_dialog.jobs_view.restore_column_widths()
|
||||
|
||||
def mouseReleaseEvent(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.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 = {
|
||||
'book_info' : (_('Show Book Details'), _('Hide Book Details')),
|
||||
'tag_browser' : (_('Show Tag Browser'), _('Hide Tag Browser')),
|
||||
'cover_browser': (_('Show Cover Browser'), _('Hide Cover Browser')),
|
||||
}
|
||||
toggle_icons = {
|
||||
'book_info' : 'book.svg',
|
||||
'tag_browser' : 'tags.svg',
|
||||
'cover_browser': 'cover_flow.svg',
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QToolBar.__init__(self, _('Side bar'), parent)
|
||||
self.setOrientation(Qt.Vertical)
|
||||
self.setMovable(False)
|
||||
self.setFloatable(False)
|
||||
self.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.setIconSize(QSize(48, 48))
|
||||
|
||||
for ac in ('book_info', 'tag_browser', 'cover_browser'):
|
||||
action = self.addAction(QIcon(I(self.toggle_icons[ac])),
|
||||
self.toggle_texts[ac][1], getattr(self, '_toggle_'+ac))
|
||||
setattr(self, 'action_toggle_'+ac, action)
|
||||
w = self.widgetForAction(action)
|
||||
w.setCheckable(True)
|
||||
setattr(self, 'show_'+ac, partial(getattr(self, '_toggle_'+ac),
|
||||
show=True))
|
||||
setattr(self, 'hide_'+ac, partial(getattr(self, '_toggle_'+ac),
|
||||
show=False))
|
||||
|
||||
|
||||
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,
|
||||
show=False)
|
||||
for ch in self.children():
|
||||
if isinstance(ch, QToolButton):
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
def initialize(self, jobs_dialog, 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:
|
||||
self.action_toggle_cover_browser.setEnabled(False)
|
||||
self.action_toggle_cover_browser.setText(
|
||||
_('Cover browser could not be loaded: ') + cover_browser_error)
|
||||
else:
|
||||
self.cover_browser.stop.connect(self.hide_cover_browser)
|
||||
self._toggle_cover_browser(dynamic.get('cover_flow_visible', False))
|
||||
|
||||
self.horizontal_splitter = horizontal_splitter
|
||||
self.vertical_splitter = vertical_splitter
|
||||
|
||||
tb_state = dynamic.get('tag_browser_state', None)
|
||||
if tb_state is not None:
|
||||
self.horizontal_splitter.restoreState(tb_state)
|
||||
|
||||
bi_state = dynamic.get('book_info_state', None)
|
||||
if bi_state is not None:
|
||||
self.vertical_splitter.restoreState(bi_state)
|
||||
self.horizontal_splitter.initialize()
|
||||
self.vertical_splitter.initialize()
|
||||
self.view_status_changed('book_info', not
|
||||
self.vertical_splitter.is_side_index_hidden)
|
||||
self.view_status_changed('tag_browser', not
|
||||
self.horizontal_splitter.is_side_index_hidden)
|
||||
self.vertical_splitter.state_changed.connect(partial(self.view_status_changed,
|
||||
'book_info'), type=Qt.QueuedConnection)
|
||||
self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed,
|
||||
'tag_browser'), type=Qt.QueuedConnection)
|
||||
|
||||
|
||||
|
||||
def view_status_changed(self, name, visible):
|
||||
action = getattr(self, 'action_toggle_'+name)
|
||||
texts = self.toggle_texts[name]
|
||||
action.setText(texts[int(visible)])
|
||||
w = self.widgetForAction(action)
|
||||
w.setCheckable(True)
|
||||
w.setChecked(visible)
|
||||
|
||||
def location_changed(self, location):
|
||||
is_lib = location == 'library'
|
||||
for ac in ('cover_browser', 'tag_browser'):
|
||||
ac = getattr(self, 'action_toggle_'+ac)
|
||||
ac.setEnabled(is_lib)
|
||||
self.widgetForAction(ac).setVisible(is_lib)
|
||||
|
||||
def save_state(self):
|
||||
dynamic.set('cover_flow_visible', self.is_cover_browser_visible)
|
||||
dynamic.set('tag_browser_state',
|
||||
str(self.horizontal_splitter.saveState()))
|
||||
dynamic.set('book_info_state',
|
||||
str(self.vertical_splitter.saveState()))
|
||||
|
||||
|
||||
@property
|
||||
def is_cover_browser_visible(self):
|
||||
return self.cover_browser is not None and self.cover_browser.isVisible()
|
||||
|
||||
def _toggle_cover_browser(self, show=None):
|
||||
if show is None:
|
||||
show = not self.is_cover_browser_visible
|
||||
self.do_toggle_cover_browser(show)
|
||||
self.view_status_changed('cover_browser', show)
|
||||
|
||||
def external_cover_flow_finished(self, *args):
|
||||
self.view_status_changed('cover_browser', False)
|
||||
|
||||
def _toggle_tag_browser(self, show=None):
|
||||
self.horizontal_splitter.toggle_side_index()
|
||||
|
||||
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)
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, re, collections
|
||||
import os, collections
|
||||
|
||||
from PyQt4.QtGui import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \
|
||||
QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame
|
||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication, pyqtSignal
|
||||
QSizePolicy, QScrollArea
|
||||
from PyQt4.QtCore import Qt, QSize, pyqtSignal
|
||||
|
||||
from calibre import fit_image, preferred_encoding, isosx
|
||||
from calibre.gui2 import config
|
||||
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.notify import get_notifier
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.library.comments import comments_to_html
|
||||
@ -17,6 +16,7 @@ from calibre.library.comments import comments_to_html
|
||||
class BookInfoDisplay(QWidget):
|
||||
|
||||
DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS+BOOK_EXTENSIONS
|
||||
files_dropped = pyqtSignal(object, object)
|
||||
|
||||
@classmethod
|
||||
def paths_from_event(cls, event):
|
||||
@ -40,8 +40,7 @@ class BookInfoDisplay(QWidget):
|
||||
def dropEvent(self, event):
|
||||
paths = self.paths_from_event(event)
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
self.emit(SIGNAL('files_dropped(PyQt_PyObject, PyQt_PyObject)'), event,
|
||||
paths)
|
||||
self.files_dropped.emit(event, paths)
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
event.acceptProposedAction()
|
||||
@ -87,6 +86,9 @@ class BookInfoDisplay(QWidget):
|
||||
|
||||
|
||||
class BookDataDisplay(QLabel):
|
||||
|
||||
mr = pyqtSignal(int)
|
||||
|
||||
def __init__(self):
|
||||
QLabel.__init__(self)
|
||||
self.setText('')
|
||||
@ -94,7 +96,7 @@ class BookInfoDisplay(QWidget):
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
|
||||
|
||||
def mouseReleaseEvent(self, ev):
|
||||
self.emit(SIGNAL('mr(int)'), 1)
|
||||
self.mr.emit(1)
|
||||
|
||||
WEIGHTS = collections.defaultdict(lambda : 100)
|
||||
WEIGHTS[_('Path')] = 0
|
||||
@ -103,6 +105,8 @@ class BookInfoDisplay(QWidget):
|
||||
WEIGHTS[_('Series')] = 2
|
||||
WEIGHTS[_('Tags')] = 3
|
||||
|
||||
show_book_info = pyqtSignal()
|
||||
|
||||
def __init__(self, clear_message):
|
||||
QWidget.__init__(self)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
@ -113,14 +117,14 @@ class BookInfoDisplay(QWidget):
|
||||
self.cover_display = BookInfoDisplay.BookCoverDisplay()
|
||||
self._layout.addWidget(self.cover_display)
|
||||
self.book_data = BookInfoDisplay.BookDataDisplay()
|
||||
self.connect(self.book_data, SIGNAL('mr(int)'), self.mouseReleaseEvent)
|
||||
self.book_data.mr.connect(self.mouseReleaseEvent)
|
||||
self._layout.addWidget(self.book_data)
|
||||
self.data = {}
|
||||
self.setVisible(False)
|
||||
self._layout.setAlignment(self.cover_display, Qt.AlignTop|Qt.AlignLeft)
|
||||
|
||||
def mouseReleaseEvent(self, ev):
|
||||
self.emit(SIGNAL('show_book_info()'))
|
||||
self.show_book_info.emit()
|
||||
|
||||
def show_data(self, data):
|
||||
if data.has_key('cover'):
|
||||
@ -128,7 +132,7 @@ class BookInfoDisplay(QWidget):
|
||||
else:
|
||||
self.cover_display.setPixmap(self.cover_display.default_pixmap)
|
||||
|
||||
rows = u''
|
||||
rows, comments = [], ''
|
||||
self.book_data.setText('')
|
||||
self.data = data.copy()
|
||||
keys = data.keys()
|
||||
@ -142,97 +146,43 @@ class BookInfoDisplay(QWidget):
|
||||
if isinstance(txt, str):
|
||||
txt = txt.decode(preferred_encoding, 'replace')
|
||||
if key == _('Comments'):
|
||||
txt = comments_to_html(txt)
|
||||
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
|
||||
self.book_data.setText(u'<table>'+rows+u'</table>')
|
||||
comments = comments_to_html(txt)
|
||||
else:
|
||||
rows.append((key, txt))
|
||||
rows = '\n'.join([u'<tr><td valign="top"><b>%s:</b></td><td valign="top">%s</td></tr>'%(k,t) for
|
||||
k, t in rows])
|
||||
if comments:
|
||||
comments = '<b>Comments:</b>'+comments
|
||||
left_pane = u'<table>%s</table>'%rows
|
||||
right_pane = u'<div>%s</div>'%comments
|
||||
self.book_data.setText(u'<table><tr><td valign="top" '
|
||||
'style="padding-right:2em">%s</td><td valign="top">%s</td></tr></table>'
|
||||
% (left_pane, right_pane))
|
||||
|
||||
self.clear_message()
|
||||
self.book_data.updateGeometry()
|
||||
self.updateGeometry()
|
||||
self.setVisible(True)
|
||||
|
||||
class MovieButton(QFrame):
|
||||
|
||||
def __init__(self, jobs_dialog):
|
||||
QFrame.__init__(self)
|
||||
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.Fixed)
|
||||
self.jobs_dialog = jobs_dialog
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.setToolTip(_('Click to see list of active jobs.'))
|
||||
self.jobs_dialog.jobs_view.restore_column_widths()
|
||||
|
||||
def mouseReleaseEvent(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.pi.isAnimated()
|
||||
|
||||
def start(self):
|
||||
self.pi.startAnimation()
|
||||
|
||||
def stop(self):
|
||||
self.pi.stopAnimation()
|
||||
|
||||
|
||||
class CoverFlowButton(QToolButton):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QToolButton.__init__(self, parent)
|
||||
self.setIconSize(QSize(80, 80))
|
||||
self.setIcon(QIcon(I('cover_flow.svg')))
|
||||
self.setCheckable(True)
|
||||
self.setChecked(False)
|
||||
self.setAutoRaise(True)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding))
|
||||
self.connect(self, SIGNAL('toggled(bool)'), self.adjust_tooltip)
|
||||
self.adjust_tooltip(False)
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
|
||||
def adjust_tooltip(self, on):
|
||||
tt = _('Click to turn off Cover Browsing') if on else _('Click to browse books by their covers')
|
||||
self.setToolTip(tt)
|
||||
|
||||
def disable(self, reason):
|
||||
self.setDisabled(True)
|
||||
self.setToolTip(_('<p>Browsing books by their covers is disabled.<br>Import of pictureflow module failed:<br>')+reason)
|
||||
|
||||
|
||||
class StatusBar(QStatusBar):
|
||||
|
||||
resized = pyqtSignal(object)
|
||||
files_dropped = pyqtSignal(object, object)
|
||||
show_book_info = pyqtSignal()
|
||||
|
||||
def initialize(self, jobs_dialog, systray=None):
|
||||
def initialize(self, systray=None):
|
||||
self.systray = systray
|
||||
self.notifier = get_notifier(systray)
|
||||
self.movie_button = MovieButton(jobs_dialog)
|
||||
self.cover_flow_button = CoverFlowButton()
|
||||
self.addPermanentWidget(self.cover_flow_button)
|
||||
self.addPermanentWidget(self.movie_button)
|
||||
self.book_info = BookInfoDisplay(self.clearMessage)
|
||||
self.book_info.setAcceptDrops(True)
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setWidget(self.book_info)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.connect(self.book_info, SIGNAL('show_book_info()'), self.show_book_info)
|
||||
self.connect(self.book_info,
|
||||
SIGNAL('files_dropped(PyQt_PyObject,PyQt_PyObject)'),
|
||||
self.files_dropped, Qt.QueuedConnection)
|
||||
self.book_info.show_book_info.connect(self.show_book_info.emit,
|
||||
type=Qt.QueuedConnection)
|
||||
self.book_info.files_dropped.connect(self.files_dropped.emit,
|
||||
type=Qt.QueuedConnection)
|
||||
self.addWidget(self.scroll_area, 100)
|
||||
self.setMinimumHeight(120)
|
||||
self.resized.connect(self.book_info.cover_display.relayout)
|
||||
@ -241,10 +191,6 @@ class StatusBar(QStatusBar):
|
||||
def resizeEvent(self, ev):
|
||||
self.resized.emit(self.size())
|
||||
|
||||
def files_dropped(self, event, paths):
|
||||
self.emit(SIGNAL('files_dropped(PyQt_PyObject, PyQt_PyObject)'), event,
|
||||
paths)
|
||||
|
||||
def reset_info(self):
|
||||
self.book_info.show_data({})
|
||||
|
||||
@ -259,33 +205,4 @@ class StatusBar(QStatusBar):
|
||||
self.notifier(msg)
|
||||
return ret
|
||||
|
||||
def jobs(self):
|
||||
src = unicode(self.movie_button.jobs.text())
|
||||
return int(re.search(r'\d+', src).group())
|
||||
|
||||
def show_book_info(self):
|
||||
self.emit(SIGNAL('show_book_info()'))
|
||||
|
||||
def job_added(self, nnum):
|
||||
jobs = self.movie_button.jobs
|
||||
src = unicode(jobs.text())
|
||||
num = self.jobs()
|
||||
text = src.replace(str(num), str(nnum))
|
||||
jobs.setText(text)
|
||||
self.movie_button.start()
|
||||
|
||||
def job_done(self, nnum):
|
||||
jobs = self.movie_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.movie_button.is_running:
|
||||
self.movie_button.stop()
|
||||
QCoreApplication.instance().alert(self, 5000)
|
||||
|
||||
|
||||
|
@ -169,7 +169,7 @@ class TagTreeItem(object):
|
||||
|
||||
def category_data(self, role):
|
||||
if role == Qt.DisplayRole:
|
||||
return self.name
|
||||
return QVariant(self.py_name + ' [%d]'%len(self.children))
|
||||
if role == Qt.DecorationRole:
|
||||
return self.icon
|
||||
if role == Qt.FontRole:
|
||||
|
@ -262,16 +262,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
SIGNAL('update_found(PyQt_PyObject)'), self.update_found)
|
||||
self.update_checker.start(2000)
|
||||
####################### Status Bar #####################
|
||||
self.status_bar.initialize(self.jobs_dialog, self.system_tray_icon)
|
||||
#self.setStatusBar(self.status_bar)
|
||||
QObject.connect(self.job_manager, SIGNAL('job_added(int)'),
|
||||
self.status_bar.job_added, Qt.QueuedConnection)
|
||||
QObject.connect(self.job_manager, SIGNAL('job_done(int)'),
|
||||
self.status_bar.job_done, Qt.QueuedConnection)
|
||||
QObject.connect(self.status_bar, SIGNAL('show_book_info()'),
|
||||
self.show_book_info)
|
||||
QObject.connect(self.status_bar, SIGNAL('files_dropped(PyQt_PyObject,PyQt_PyObject)'),
|
||||
self.files_dropped_on_book)
|
||||
self.status_bar.initialize(self.system_tray_icon)
|
||||
self.status_bar.show_book_info.connect(self.show_book_info)
|
||||
self.status_bar.files_dropped.connect(self.files_dropped_on_book)
|
||||
|
||||
####################### Setup Toolbar #####################
|
||||
md = QMenu()
|
||||
md.addAction(_('Edit metadata individually'))
|
||||
@ -459,6 +453,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
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 ########################
|
||||
similar_menu = QMenu(_('Similar books...'))
|
||||
similar_menu.addAction(self.action_books_by_same_author)
|
||||
@ -554,12 +552,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.cover_cache = CoverCache(self.library_path)
|
||||
self.cover_cache.start()
|
||||
self.library_view.model().cover_cache = self.cover_cache
|
||||
self.tags_view.setVisible(False)
|
||||
self.tag_match.setVisible(False)
|
||||
self.popularity.setVisible(False)
|
||||
self.restriction_label.setVisible(False)
|
||||
self.edit_categories.setVisible(False)
|
||||
self.search_restriction.setVisible(False)
|
||||
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_edit_categories)
|
||||
self.tags_view.set_database(db, self.tag_match, self.popularity, self.search_restriction)
|
||||
self.connect(self.tags_view,
|
||||
@ -626,22 +618,28 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
if not config['separate_cover_flow']:
|
||||
self.library.layout().addWidget(self.cover_flow)
|
||||
self.cover_flow.currentChanged.connect(self.sync_listview_to_cf)
|
||||
self.connect(self.status_bar.cover_flow_button,
|
||||
SIGNAL('toggled(bool)'), self.toggle_cover_flow)
|
||||
self.connect(self.cover_flow, SIGNAL('stop()'),
|
||||
self.status_bar.cover_flow_button.toggle)
|
||||
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)
|
||||
else:
|
||||
self.status_bar.cover_flow_button.disable(pictureflowerror)
|
||||
|
||||
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)
|
||||
|
||||
####################### Side Bar ###############################
|
||||
|
||||
self.sidebar.initialize(self.jobs_dialog, 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']:
|
||||
from calibre.library.server import start_threaded_server
|
||||
from calibre.library import server_config
|
||||
@ -668,19 +666,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
|
||||
if self.cover_flow is not None and dynamic.get('cover_flow_visible', False):
|
||||
self.status_bar.cover_flow_button.toggle()
|
||||
|
||||
tb_state = dynamic.get('tag_browser_state', None)
|
||||
if tb_state is not None:
|
||||
self.horizontal_splitter.restoreState(tb_state)
|
||||
self.toggle_tags_view(True)
|
||||
|
||||
bi_state = dynamic.get('book_info_state', None)
|
||||
if bi_state is not None:
|
||||
self.vertical_splitter.restoreState(bi_state)
|
||||
self.horizontal_splitter.initialize()
|
||||
self.vertical_splitter.initialize()
|
||||
|
||||
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
|
||||
v = self.library_view
|
||||
@ -782,11 +767,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
if search:
|
||||
self.search.set_search_string(join.join(search))
|
||||
|
||||
|
||||
|
||||
def uncheck_cover_button(self, *args):
|
||||
self.status_bar.cover_flow_button.setChecked(False)
|
||||
|
||||
def toggle_cover_flow(self, show):
|
||||
if config['separate_cover_flow']:
|
||||
if show:
|
||||
@ -802,8 +782,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
||||
self.library_view.scrollTo(self.library_view.currentIndex())
|
||||
d.show()
|
||||
self.connect(d, SIGNAL('finished(int)'),
|
||||
self.uncheck_cover_button)
|
||||
d.finished.connect(self.sidebar.external_cover_flow_finished)
|
||||
self.cf_dialog = d
|
||||
self.cover_flow_sync_timer.start(500)
|
||||
else:
|
||||
@ -825,8 +804,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.library_view.currentIndex())
|
||||
self.cover_flow.setVisible(True)
|
||||
self.cover_flow.setFocus(Qt.OtherFocusReason)
|
||||
#self.status_bar.book_info.book_data.setMaximumHeight(100)
|
||||
#self.status_bar.setMaximumHeight(120)
|
||||
self.library_view.scrollTo(self.library_view.currentIndex())
|
||||
self.cover_flow_sync_timer.start(500)
|
||||
else:
|
||||
@ -837,26 +814,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
sm = self.library_view.selectionModel()
|
||||
sm.select(idx, sm.ClearAndSelect|sm.Rows)
|
||||
self.library_view.setCurrentIndex(idx)
|
||||
#self.status_bar.book_info.book_data.setMaximumHeight(1000)
|
||||
#self.resize(self.width(), self._calculated_available_height)
|
||||
#self.setMaximumHeight(available_height())
|
||||
|
||||
def toggle_tags_view(self, show):
|
||||
if show:
|
||||
self.tags_view.setVisible(True)
|
||||
self.tag_match.setVisible(True)
|
||||
self.popularity.setVisible(True)
|
||||
self.restriction_label.setVisible(True)
|
||||
self.edit_categories.setVisible(True)
|
||||
self.search_restriction.setVisible(True)
|
||||
self.tags_view.setFocus(Qt.OtherFocusReason)
|
||||
else:
|
||||
self.tags_view.setVisible(False)
|
||||
self.tag_match.setVisible(False)
|
||||
self.popularity.setVisible(False)
|
||||
self.restriction_label.setVisible(False)
|
||||
self.edit_categories.setVisible(False)
|
||||
self.search_restriction.setVisible(False)
|
||||
|
||||
|
||||
'''
|
||||
Handling of the count of books in a restricted view requires that
|
||||
@ -887,7 +846,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.restriction_count_of_books_in_view = self.current_view().row_count()
|
||||
t = _("({0} of {1})").format(self.current_view().row_count(),
|
||||
self.restriction_count_of_books_in_view)
|
||||
self.search_count.setStyleSheet('QLabel { background-color: yellow; }')
|
||||
self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }')
|
||||
else: # No restriction
|
||||
if all == 'yes':
|
||||
t = _("(all books)")
|
||||
@ -2330,6 +2289,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
view.resizeColumnsToContents()
|
||||
view.resize_on_select = False
|
||||
self.status_bar.reset_info()
|
||||
self.sidebar.location_changed(location)
|
||||
if location == 'library':
|
||||
self.action_edit.setEnabled(True)
|
||||
self.action_merge.setEnabled(True)
|
||||
@ -2337,7 +2297,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.view_menu.actions()[1].setEnabled(True)
|
||||
self.action_open_containing_folder.setEnabled(True)
|
||||
self.action_sync.setEnabled(True)
|
||||
self.status_bar.cover_flow_button.setEnabled(True)
|
||||
for action in list(self.delete_menu.actions())[1:]:
|
||||
action.setEnabled(True)
|
||||
else:
|
||||
@ -2347,7 +2306,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.view_menu.actions()[1].setEnabled(False)
|
||||
self.action_open_containing_folder.setEnabled(False)
|
||||
self.action_sync.setEnabled(False)
|
||||
self.status_bar.cover_flow_button.setEnabled(False)
|
||||
for action in list(self.delete_menu.actions())[1:]:
|
||||
action.setEnabled(False)
|
||||
|
||||
@ -2463,11 +2421,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
def write_settings(self):
|
||||
config.set('main_window_geometry', self.saveGeometry())
|
||||
dynamic.set('sort_history', self.library_view.model().sort_history)
|
||||
dynamic.set('cover_flow_visible', self.cover_flow.isVisible())
|
||||
dynamic.set('tag_browser_state',
|
||||
str(self.horizontal_splitter.saveState()))
|
||||
dynamic.set('book_info_state',
|
||||
str(self.vertical_splitter.saveState()))
|
||||
self.sidebar.save_state()
|
||||
self.library_view.write_settings()
|
||||
if self.device_connected:
|
||||
self.save_device_view_settings()
|
||||
|
@ -982,6 +982,12 @@ class SplitterHandle(QSplitterHandle):
|
||||
|
||||
class Splitter(QSplitter):
|
||||
|
||||
state_changed = pyqtSignal(object)
|
||||
|
||||
def __init__(self, *args):
|
||||
QSplitter.__init__(self, *args)
|
||||
self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection)
|
||||
|
||||
def createHandle(self):
|
||||
return SplitterHandle(self.orientation(), self)
|
||||
|
||||
@ -990,6 +996,22 @@ class Splitter(QSplitter):
|
||||
h = self.handle(i)
|
||||
if h is not None:
|
||||
h.splitter_moved()
|
||||
self.state_changed.emit(not self.is_side_index_hidden)
|
||||
|
||||
def splitter_moved(self, *args):
|
||||
self.state_changed.emit(not self.is_side_index_hidden)
|
||||
|
||||
@property
|
||||
def side_index(self):
|
||||
return 0 if self.orientation() == Qt.Horizontal else 1
|
||||
|
||||
@property
|
||||
def is_side_index_hidden(self):
|
||||
sizes = list(self.sizes())
|
||||
return sizes[self.side_index] == 0
|
||||
|
||||
def toggle_side_index(self):
|
||||
self.double_clicked(None)
|
||||
|
||||
def double_clicked(self, handle):
|
||||
sizes = list(self.sizes())
|
||||
@ -997,8 +1019,7 @@ class Splitter(QSplitter):
|
||||
idx = sizes.index(0)
|
||||
sizes[idx] = 80
|
||||
else:
|
||||
idx = 0 if self.orientation() == Qt.Horizontal else 1
|
||||
sizes[idx] = 0
|
||||
sizes[self.side_index] = 0
|
||||
self.setSizes(sizes)
|
||||
self.initialize()
|
||||
|
||||
|
@ -145,6 +145,8 @@ class CustomColumns(object):
|
||||
if v['normalized']:
|
||||
tn = 'custom_column_{0}'.format(i)
|
||||
self.tag_browser_categories[tn] = [v['label'], 'value']
|
||||
if v['datatype'] == 'rating':
|
||||
self.tag_browser_formatters[tn] = lambda x:u'\u2605'*int(round(x/2.))
|
||||
|
||||
|
||||
def get_custom(self, idx, label=None, num=None, index_is_id=False):
|
||||
|
@ -127,6 +127,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
'authors' : ['author', 'name'],
|
||||
'news' : ['news', 'name'],
|
||||
}
|
||||
self.tag_browser_formatters = {}
|
||||
|
||||
self.connect()
|
||||
self.is_case_sensitive = not iswindows and not isosx and \
|
||||
@ -632,11 +633,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
else:
|
||||
icon = icon_map['*custom']
|
||||
tooltip = self.custom_column_label_map[category]['name']
|
||||
if ids is None: # no filtering
|
||||
categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon, tooltip = tooltip)
|
||||
for r in data if r[2] > 0]
|
||||
else: # filter out zero-count tags
|
||||
categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon, tooltip = tooltip)
|
||||
formatter = self.tag_browser_formatters.get(tn, lambda x: x)
|
||||
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0], icon=icon, tooltip = tooltip)
|
||||
for r in data if r[2] > 0]
|
||||
categories['format'] = []
|
||||
for fmt in self.conn.get('SELECT DISTINCT format FROM data'):
|
||||
|
Loading…
x
Reference in New Issue
Block a user