Re-organize code in gui2.library module

This commit is contained in:
Kovid Goyal 2010-05-17 17:17:42 -06:00
parent d393b430bd
commit d600ef514b
11 changed files with 632 additions and 588 deletions

View File

@ -32,8 +32,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.search.setMinimumContentsLength(25) self.search.setMinimumContentsLength(25)
self.search.initialize('scheduler_search_history') self.search.initialize('scheduler_search_history')
self.recipe_box.layout().insertWidget(0, self.search) self.recipe_box.layout().insertWidget(0, self.search)
self.connect(self.search, SIGNAL('search(PyQt_PyObject,PyQt_PyObject)'), self.search.search.connect(self.recipe_model.search)
self.recipe_model.search)
self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'),
self.search.search_done) self.search.search_done)
self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'),

View File

@ -0,0 +1,9 @@
#!/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'

View File

@ -0,0 +1,311 @@
#!/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 sys
from math import cos, sin, pi
from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
QIcon, QDoubleSpinBox, QVariant, QSpinBox, \
QStyledItemDelegate, QCompleter, \
QComboBox
from calibre.gui2 import UNDEFINED_QDATE
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
from calibre.utils.date import now
from calibre.utils.config import tweaks
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
class RatingDelegate(QStyledItemDelegate): # {{{
COLOR = QColor("blue")
SIZE = 16
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self._parent = parent
self.dummy = QModelIndex()
self.star_path = QPainterPath()
self.star_path.moveTo(90, 50)
for i in range(1, 5):
self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
50 + 40 * sin(0.8 * i * pi))
self.star_path.closeSubpath()
self.star_path.setFillRule(Qt.WindingFill)
gradient = QLinearGradient(0, 0, 0, 100)
gradient.setColorAt(0.0, self.COLOR)
gradient.setColorAt(1.0, self.COLOR)
self.brush = QBrush(gradient)
self.factor = self.SIZE/100.
def sizeHint(self, option, index):
#num = index.model().data(index, Qt.DisplayRole).toInt()[0]
return QSize(5*(self.SIZE), self.SIZE+4)
def paint(self, painter, option, index):
style = self._parent.style()
option = QStyleOptionViewItemV4(option)
self.initStyleOption(option, self.dummy)
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
def draw_star():
painter.save()
painter.scale(self.factor, self.factor)
painter.translate(50.0, 50.0)
painter.rotate(-20)
painter.translate(-50.0, -50.0)
painter.drawPath(self.star_path)
painter.restore()
painter.save()
if hasattr(QStyle, 'CE_ItemViewItem'):
style.drawControl(QStyle.CE_ItemViewItem, option,
painter, self._parent)
elif option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
try:
painter.setRenderHint(QPainter.Antialiasing)
painter.setClipRect(option.rect)
y = option.rect.center().y()-self.SIZE/2.
x = option.rect.left()
painter.setPen(self.PEN)
painter.setBrush(self.brush)
painter.translate(x, y)
i = 0
while i < num:
draw_star()
painter.translate(self.SIZE, 0)
i += 1
except:
import traceback
traceback.print_exc()
painter.restore()
def createEditor(self, parent, option, index):
sb = QStyledItemDelegate.createEditor(self, parent, option, index)
sb.setMinimum(0)
sb.setMaximum(5)
return sb
# }}}
class DateDelegate(QStyledItemDelegate): # {{{
def displayText(self, val, locale):
d = val.toDate()
if d == UNDEFINED_QDATE:
return ''
return d.toString('dd MMM yyyy')
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
stdformat = unicode(qde.displayFormat())
if 'yyyy' not in stdformat:
stdformat = stdformat.replace('yy', 'yyyy')
qde.setDisplayFormat(stdformat)
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
# }}}
class PubDateDelegate(QStyledItemDelegate): # {{{
def displayText(self, val, locale):
d = val.toDate()
if d == UNDEFINED_QDATE:
return ''
format = tweaks['gui_pubdate_display_format']
if format is None:
format = 'MMM yyyy'
return d.toString(format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM yyyy')
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
# }}}
class TextDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent):
'''
Delegate for text data. If auto_complete_function needs to return a list
of text items to auto-complete with. The funciton is None no
auto-complete will be used.
'''
QStyledItemDelegate.__init__(self, parent)
self.auto_complete_function = None
def set_auto_complete_function(self, f):
self.auto_complete_function = f
def createEditor(self, parent, option, index):
editor = EnLineEdit(parent)
if self.auto_complete_function:
complete_items = [i[1] for i in self.auto_complete_function()]
completer = QCompleter(complete_items, self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setCompletionMode(QCompleter.InlineCompletion)
editor.setCompleter(completer)
return editor
#}}}
class TagsDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self.db = None
def set_database(self, db):
self.db = db
def createEditor(self, parent, option, index):
if self.db:
col = index.model().column_map[index.column()]
if not index.model().is_custom_column(col):
editor = TagsLineEdit(parent, self.db.all_tags())
else:
editor = TagsLineEdit(parent, sorted(list(self.db.all_custom(label=col))))
return editor
else:
editor = EnLineEdit(parent)
return editor
# }}}
class CcDateDelegate(QStyledItemDelegate): # {{{
'''
Delegate for custom columns dates. Because this delegate stores the
format as an instance variable, a new instance must be created for each
column. This differs from all the other delegates.
'''
def set_format(self, format):
if not format:
self.format = 'dd MMM yyyy'
else:
self.format = format
def displayText(self, val, locale):
d = val.toDate()
if d == UNDEFINED_QDATE:
return ''
return d.toString(self.format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.format)
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
def setEditorData(self, editor, index):
m = index.model()
# db col is not named for the field, but for the table number. To get it,
# gui column -> column label -> table number -> db column
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]]
if val is None:
val = now()
editor.setDate(val)
def setModelData(self, editor, model, index):
val = editor.date()
if val == UNDEFINED_QDATE:
val = None
model.setData(index, QVariant(val), Qt.EditRole)
# }}}
class CcTextDelegate(QStyledItemDelegate): # {{{
'''
Delegate for text/int/float data.
'''
def createEditor(self, parent, option, index):
m = index.model()
col = m.column_map[index.column()]
typ = m.custom_columns[col]['datatype']
if typ == 'int':
editor = QSpinBox(parent)
editor.setRange(-100, sys.maxint)
editor.setSpecialValueText(_('Undefined'))
editor.setSingleStep(1)
elif typ == 'float':
editor = QDoubleSpinBox(parent)
editor.setSpecialValueText(_('Undefined'))
editor.setRange(-100., float(sys.maxint))
editor.setDecimals(2)
else:
editor = EnLineEdit(parent)
complete_items = sorted(list(m.db.all_custom(label=col)))
completer = QCompleter(complete_items, self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setCompletionMode(QCompleter.PopupCompletion)
editor.setCompleter(completer)
return editor
# }}}
class CcCommentsDelegate(QStyledItemDelegate): # {{{
'''
Delegate for comments data.
'''
def createEditor(self, parent, option, index):
m = index.model()
col = m.column_map[index.column()]
# db col is not named for the field, but for the table number. To get it,
# gui column -> column label -> table number -> db column
text = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[col]['num']]]
editor = CommentsDialog(parent, text)
d = editor.exec_()
if d:
m.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole)
return None
def setModelData(self, editor, model, index):
model.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole)
# }}}
class CcBoolDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent):
'''
Delegate for custom_column bool data.
'''
QStyledItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
editor = QComboBox(parent)
items = [_('Y'), _('N'), ' ']
icons = [I('ok.svg'), I('list_remove.svg'), I('blank.svg')]
if tweaks['bool_custom_columns_are_tristate'] == 'no':
items = items[:-1]
icons = icons[:-1]
for icon, text in zip(icons, items):
editor.addItem(QIcon(icon), text)
return editor
def setModelData(self, editor, model, index):
val = {0:True, 1:False, 2:None}[editor.currentIndex()]
model.setData(index, QVariant(val), Qt.EditRole)
def setEditorData(self, editor, index):
m = index.model()
# db col is not named for the field, but for the table number. To get it,
# gui column -> column label -> table number -> db column
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]]
if tweaks['bool_custom_columns_are_tristate'] == 'no':
val = 1 if not val else 0
else:
val = 2 if val is None else 1 if not val else 0
editor.setCurrentIndex(val)
# }}}

View File

@ -1,324 +1,42 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, textwrap, traceback, re, shutil, functools, sys import shutil, functools, re, os, traceback
from operator import attrgetter
from math import cos, sin, pi
from contextlib import closing from contextlib import closing
from operator import attrgetter
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \ from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QPainterPath, QLinearGradient, QBrush, \ QModelIndex, QVariant, QDate
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
QIcon, QImage, QMenu, QSpinBox, QDoubleSpinBox, \
QStyledItemDelegate, QCompleter, \
QComboBox
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \
SIGNAL, QObject, QSize, QModelIndex, QDate
from calibre import strftime from calibre.gui2 import NONE, config, UNDEFINED_QDATE
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.gui2 import NONE, config, error_dialog, UNDEFINED_QDATE
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.date import dt_factory, qt_to_dt, isoformat, now from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.utils.pyparsing import ParseException from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
from calibre import strftime
# Delegates {{{ def human_readable(size, precision=1):
""" Convert a size in bytes into megabytes """
return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
class DummyDelegate(QStyledItemDelegate): TIME_FMT = '%d %b %Y'
def sizeHint(self, option, index):
return QSize(0, 0)
def paint(self, painter, option, index):
pass
class RatingDelegate(QStyledItemDelegate):
COLOR = QColor("blue")
SIZE = 16
PEN = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self._parent = parent
self.dummy = QModelIndex()
self.star_path = QPainterPath()
self.star_path.moveTo(90, 50)
for i in range(1, 5):
self.star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
50 + 40 * sin(0.8 * i * pi))
self.star_path.closeSubpath()
self.star_path.setFillRule(Qt.WindingFill)
gradient = QLinearGradient(0, 0, 0, 100)
gradient.setColorAt(0.0, self.COLOR)
gradient.setColorAt(1.0, self.COLOR)
self.brush = QBrush(gradient)
self.factor = self.SIZE/100.
def sizeHint(self, option, index):
#num = index.model().data(index, Qt.DisplayRole).toInt()[0]
return QSize(5*(self.SIZE), self.SIZE+4)
def paint(self, painter, option, index):
style = self._parent.style()
option = QStyleOptionViewItemV4(option)
self.initStyleOption(option, self.dummy)
num = index.model().data(index, Qt.DisplayRole).toInt()[0]
def draw_star():
painter.save()
painter.scale(self.factor, self.factor)
painter.translate(50.0, 50.0)
painter.rotate(-20)
painter.translate(-50.0, -50.0)
painter.drawPath(self.star_path)
painter.restore()
painter.save()
if hasattr(QStyle, 'CE_ItemViewItem'):
style.drawControl(QStyle.CE_ItemViewItem, option,
painter, self._parent)
elif option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
try:
painter.setRenderHint(QPainter.Antialiasing)
painter.setClipRect(option.rect)
y = option.rect.center().y()-self.SIZE/2.
x = option.rect.left()
painter.setPen(self.PEN)
painter.setBrush(self.brush)
painter.translate(x, y)
i = 0
while i < num:
draw_star()
painter.translate(self.SIZE, 0)
i += 1
except:
traceback.print_exc()
painter.restore()
def createEditor(self, parent, option, index):
sb = QStyledItemDelegate.createEditor(self, parent, option, index)
sb.setMinimum(0)
sb.setMaximum(5)
return sb
class DateDelegate(QStyledItemDelegate):
def displayText(self, val, locale):
d = val.toDate()
if d == UNDEFINED_QDATE:
return ''
return d.toString('dd MMM yyyy')
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
stdformat = unicode(qde.displayFormat())
if 'yyyy' not in stdformat:
stdformat = stdformat.replace('yy', 'yyyy')
qde.setDisplayFormat(stdformat)
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
class PubDateDelegate(QStyledItemDelegate):
def displayText(self, val, locale):
d = val.toDate()
if d == UNDEFINED_QDATE:
return ''
format = tweaks['gui_pubdate_display_format']
if format is None:
format = 'MMM yyyy'
return d.toString(format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM yyyy')
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
class TextDelegate(QStyledItemDelegate):
def __init__(self, parent):
'''
Delegate for text data. If auto_complete_function needs to return a list
of text items to auto-complete with. The funciton is None no
auto-complete will be used.
'''
QStyledItemDelegate.__init__(self, parent)
self.auto_complete_function = None
def set_auto_complete_function(self, f):
self.auto_complete_function = f
def createEditor(self, parent, option, index):
editor = EnLineEdit(parent)
if self.auto_complete_function:
complete_items = [i[1] for i in self.auto_complete_function()]
completer = QCompleter(complete_items, self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setCompletionMode(QCompleter.InlineCompletion)
editor.setCompleter(completer)
return editor
class TagsDelegate(QStyledItemDelegate):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self.db = None
def set_database(self, db):
self.db = db
def createEditor(self, parent, option, index):
if self.db:
col = index.model().column_map[index.column()]
if not index.model().is_custom_column(col):
editor = TagsLineEdit(parent, self.db.all_tags())
else:
editor = TagsLineEdit(parent, sorted(list(self.db.all_custom(label=col))))
return editor
else:
editor = EnLineEdit(parent)
return editor
class CcDateDelegate(QStyledItemDelegate):
'''
Delegate for custom columns dates. Because this delegate stores the
format as an instance variable, a new instance must be created for each
column. This differs from all the other delegates.
'''
def set_format(self, format):
if not format:
self.format = 'dd MMM yyyy'
else:
self.format = format
def displayText(self, val, locale):
d = val.toDate()
if d == UNDEFINED_QDATE:
return ''
return d.toString(self.format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.format)
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
def setEditorData(self, editor, index):
m = index.model()
# db col is not named for the field, but for the table number. To get it,
# gui column -> column label -> table number -> db column
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]]
if val is None:
val = now()
editor.setDate(val)
def setModelData(self, editor, model, index):
val = editor.date()
if val == UNDEFINED_QDATE:
val = None
model.setData(index, QVariant(val), Qt.EditRole)
class CcTextDelegate(QStyledItemDelegate):
'''
Delegate for text/int/float data.
'''
def createEditor(self, parent, option, index):
m = index.model()
col = m.column_map[index.column()]
typ = m.custom_columns[col]['datatype']
if typ == 'int':
editor = QSpinBox(parent)
editor.setRange(-100, sys.maxint)
editor.setSpecialValueText(_('Undefined'))
editor.setSingleStep(1)
elif typ == 'float':
editor = QDoubleSpinBox(parent)
editor.setSpecialValueText(_('Undefined'))
editor.setRange(-100., float(sys.maxint))
editor.setDecimals(2)
else:
editor = EnLineEdit(parent)
complete_items = sorted(list(m.db.all_custom(label=col)))
completer = QCompleter(complete_items, self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setCompletionMode(QCompleter.PopupCompletion)
editor.setCompleter(completer)
return editor
class CcCommentsDelegate(QStyledItemDelegate):
'''
Delegate for comments data.
'''
def createEditor(self, parent, option, index):
m = index.model()
col = m.column_map[index.column()]
# db col is not named for the field, but for the table number. To get it,
# gui column -> column label -> table number -> db column
text = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[col]['num']]]
editor = CommentsDialog(parent, text)
d = editor.exec_()
if d:
m.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole)
return None
def setModelData(self, editor, model, index):
model.setData(index, QVariant(editor.textbox.toPlainText()), Qt.EditRole)
class CcBoolDelegate(QStyledItemDelegate):
def __init__(self, parent):
'''
Delegate for custom_column bool data.
'''
QStyledItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
editor = QComboBox(parent)
items = [_('Y'), _('N'), ' ']
icons = [I('ok.svg'), I('list_remove.svg'), I('blank.svg')]
if tweaks['bool_custom_columns_are_tristate'] == 'no':
items = items[:-1]
icons = icons[:-1]
for icon, text in zip(icons, items):
editor.addItem(QIcon(icon), text)
return editor
def setModelData(self, editor, model, index):
val = {0:True, 1:False, 2:None}[editor.currentIndex()]
model.setData(index, QVariant(val), Qt.EditRole)
def setEditorData(self, editor, index):
m = index.model()
# db col is not named for the field, but for the table number. To get it,
# gui column -> column label -> table number -> db column
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]]
if tweaks['bool_custom_columns_are_tristate'] == 'no':
val = 1 if not val else 0
else:
val = 2 if val is None else 1 if not val else 0
editor.setCurrentIndex(val)
# }}}
class BooksModel(QAbstractTableModel): # {{{ class BooksModel(QAbstractTableModel): # {{{
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
sorting_done = pyqtSignal(object, name='sortingDone') sorting_done = pyqtSignal(object, name='sortingDone')
database_changed = pyqtSignal(object, name='databaseChanged') database_changed = pyqtSignal(object, name='databaseChanged')
new_bookdisplay_data = pyqtSignal(object)
count_changed_signal = pyqtSignal(int)
searched = pyqtSignal(object)
orig_headers = { orig_headers = {
'title' : _("Title"), 'title' : _("Title"),
@ -408,7 +126,7 @@ class BooksModel(QAbstractTableModel): # {{{
id = self.db.id(row) id = self.db.id(row)
self.cover_cache.refresh([id]) self.cover_cache.refresh([id])
if row == current_row: if row == current_row:
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), self.new_bookdisplay_data.emit(
self.get_book_display_info(row)) self.get_book_display_info(row))
self.dataChanged.emit(self.index(row, 0), self.index(row, self.dataChanged.emit(self.index(row, 0), self.index(row,
self.columnCount(QModelIndex())-1)) self.columnCount(QModelIndex())-1))
@ -435,7 +153,7 @@ class BooksModel(QAbstractTableModel): # {{{
return ret return ret
def count_changed(self, *args): def count_changed(self, *args):
self.emit(SIGNAL('count_changed(int)'), self.db.count()) self.count_changed_signal.emit(self.db.count())
def row_indices(self, index): def row_indices(self, index):
''' Return list indices of all cells in index.row()''' ''' Return list indices of all cells in index.row()'''
@ -478,14 +196,14 @@ class BooksModel(QAbstractTableModel): # {{{
try: try:
self.db.search(text) self.db.search(text)
except ParseException: except ParseException:
self.emit(SIGNAL('searched(PyQt_PyObject)'), False) self.searched.emit(False)
return return
self.last_search = text self.last_search = text
if reset: if reset:
self.clear_caches() self.clear_caches()
self.reset() self.reset()
if self.last_search: if self.last_search:
self.emit(SIGNAL('searched(PyQt_PyObject)'), True) self.searched.emit(True)
def sort(self, col, order, reset=True): def sort(self, col, order, reset=True):
@ -584,7 +302,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.set_cache(idx) self.set_cache(idx)
data = self.get_book_display_info(idx) data = self.get_book_display_info(idx)
if emit_signal: if emit_signal:
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data) self.new_bookdisplay_data.emit(data)
else: else:
return data return data
@ -981,8 +699,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False)) self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
else: else:
self.db.set(row, column, val) self.db.set(row, column, val)
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ self.dataChanged.emit(index, index)
index, index)
return True return True
def set_search_restriction(self, s): def set_search_restriction(self, s):
@ -990,241 +707,7 @@ class BooksModel(QAbstractTableModel): # {{{
# }}} # }}}
class BooksView(QTableView): # {{{ class OnDeviceSearch(SearchQueryParser): # {{{
TIME_FMT = '%d %b %Y'
wrapper = textwrap.TextWrapper(width=20)
@classmethod
def wrap(cls, s, width=20):
cls.wrapper.width = width
return cls.wrapper.fill(s)
@classmethod
def human_readable(cls, size, precision=1):
""" Convert a size in bytes into megabytes """
return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
def __init__(self, parent, modelcls=BooksModel):
QTableView.__init__(self, parent)
self.rating_delegate = RatingDelegate(self)
self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self)
self.tags_delegate = TagsDelegate(self)
self.authors_delegate = TextDelegate(self)
self.series_delegate = TextDelegate(self)
self.publisher_delegate = TextDelegate(self)
self.text_delegate = TextDelegate(self)
self.cc_text_delegate = CcTextDelegate(self)
self.cc_bool_delegate = CcBoolDelegate(self)
self.cc_comments_delegate = CcCommentsDelegate(self)
self.display_parent = parent
self._model = modelcls(self)
self.setModel(self._model)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True)
self.selectionModel().currentRowChanged.connect(self._model.current_changed)
self.column_header = self.horizontalHeader()
self._model.database_changed.connect(self.database_changed)
hv = self.verticalHeader()
hv.setClickable(True)
hv.setCursor(Qt.PointingHandCursor)
self.selected_ids = []
self._model.about_to_be_sorted.connect(self.about_to_be_sorted)
self._model.sorting_done.connect(self.sorting_done)
def about_to_be_sorted(self, idc):
selected_rows = [r.row() for r in self.selectionModel().selectedRows()]
self.selected_ids = [idc(r) for r in selected_rows]
def sorting_done(self, indexc):
if self.selected_ids:
indices = [self.model().index(indexc(i), 0) for i in
self.selected_ids]
sm = self.selectionModel()
for idx in indices:
sm.select(idx, sm.Select|sm.Rows)
self.selected_ids = []
def set_ondevice_column_visibility(self):
m = self._model
self.column_header.setSectionHidden(m.column_map.index('ondevice'),
not m.device_connected)
def set_device_connected(self, is_connected):
self._model.set_device_connected(is_connected)
self.set_ondevice_column_visibility()
def database_changed(self, db):
for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate, self.pubdate_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate())
cm = self._model.column_map
self.set_ondevice_column_visibility()
for colhead in cm:
if self._model.is_custom_column(colhead):
cc = self._model.custom_columns[colhead]
if cc['datatype'] == 'datetime':
delegate = CcDateDelegate(self)
delegate.set_format(cc['display'].get('date_format',''))
self.setItemDelegateForColumn(cm.index(colhead), delegate)
elif cc['datatype'] == 'comments':
self.setItemDelegateForColumn(cm.index(colhead), self.cc_comments_delegate)
elif cc['datatype'] == 'text':
if cc['is_multiple']:
self.setItemDelegateForColumn(cm.index(colhead), self.tags_delegate)
else:
self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate)
elif cc['datatype'] in ('int', 'float'):
self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate)
elif cc['datatype'] == 'bool':
self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate)
elif cc['datatype'] == 'rating':
self.setItemDelegateForColumn(cm.index(colhead), self.rating_delegate)
else:
dattr = colhead+'_delegate'
delegate = colhead if hasattr(self, dattr) else 'text'
self.setItemDelegateForColumn(cm.index(colhead), getattr(self,
delegate+'_delegate'))
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, delete, similar_menu=None):
self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.context_menu = QMenu(self)
if edit_metadata is not None:
self.context_menu.addAction(edit_metadata)
if send_to_device is not None:
self.context_menu.addAction(send_to_device)
if convert is not None:
self.context_menu.addAction(convert)
self.context_menu.addAction(view)
self.context_menu.addAction(save)
if open_folder is not None:
self.context_menu.addAction(open_folder)
if delete is not None:
self.context_menu.addAction(delete)
if book_details is not None:
self.context_menu.addAction(book_details)
if similar_menu is not None:
self.context_menu.addMenu(similar_menu)
def contextMenuEvent(self, event):
self.context_menu.popup(event.globalPos())
event.accept()
def restore_sort_at_startup(self, saved_history):
if tweaks['sort_columns_at_startup'] is not None:
saved_history = tweaks['sort_columns_at_startup']
if saved_history is None:
return
for col,order in reversed(saved_history):
self.sortByColumn(col, order)
self.model().sort_history = saved_history
def sortByColumn(self, colname, order):
try:
idx = self._model.column_map.index(colname)
except ValueError:
idx = 0
QTableView.sortByColumn(self, idx, order)
@classmethod
def paths_from_event(cls, event):
'''
Accept a drop event and return a list of paths that can be read from
and represent files with extensions.
'''
if event.mimeData().hasFormat('text/uri-list'):
urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
def dragEnterEvent(self, event):
if int(event.possibleActions() & Qt.CopyAction) + \
int(event.possibleActions() & Qt.MoveAction) == 0:
return
paths = self.paths_from_event(event)
if paths:
event.acceptProposedAction()
def dragMoveEvent(self, event):
event.acceptProposedAction()
def dropEvent(self, event):
paths = self.paths_from_event(event)
event.setDropAction(Qt.CopyAction)
event.accept()
self.emit(SIGNAL('files_dropped(PyQt_PyObject)'), paths)
def set_database(self, db):
self._model.set_database(db)
self.tags_delegate.set_database(db)
self.authors_delegate.set_auto_complete_function(db.all_authors)
self.series_delegate.set_auto_complete_function(db.all_series)
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
def close(self):
self._model.close()
def set_editable(self, editable):
self._model.set_editable(editable)
def connect_to_search_box(self, sb, search_done):
QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
self._model.search)
self._search_done = search_done
self.connect(self._model, SIGNAL('searched(PyQt_PyObject)'),
self.search_done)
def connect_to_restriction_set(self, tv):
QObject.connect(tv, SIGNAL('restriction_set(PyQt_PyObject)'),
self._model.set_search_restriction) # must be synchronous (not queued)
def connect_to_book_display(self, bd):
QObject.connect(self._model, SIGNAL('new_bookdisplay_data(PyQt_PyObject)'),
bd)
def search_done(self, ok):
self._search_done(self, ok)
def row_count(self):
return self._model.count()
# }}}
class DeviceBooksView(BooksView):
def __init__(self, parent):
BooksView.__init__(self, parent, DeviceBooksModel)
self.columns_resized = False
self.resize_on_select = False
self.rating_delegate = None
for i in range(10):
self.setItemDelegateForColumn(i, TextDelegate(self))
self.setDragDropMode(self.NoDragDrop)
self.setAcceptDrops(False)
def set_database(self, db):
self._model.set_database(db)
def resizeColumnsToContents(self):
QTableView.resizeColumnsToContents(self)
self.columns_resized = True
def connect_dirtied_signal(self, slot):
QObject.connect(self._model, SIGNAL('booklist_dirtied()'), slot)
def sortByColumn(self, col, order):
QTableView.sortByColumn(self, col, order)
def dropEvent(self, *args):
error_dialog(self, _('Not allowed'),
_('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_()
class OnDeviceSearch(SearchQueryParser):
def __init__(self, model): def __init__(self, model):
SearchQueryParser.__init__(self) SearchQueryParser.__init__(self)
@ -1282,8 +765,11 @@ class OnDeviceSearch(SearchQueryParser):
traceback.print_exc() traceback.print_exc()
return matches return matches
# }}}
class DeviceBooksModel(BooksModel): class DeviceBooksModel(BooksModel): # {{{
booklist_dirtied = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
BooksModel.__init__(self, parent) BooksModel.__init__(self, parent)
@ -1300,7 +786,7 @@ class DeviceBooksModel(BooksModel):
self.marked_for_deletion[job] = self.indices(rows) self.marked_for_deletion[job] = self.indices(rows)
for row in rows: for row in rows:
indices = self.row_indices(row) indices = self.row_indices(row)
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1]) self.dataChanged.emit(indices[0], indices[-1])
def deletion_done(self, job, succeeded=True): def deletion_done(self, job, succeeded=True):
if not self.marked_for_deletion.has_key(job): if not self.marked_for_deletion.has_key(job):
@ -1309,7 +795,7 @@ class DeviceBooksModel(BooksModel):
for row in rows: for row in rows:
if not succeeded: if not succeeded:
indices = self.row_indices(self.index(row, 0)) indices = self.row_indices(self.index(row, 0))
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), indices[0], indices[-1]) self.dataChanged.emit(indices[0], indices[-1])
def paths_deleted(self, paths): def paths_deleted(self, paths):
self.map = list(range(0, len(self.db))) self.map = list(range(0, len(self.db)))
@ -1339,7 +825,7 @@ class DeviceBooksModel(BooksModel):
try: try:
matches = self.search_engine.parse(text) matches = self.search_engine.parse(text)
except ParseException: except ParseException:
self.emit(SIGNAL('searched(PyQt_PyObject)'), False) self.searched.emit(False)
return return
self.map = [] self.map = []
@ -1351,7 +837,7 @@ class DeviceBooksModel(BooksModel):
self.reset() self.reset()
self.last_search = text self.last_search = text
if self.last_search: if self.last_search:
self.emit(SIGNAL('searched(PyQt_PyObject)'), True) self.searched.emit(False)
def resort(self, reset): def resort(self, reset):
@ -1443,7 +929,7 @@ class DeviceBooksModel(BooksModel):
dt = dt_factory(item.datetime, assume_utc=True) dt = dt_factory(item.datetime, assume_utc=True)
data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False) data[_('Timestamp')] = isoformat(dt, sep=' ', as_utc=False)
data[_('Tags')] = ', '.join(item.tags) data[_('Tags')] = ', '.join(item.tags)
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data) self.new_bookdisplay_data.emit(data)
def paths(self, rows): def paths(self, rows):
return [self.db[self.map[r.row()]].path for r in rows ] return [self.db[self.map[r.row()]].path for r in rows ]
@ -1471,11 +957,11 @@ class DeviceBooksModel(BooksModel):
return QVariant(authors_to_string(au)) return QVariant(authors_to_string(au))
elif col == 2: elif col == 2:
size = self.db[self.map[row]].size size = self.db[self.map[row]].size
return QVariant(BooksView.human_readable(size)) return QVariant(human_readable(size))
elif col == 3: elif col == 3:
dt = self.db[self.map[row]].datetime dt = self.db[self.map[row]].datetime
dt = dt_factory(dt, assume_utc=True, as_utc=False) dt = dt_factory(dt, assume_utc=True, as_utc=False)
return QVariant(strftime(BooksView.TIME_FMT, dt.timetuple())) return QVariant(strftime(TIME_FMT, dt.timetuple()))
elif col == 4: elif col == 4:
tags = self.db[self.map[row]].tags tags = self.db[self.map[row]].tags
if tags: if tags:
@ -1526,8 +1012,8 @@ class DeviceBooksModel(BooksModel):
tags = [i.strip() for i in val.split(',')] tags = [i.strip() for i in val.split(',')]
tags = [t for t in tags if t] tags = [t for t in tags if t]
self.db.set_tags(self.db[idx], tags) self.db.set_tags(self.db[idx], tags)
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index) self.dataChanged.emit(index, index)
self.emit(SIGNAL('booklist_dirtied()')) self.booklist_dirtied.emit()
if col == self.sorted_on[0]: if col == self.sorted_on[0]:
self.sort(col, self.sorted_on[1]) self.sort(col, self.sorted_on[1])
done = True done = True
@ -1538,3 +1024,6 @@ class DeviceBooksModel(BooksModel):
def set_search_restriction(self, s): def set_search_restriction(self, s):
pass pass
# }}}

View File

@ -0,0 +1,242 @@
#!/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 os
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
from calibre.utils.config import tweaks
from calibre.gui2 import error_dialog
class BooksView(QTableView): # {{{
files_dropped = pyqtSignal(object)
def __init__(self, parent, modelcls=BooksModel):
QTableView.__init__(self, parent)
self.rating_delegate = RatingDelegate(self)
self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self)
self.tags_delegate = TagsDelegate(self)
self.authors_delegate = TextDelegate(self)
self.series_delegate = TextDelegate(self)
self.publisher_delegate = TextDelegate(self)
self.text_delegate = TextDelegate(self)
self.cc_text_delegate = CcTextDelegate(self)
self.cc_bool_delegate = CcBoolDelegate(self)
self.cc_comments_delegate = CcCommentsDelegate(self)
self.display_parent = parent
self._model = modelcls(self)
self.setModel(self._model)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True)
self.selectionModel().currentRowChanged.connect(self._model.current_changed)
self.column_header = self.horizontalHeader()
self._model.database_changed.connect(self.database_changed)
hv = self.verticalHeader()
hv.setClickable(True)
hv.setCursor(Qt.PointingHandCursor)
self.selected_ids = []
self._model.about_to_be_sorted.connect(self.about_to_be_sorted)
self._model.sorting_done.connect(self.sorting_done)
def about_to_be_sorted(self, idc):
selected_rows = [r.row() for r in self.selectionModel().selectedRows()]
self.selected_ids = [idc(r) for r in selected_rows]
def sorting_done(self, indexc):
if self.selected_ids:
indices = [self.model().index(indexc(i), 0) for i in
self.selected_ids]
sm = self.selectionModel()
for idx in indices:
sm.select(idx, sm.Select|sm.Rows)
self.selected_ids = []
def set_ondevice_column_visibility(self):
m = self._model
self.column_header.setSectionHidden(m.column_map.index('ondevice'),
not m.device_connected)
def set_device_connected(self, is_connected):
self._model.set_device_connected(is_connected)
self.set_ondevice_column_visibility()
def database_changed(self, db):
for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate, self.pubdate_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate())
cm = self._model.column_map
self.set_ondevice_column_visibility()
for colhead in cm:
if self._model.is_custom_column(colhead):
cc = self._model.custom_columns[colhead]
if cc['datatype'] == 'datetime':
delegate = CcDateDelegate(self)
delegate.set_format(cc['display'].get('date_format',''))
self.setItemDelegateForColumn(cm.index(colhead), delegate)
elif cc['datatype'] == 'comments':
self.setItemDelegateForColumn(cm.index(colhead), self.cc_comments_delegate)
elif cc['datatype'] == 'text':
if cc['is_multiple']:
self.setItemDelegateForColumn(cm.index(colhead), self.tags_delegate)
else:
self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate)
elif cc['datatype'] in ('int', 'float'):
self.setItemDelegateForColumn(cm.index(colhead), self.cc_text_delegate)
elif cc['datatype'] == 'bool':
self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate)
elif cc['datatype'] == 'rating':
self.setItemDelegateForColumn(cm.index(colhead), self.rating_delegate)
else:
dattr = colhead+'_delegate'
delegate = colhead if hasattr(self, dattr) else 'text'
self.setItemDelegateForColumn(cm.index(colhead), getattr(self,
delegate+'_delegate'))
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, delete, similar_menu=None):
self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.context_menu = QMenu(self)
if edit_metadata is not None:
self.context_menu.addAction(edit_metadata)
if send_to_device is not None:
self.context_menu.addAction(send_to_device)
if convert is not None:
self.context_menu.addAction(convert)
self.context_menu.addAction(view)
self.context_menu.addAction(save)
if open_folder is not None:
self.context_menu.addAction(open_folder)
if delete is not None:
self.context_menu.addAction(delete)
if book_details is not None:
self.context_menu.addAction(book_details)
if similar_menu is not None:
self.context_menu.addMenu(similar_menu)
def contextMenuEvent(self, event):
self.context_menu.popup(event.globalPos())
event.accept()
def restore_sort_at_startup(self, saved_history):
if tweaks['sort_columns_at_startup'] is not None:
saved_history = tweaks['sort_columns_at_startup']
if saved_history is None:
return
for col,order in reversed(saved_history):
self.sortByColumn(col, order)
self.model().sort_history = saved_history
def sortByColumn(self, colname, order):
try:
idx = self._model.column_map.index(colname)
except ValueError:
idx = 0
QTableView.sortByColumn(self, idx, order)
@classmethod
def paths_from_event(cls, event):
'''
Accept a drop event and return a list of paths that can be read from
and represent files with extensions.
'''
if event.mimeData().hasFormat('text/uri-list'):
urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()]
return [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)]
def dragEnterEvent(self, event):
if int(event.possibleActions() & Qt.CopyAction) + \
int(event.possibleActions() & Qt.MoveAction) == 0:
return
paths = self.paths_from_event(event)
if paths:
event.acceptProposedAction()
def dragMoveEvent(self, event):
event.acceptProposedAction()
def dropEvent(self, event):
paths = self.paths_from_event(event)
event.setDropAction(Qt.CopyAction)
event.accept()
self.files_dropped.emit(paths)
def set_database(self, db):
self._model.set_database(db)
self.tags_delegate.set_database(db)
self.authors_delegate.set_auto_complete_function(db.all_authors)
self.series_delegate.set_auto_complete_function(db.all_series)
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
def close(self):
self._model.close()
def set_editable(self, editable):
self._model.set_editable(editable)
def connect_to_search_box(self, sb, search_done):
sb.search.connect(self._model.search)
self._search_done = search_done
self._model.searched.connect(self.search_done)
def connect_to_restriction_set(self, tv):
# must be synchronous (not queued)
tv.restriction_set.connect(self._model.set_search_restriction)
def connect_to_book_display(self, bd):
self._model.new_bookdisplay_data.connect(bd)
def search_done(self, ok):
self._search_done(self, ok)
def row_count(self):
return self._model.count()
# }}}
class DeviceBooksView(BooksView): # {{{
def __init__(self, parent):
BooksView.__init__(self, parent, DeviceBooksModel)
self.columns_resized = False
self.resize_on_select = False
self.rating_delegate = None
for i in range(10):
self.setItemDelegateForColumn(i, TextDelegate(self))
self.setDragDropMode(self.NoDragDrop)
self.setAcceptDrops(False)
def set_database(self, db):
self._model.set_database(db)
def resizeColumnsToContents(self):
QTableView.resizeColumnsToContents(self)
self.columns_resized = True
def connect_dirtied_signal(self, slot):
self._model.booklist_dirtied.connect(slot)
def sortByColumn(self, col, order):
QTableView.sortByColumn(self, col, order)
def dropEvent(self, *args):
error_dialog(self, _('Not allowed'),
_('Dropping onto a device is not supported. First add the book to the calibre library.')).exec_()
# }}}

View File

@ -81,7 +81,7 @@ class Main(MainWindow, Ui_MainWindow):
self.search = SearchBox2(self) self.search = SearchBox2(self)
self.search.initialize('lrf_viewer_search_history') self.search.initialize('lrf_viewer_search_history')
self.search_action = self.tool_bar.addWidget(self.search) self.search_action = self.tool_bar.addWidget(self.search)
QObject.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.find) self.search.search.connect(self.find)
self.action_next_page.setShortcuts([QKeySequence.MoveToNextPage, QKeySequence(Qt.Key_Space)]) self.action_next_page.setShortcuts([QKeySequence.MoveToNextPage, QKeySequence(Qt.Key_Space)])
self.action_previous_page.setShortcuts([QKeySequence.MoveToPreviousPage, QKeySequence(Qt.Key_Backspace)]) self.action_previous_page.setShortcuts([QKeySequence.MoveToPreviousPage, QKeySequence(Qt.Key_Backspace)])

View File

@ -793,7 +793,7 @@
<customwidget> <customwidget>
<class>BooksView</class> <class>BooksView</class>
<extends>QTableView</extends> <extends>QTableView</extends>
<header>library.h</header> <header>calibre/gui2/library/views.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>LocationView</class> <class>LocationView</class>
@ -803,7 +803,7 @@
<customwidget> <customwidget>
<class>DeviceBooksView</class> <class>DeviceBooksView</class>
<extends>QTableView</extends> <extends>QTableView</extends>
<header>library.h</header> <header>calibre/gui2/library/views.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>TagsView</class> <class>TagsView</class>

View File

@ -6,7 +6,8 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt4.Qt import QComboBox, SIGNAL, Qt, QLineEdit, QStringList, pyqtSlot from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \
pyqtSignal, SIGNAL
from PyQt4.QtGui import QCompleter from PyQt4.QtGui import QCompleter
from calibre.gui2 import config from calibre.gui2 import config
@ -56,6 +57,8 @@ class SearchBox2(QComboBox):
INTERVAL = 1500 #: Time to wait before emitting search signal INTERVAL = 1500 #: Time to wait before emitting search signal
MAX_COUNT = 25 MAX_COUNT = 25
search = pyqtSignal(object, object)
def __init__(self, parent=None): def __init__(self, parent=None):
QComboBox.__init__(self, parent) QComboBox.__init__(self, parent)
self.normal_background = 'rgb(255, 255, 255, 0%)' self.normal_background = 'rgb(255, 255, 255, 0%)'
@ -108,7 +111,7 @@ class SearchBox2(QComboBox):
def clear(self): def clear(self):
self.clear_to_help() self.clear_to_help()
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False) self.search.emit('', False)
def search_done(self, ok): def search_done(self, ok):
if not unicode(self.currentText()).strip(): if not unicode(self.currentText()).strip():
@ -155,7 +158,7 @@ class SearchBox2(QComboBox):
self.help_state = False self.help_state = False
refinement = text.startswith(self.prev_search) and ':' not in text refinement = text.startswith(self.prev_search) and ':' not in text
self.prev_search = text self.prev_search = text
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement) self.search.emit(text, refinement)
idx = self.findText(text, Qt.MatchFixedString) idx = self.findText(text, Qt.MatchFixedString)
self.block_signals(True) self.block_signals(True)
@ -187,7 +190,7 @@ class SearchBox2(QComboBox):
def set_search_string(self, txt): def set_search_string(self, txt):
self.normalize_state() self.normalize_state()
self.setEditText(txt) self.setEditText(txt)
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False) self.search.emit(txt, False)
self.line_edit.end(False) self.line_edit.end(False)
self.initial_state = False self.initial_state = False

View File

@ -20,6 +20,7 @@ from calibre.library.database2 import Tag
class TagsView(QTreeView): class TagsView(QTreeView):
need_refresh = pyqtSignal() need_refresh = pyqtSignal()
restriction_set = pyqtSignal(object)
def __init__(self, *args): def __init__(self, *args):
QTreeView.__init__(self, *args) QTreeView.__init__(self, *args)
@ -66,7 +67,7 @@ class TagsView(QTreeView):
else: else:
self.search_restriction = 'search:"%s"' % unicode(s).strip() self.search_restriction = 'search:"%s"' % unicode(s).strip()
self.model().set_search_restriction(self.search_restriction) self.model().set_search_restriction(self.search_restriction)
self.emit(SIGNAL('restriction_set(PyQt_PyObject)'), self.search_restriction) self.restriction_set.emit(self.search_restriction)
self.recount() # Must happen after the emission of the restriction_set signal self.recount() # Must happen after the emission of the restriction_set signal
self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
self._model.tokens(), self.match_all) self._model.tokens(), self.match_all)

View File

@ -507,9 +507,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.card_b_view.set_context_menu(None, None, None, self.card_b_view.set_context_menu(None, None, None,
self.action_view, self.action_save, None, None, self.action_del) self.action_view, self.action_save, None, None, self.action_del)
QObject.connect(self.library_view, self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
SIGNAL('files_dropped(PyQt_PyObject)'),
self.files_dropped, Qt.QueuedConnection)
for func, args in [ for func, args in [
('connect_to_search_box', (self.search, ('connect_to_search_box', (self.search,
self.search_done)), self.search_done)),
@ -544,24 +542,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.connect(self.tags_view, self.connect(self.tags_view,
SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
self.search.search_from_tags) self.search.search_from_tags)
self.connect(self.tags_view, for x in (self.saved_search.clear_to_help, self.mark_restriction_set):
SIGNAL('restriction_set(PyQt_PyObject)'), self.tags_view.restriction_set.connect(x)
self.saved_search.clear_to_help)
self.connect(self.tags_view,
SIGNAL('restriction_set(PyQt_PyObject)'),
self.mark_restriction_set)
self.connect(self.tags_view, self.connect(self.tags_view,
SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
self.saved_search.clear_to_help) self.saved_search.clear_to_help)
self.connect(self.search, self.search.search.connect(self.tags_view.model().reinit)
SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), for x in (self.location_view.count_changed, self.tags_view.recount,
self.tags_view.model().reinit) self.restriction_count_changed):
self.connect(self.library_view.model(), self.library_view.model().count_changed_signal.connect(x)
SIGNAL('count_changed(int)'), self.location_view.count_changed)
self.connect(self.library_view.model(), SIGNAL('count_changed(int)'),
self.tags_view.recount, Qt.QueuedConnection)
self.connect(self.library_view.model(), SIGNAL('count_changed(int)'),
self.restriction_count_changed, Qt.QueuedConnection)
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared) self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
self.connect(self.saved_search, SIGNAL('changed()'), self.tags_view.saved_searches_changed, Qt.QueuedConnection) self.connect(self.saved_search, SIGNAL('changed()'), self.tags_view.saved_searches_changed, Qt.QueuedConnection)
if not gprefs.get('quick_start_guide_added', False): if not gprefs.get('quick_start_guide_added', False):

View File

@ -244,7 +244,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.pos.editingFinished.connect(self.goto_page_num) self.pos.editingFinished.connect(self.goto_page_num)
self.connect(self.vertical_scrollbar, SIGNAL('valueChanged(int)'), self.connect(self.vertical_scrollbar, SIGNAL('valueChanged(int)'),
lambda x: self.goto_page(x/100.)) lambda x: self.goto_page(x/100.))
self.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.find) self.search.search.connect(self.find)
self.connect(self.toc, SIGNAL('clicked(QModelIndex)'), self.toc_clicked) self.connect(self.toc, SIGNAL('clicked(QModelIndex)'), self.toc_clicked)
self.connect(self.reference, SIGNAL('goto(PyQt_PyObject)'), self.goto) self.connect(self.reference, SIGNAL('goto(PyQt_PyObject)'), self.goto)