mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on allowing half-stars for custom rating columns
Still have to implement support in the bulk metadata editors and the content server.
This commit is contained in:
parent
a8f7c27e40
commit
baddd6c298
@ -4,24 +4,25 @@ FullName: calibre Symbols
|
|||||||
FamilyName: calibre Symbols
|
FamilyName: calibre Symbols
|
||||||
Weight: Medium
|
Weight: Medium
|
||||||
Copyright: Created by Kovid Goyal with FontForge 2.0 (http://fontforge.sf.net)
|
Copyright: Created by Kovid Goyal with FontForge 2.0 (http://fontforge.sf.net)
|
||||||
UComments: "2012-2-27: Created."
|
UComments: "2012-2-27: Created."
|
||||||
Version: 001.000
|
Version: 001.000
|
||||||
ItalicAngle: 0
|
ItalicAngle: 0
|
||||||
UnderlinePosition: -100
|
UnderlinePosition: -100
|
||||||
UnderlineWidth: 50
|
UnderlineWidth: 50
|
||||||
Ascent: 800
|
Ascent: 800
|
||||||
Descent: 200
|
Descent: 200
|
||||||
|
InvalidEm: 0
|
||||||
LayerCount: 2
|
LayerCount: 2
|
||||||
Layer: 0 0 "Back" 1
|
Layer: 0 0 "Back" 1
|
||||||
Layer: 1 0 "Fore" 0
|
Layer: 1 0 "Fore" 0
|
||||||
NeedsXUIDChange: 1
|
|
||||||
XUID: [1021 913 325894820 11538708]
|
XUID: [1021 913 325894820 11538708]
|
||||||
|
StyleMap: 0x0000
|
||||||
FSType: 0
|
FSType: 0
|
||||||
OS2Version: 0
|
OS2Version: 0
|
||||||
OS2_WeightWidthSlopeOnly: 0
|
OS2_WeightWidthSlopeOnly: 0
|
||||||
OS2_UseTypoMetrics: 1
|
OS2_UseTypoMetrics: 1
|
||||||
CreationTime: 1330331997
|
CreationTime: 1330331997
|
||||||
ModificationTime: 1330487767
|
ModificationTime: 1472969125
|
||||||
OS2TypoAscent: 0
|
OS2TypoAscent: 0
|
||||||
OS2TypoAOffset: 1
|
OS2TypoAOffset: 1
|
||||||
OS2TypoDescent: 0
|
OS2TypoDescent: 0
|
||||||
@ -44,10 +45,10 @@ DisplaySize: -24
|
|||||||
AntiAlias: 1
|
AntiAlias: 1
|
||||||
FitToEm: 1
|
FitToEm: 1
|
||||||
WidthSeparation: 150
|
WidthSeparation: 150
|
||||||
WinInfo: 9600 75 22
|
WinInfo: 0 152 34
|
||||||
BeginPrivate: 0
|
BeginPrivate: 0
|
||||||
EndPrivate
|
EndPrivate
|
||||||
BeginChars: 1114112 3
|
BeginChars: 1114112 4
|
||||||
|
|
||||||
StartChar: uni2605
|
StartChar: uni2605
|
||||||
Encoding: 9733 9733 0
|
Encoding: 9733 9733 0
|
||||||
@ -91,7 +92,7 @@ SplineSet
|
|||||||
485.545 635.457 493.518 604.173 506.689 547.357 c 2
|
485.545 635.457 493.518 604.173 506.689 547.357 c 2
|
||||||
551.923 352.862 l 1
|
551.923 352.862 l 1
|
||||||
EndSplineSet
|
EndSplineSet
|
||||||
Validated: 524289
|
Validated: 1
|
||||||
EndChar
|
EndChar
|
||||||
|
|
||||||
StartChar: zero
|
StartChar: zero
|
||||||
@ -148,5 +149,35 @@ SplineSet
|
|||||||
EndSplineSet
|
EndSplineSet
|
||||||
Validated: 1
|
Validated: 1
|
||||||
EndChar
|
EndChar
|
||||||
|
|
||||||
|
StartChar: onehalf
|
||||||
|
Encoding: 189 189 3
|
||||||
|
Width: 979
|
||||||
|
VWidth: -26
|
||||||
|
Flags: WO
|
||||||
|
LayerCount: 2
|
||||||
|
Fore
|
||||||
|
SplineSet
|
||||||
|
466.134 74.71 m 1
|
||||||
|
320.554 -51.8184 l 2
|
||||||
|
274.802 -91.5547 249.758 -112.902 245.426 -115.866 c 0
|
||||||
|
241.092 -118.828 236.846 -120.31 232.688 -120.31 c 0
|
||||||
|
227.835 -120.31 223.415 -118.306 219.429 -114.297 c 0
|
||||||
|
215.442 -110.289 213.449 -105.844 213.449 -100.965 c 0
|
||||||
|
213.449 -97.8281 223.329 -71.3379 243.087 -21.4932 c 2
|
||||||
|
322.115 180.323 l 1
|
||||||
|
152.618 289.598 l 2
|
||||||
|
104.783 320.271 79.2217 337.176 75.9297 340.313 c 0
|
||||||
|
72.6357 343.45 70.9893 347.981 70.9893 353.907 c 0
|
||||||
|
70.9893 369.243 79.8291 376.912 97.5059 376.912 c 0
|
||||||
|
98.8926 376.912 123.155 374.82 170.296 370.638 c 2
|
||||||
|
379.825 352.862 l 1
|
||||||
|
427.14 555.201 l 2
|
||||||
|
439.271 607.834 446.811 636.764 449.757 641.992 c 0
|
||||||
|
452.702 647.221 458.162 649.834 466.134 649.834 c 4
|
||||||
|
474.454 649.834 466.134 74.71 466.134 74.71 c 1
|
||||||
|
EndSplineSet
|
||||||
|
Validated: 524321
|
||||||
|
EndChar
|
||||||
EndChars
|
EndChars
|
||||||
EndSplineFont
|
EndSplineFont
|
||||||
|
Binary file not shown.
@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
@ -393,3 +394,14 @@ def check_doi(doi):
|
|||||||
return doi_check.group()
|
return doi_check.group()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def rating_to_stars(value, allow_half_star=False, star=u'★', half=u'½'):
|
||||||
|
r = max(0, min(int(value), 10))
|
||||||
|
if allow_half_star:
|
||||||
|
ans = u'★' * (r // 2)
|
||||||
|
if r % 2:
|
||||||
|
ans += u'½'
|
||||||
|
else:
|
||||||
|
ans = u'★' * int(r/2.0)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,11 +106,16 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
|
|||||||
elif metadata['datatype'] == 'rating':
|
elif metadata['datatype'] == 'rating':
|
||||||
val = getattr(mi, field)
|
val = getattr(mi, field)
|
||||||
if val:
|
if val:
|
||||||
val = val/2.0
|
if disp.get('allow_half_stars'):
|
||||||
|
val = max(0, min(int(val), 10))
|
||||||
|
star_string = u'\u2605' * (val // 2) + (u'\u00bd' if val % 2 else '')
|
||||||
|
else:
|
||||||
|
val = max(0, min(int(val/2.0), 5))
|
||||||
|
star_string = u'\u2605' * val
|
||||||
ans.append((field,
|
ans.append((field,
|
||||||
u'<td class="title">%s</td><td class="rating value" '
|
u'<td class="title">%s</td><td class="rating value" '
|
||||||
'style=\'font-family:"%s"\'>%s</td>'%(
|
'style=\'font-family:"%s"\'>%s</td>'%(
|
||||||
name, rating_font, u'\u2605'*int(val))))
|
name, rating_font, star_string)))
|
||||||
elif metadata['datatype'] == 'composite':
|
elif metadata['datatype'] == 'composite':
|
||||||
val = getattr(mi, field)
|
val = getattr(mi, field)
|
||||||
if val:
|
if val:
|
||||||
|
@ -21,6 +21,7 @@ from calibre.utils.config import tweaks
|
|||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox
|
from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox
|
||||||
|
from calibre.gui2.widgets2 import RatingEditor
|
||||||
|
|
||||||
class Base(object):
|
class Base(object):
|
||||||
|
|
||||||
@ -158,27 +159,18 @@ class Float(Int):
|
|||||||
val = self.widgets[1].minimum()
|
val = self.widgets[1].minimum()
|
||||||
self.widgets[1].setValue(val)
|
self.widgets[1].setValue(val)
|
||||||
|
|
||||||
class Rating(Int):
|
class Rating(Base):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
Int.setup_ui(self, parent)
|
allow_half_stars = self.col_metadata['display'].get('allow_half_stars', False)
|
||||||
w = self.widgets[1]
|
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), RatingEditor(parent=parent, is_half_star=allow_half_stars)]
|
||||||
w.setRange(0, 5)
|
|
||||||
w.setSuffix(' '+_('star(s)'))
|
|
||||||
w.setSpecialValueText(_('Not rated'))
|
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
if val is None:
|
val = max(0, min(int(val or 0), 10))
|
||||||
val = 0
|
self.widgets[1].rating_value = val
|
||||||
self.widgets[1].setValue(int(round(val/2.)))
|
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.widgets[1].value()
|
return self.widgets[1].rating_value or None
|
||||||
if val == 0:
|
|
||||||
val = None
|
|
||||||
else:
|
|
||||||
val *= 2
|
|
||||||
return val
|
|
||||||
|
|
||||||
class DateTimeEdit(QDateTimeEdit):
|
class DateTimeEdit(QDateTimeEdit):
|
||||||
|
|
||||||
|
@ -12,10 +12,11 @@ from PyQt5.Qt import (Qt, QApplication, QStyle, QIcon, QDoubleSpinBox, QStyleOp
|
|||||||
QAbstractTextDocumentLayout, QFont, QFontInfo, QDate, QDateTimeEdit, QDateTime,
|
QAbstractTextDocumentLayout, QFont, QFontInfo, QDate, QDateTimeEdit, QDateTime,
|
||||||
QStyleOptionComboBox, QStyleOptionSpinBox, QLocale, QSize, QLineEdit)
|
QStyleOptionComboBox, QStyleOptionSpinBox, QLocale, QSize, QLineEdit)
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import rating_to_stars
|
||||||
from calibre.gui2 import UNDEFINED_QDATETIME, rating_font
|
from calibre.gui2 import UNDEFINED_QDATETIME, rating_font
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.gui2.widgets import EnLineEdit
|
from calibre.gui2.widgets import EnLineEdit
|
||||||
from calibre.gui2.widgets2 import populate_standard_spinbox_context_menu
|
from calibre.gui2.widgets2 import populate_standard_spinbox_context_menu, RatingEditor
|
||||||
from calibre.gui2.complete2 import EditWithComplete
|
from calibre.gui2.complete2 import EditWithComplete
|
||||||
from calibre.utils.date import now, format_date, qt_to_dt, is_date_undefined
|
from calibre.utils.date import now, format_date, qt_to_dt, is_date_undefined
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
@ -176,6 +177,7 @@ class RatingDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
QStyledItemDelegate.__init__(self, *args, **kwargs)
|
QStyledItemDelegate.__init__(self, *args, **kwargs)
|
||||||
|
self.is_half_star = kwargs.get('is_half_star', False)
|
||||||
self.table_widget = args[0]
|
self.table_widget = args[0]
|
||||||
self.rf = QFont(rating_font())
|
self.rf = QFont(rating_font())
|
||||||
self.em = Qt.ElideMiddle
|
self.em = Qt.ElideMiddle
|
||||||
@ -184,33 +186,25 @@ class RatingDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
|||||||
delta = 2
|
delta = 2
|
||||||
self.rf.setPointSize(QFontInfo(QApplication.font()).pointSize()+delta)
|
self.rf.setPointSize(QFontInfo(QApplication.font()).pointSize()+delta)
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
sb = QSpinBox(parent)
|
|
||||||
sb.setMinimum(0)
|
|
||||||
sb.setMaximum(5)
|
|
||||||
sb.setSuffix(' ' + _('stars'))
|
|
||||||
sb.setSpecialValueText(_('Not rated'))
|
|
||||||
return sb
|
|
||||||
|
|
||||||
def get_required_width(self, editor, style, fm):
|
def get_required_width(self, editor, style, fm):
|
||||||
val = editor.maximum()
|
return editor.sizeHint().width()
|
||||||
text = editor.textFromValue(val) + editor.suffix()
|
|
||||||
srect = style.itemTextRect(fm, editor.geometry(), Qt.AlignLeft, False,
|
|
||||||
text + u'M')
|
|
||||||
return srect.width()
|
|
||||||
|
|
||||||
def displayText(self, value, locale):
|
def displayText(self, value, locale):
|
||||||
r = int(value)
|
return rating_to_stars(value, self.is_half_star)
|
||||||
if r < 0 or r > 5:
|
|
||||||
r = 0
|
def createEditor(self, parent, option, index):
|
||||||
return u'\u2605'*r
|
return RatingEditor(parent, is_half_star=self.is_half_star)
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
def setEditorData(self, editor, index):
|
||||||
if check_key_modifier(Qt.ControlModifier):
|
if check_key_modifier(Qt.ControlModifier):
|
||||||
val = 0
|
val = 0
|
||||||
else:
|
else:
|
||||||
val = index.data(Qt.EditRole)
|
val = index.data(Qt.EditRole)
|
||||||
editor.setValue(val)
|
editor.rating_value = val
|
||||||
|
|
||||||
|
def setModelData(self, editor, model, index):
|
||||||
|
val = editor.rating_value
|
||||||
|
model.setData(index, val, Qt.EditRole)
|
||||||
|
|
||||||
def sizeHint(self, option, index):
|
def sizeHint(self, option, index):
|
||||||
option.font = self.rf
|
option.font = self.rf
|
||||||
@ -224,6 +218,7 @@ class RatingDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
class DateDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
class DateDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
||||||
|
|
||||||
def __init__(self, parent, tweak_name='gui_timestamp_display_format',
|
def __init__(self, parent, tweak_name='gui_timestamp_display_format',
|
||||||
|
@ -709,6 +709,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
def build_data_convertors(self):
|
def build_data_convertors(self):
|
||||||
|
rating_fields = {}
|
||||||
|
|
||||||
def renderer(field, decorator=False):
|
def renderer(field, decorator=False):
|
||||||
idfunc = self.db.id
|
idfunc = self.db.id
|
||||||
@ -776,8 +777,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
def func(idx):
|
def func(idx):
|
||||||
return (QDateTime(as_local_time(fffunc(field_obj, idfunc(idx), default_value=UNDEFINED_DATE))))
|
return (QDateTime(as_local_time(fffunc(field_obj, idfunc(idx), default_value=UNDEFINED_DATE))))
|
||||||
elif dt == 'rating':
|
elif dt == 'rating':
|
||||||
|
rating_fields[field] = m['display'].get('allow_half_stars', False)
|
||||||
def func(idx):
|
def func(idx):
|
||||||
return (int(fffunc(field_obj, idfunc(idx), default_value=0)/2.0))
|
return int(fffunc(field_obj, idfunc(idx), default_value=0))
|
||||||
elif dt == 'series':
|
elif dt == 'series':
|
||||||
sidx_field = self.db.new_api.fields[field + '_index']
|
sidx_field = self.db.new_api.fields[field + '_index']
|
||||||
def func(idx):
|
def func(idx):
|
||||||
@ -817,8 +819,20 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif dt == 'bool':
|
elif dt == 'bool':
|
||||||
self.dc_decorator[col] = renderer(col, 'bool')
|
self.dc_decorator[col] = renderer(col, 'bool')
|
||||||
|
|
||||||
|
tc = self.dc.copy()
|
||||||
|
def stars_tooltip(func, allow_half=True):
|
||||||
|
def f(idx):
|
||||||
|
ans = val = int(func(idx))
|
||||||
|
ans = str(val // 2)
|
||||||
|
if allow_half and val % 2:
|
||||||
|
ans += '.5'
|
||||||
|
return _('%s stars') % ans
|
||||||
|
return f
|
||||||
|
for f, allow_half in rating_fields.iteritems():
|
||||||
|
tc[f] = stars_tooltip(self.dc[f], allow_half)
|
||||||
# build a index column to data converter map, to remove the string lookup in the data loop
|
# build a index column to data converter map, to remove the string lookup in the data loop
|
||||||
self.column_to_dc_map = [self.dc[col] for col in self.column_map]
|
self.column_to_dc_map = [self.dc[col] for col in self.column_map]
|
||||||
|
self.column_to_tc_map = [tc[col] for col in self.column_map]
|
||||||
self.column_to_dc_decorator_map = [self.dc_decorator.get(col, None) for col in self.column_map]
|
self.column_to_dc_decorator_map = [self.dc_decorator.get(col, None) for col in self.column_map]
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
@ -850,7 +864,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
return None
|
return None
|
||||||
self.icon_cache[id_][cache_index] = None
|
self.icon_cache[id_][cache_index] = None
|
||||||
return self.column_to_dc_map[col](index.row())
|
return self.column_to_dc_map[col](index.row())
|
||||||
elif role in (Qt.EditRole, Qt.ToolTipRole):
|
elif role == Qt.ToolTipRole:
|
||||||
|
return self.column_to_tc_map[col](index.row())
|
||||||
|
elif role == Qt.EditRole:
|
||||||
return self.column_to_dc_map[col](index.row())
|
return self.column_to_dc_map[col](index.row())
|
||||||
elif role == Qt.BackgroundRole:
|
elif role == Qt.BackgroundRole:
|
||||||
if self.id(index) in self.ids_to_highlight_set:
|
if self.id(index) in self.ids_to_highlight_set:
|
||||||
@ -996,9 +1012,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif typ == 'bool':
|
elif typ == 'bool':
|
||||||
val = value if value is None else bool(value)
|
val = value if value is None else bool(value)
|
||||||
elif typ == 'rating':
|
elif typ == 'rating':
|
||||||
val = int(value)
|
val = max(0, min(int(value or 0), 10))
|
||||||
val = 0 if val < 0 else 5 if val > 5 else val
|
|
||||||
val *= 2
|
|
||||||
elif typ in ('int', 'float'):
|
elif typ in ('int', 'float'):
|
||||||
if value == 0:
|
if value == 0:
|
||||||
val = '0'
|
val = '0'
|
||||||
@ -1089,8 +1103,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
id = self.db.id(row)
|
id = self.db.id(row)
|
||||||
books_to_refresh = set([id])
|
books_to_refresh = set([id])
|
||||||
if column == 'rating':
|
if column == 'rating':
|
||||||
val = 0 if val < 0 else 5 if val > 5 else val
|
val = max(0, min(int(val or 0), 10))
|
||||||
val *= 2
|
|
||||||
self.db.set_rating(id, val)
|
self.db.set_rating(id, val)
|
||||||
elif column == 'series':
|
elif column == 'series':
|
||||||
val = val.strip()
|
val = val.strip()
|
||||||
|
@ -224,6 +224,7 @@ class BooksView(QTableView): # {{{
|
|||||||
self.setWordWrap(False)
|
self.setWordWrap(False)
|
||||||
|
|
||||||
self.rating_delegate = RatingDelegate(self)
|
self.rating_delegate = RatingDelegate(self)
|
||||||
|
self.half_rating_delegate = RatingDelegate(self, is_half_star=True)
|
||||||
self.timestamp_delegate = DateDelegate(self)
|
self.timestamp_delegate = DateDelegate(self)
|
||||||
self.pubdate_delegate = PubDateDelegate(self)
|
self.pubdate_delegate = PubDateDelegate(self)
|
||||||
self.last_modified_delegate = DateDelegate(self,
|
self.last_modified_delegate = DateDelegate(self,
|
||||||
@ -760,9 +761,9 @@ class BooksView(QTableView): # {{{
|
|||||||
def database_changed(self, db):
|
def database_changed(self, db):
|
||||||
db.data.add_marked_listener(self.marked_changed_listener)
|
db.data.add_marked_listener(self.marked_changed_listener)
|
||||||
for i in range(self.model().columnCount(None)):
|
for i in range(self.model().columnCount(None)):
|
||||||
if self.itemDelegateForColumn(i) in (self.rating_delegate,
|
if self.itemDelegateForColumn(i) in (
|
||||||
self.timestamp_delegate, self.pubdate_delegate,
|
self.rating_delegate, self.timestamp_delegate, self.pubdate_delegate,
|
||||||
self.last_modified_delegate, self.languages_delegate):
|
self.last_modified_delegate, self.languages_delegate, self.half_rating_delegate):
|
||||||
self.setItemDelegateForColumn(i, self.itemDelegate())
|
self.setItemDelegateForColumn(i, self.itemDelegate())
|
||||||
|
|
||||||
cm = self.column_map
|
cm = self.column_map
|
||||||
@ -799,7 +800,8 @@ class BooksView(QTableView): # {{{
|
|||||||
elif cc['datatype'] == 'bool':
|
elif cc['datatype'] == 'bool':
|
||||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate)
|
self.setItemDelegateForColumn(cm.index(colhead), self.cc_bool_delegate)
|
||||||
elif cc['datatype'] == 'rating':
|
elif cc['datatype'] == 'rating':
|
||||||
self.setItemDelegateForColumn(cm.index(colhead), self.rating_delegate)
|
d = self.half_rating_delegate if cc['display'].get('allow_half_stars', False) else self.rating_delegate
|
||||||
|
self.setItemDelegateForColumn(cm.index(colhead), d)
|
||||||
elif cc['datatype'] == 'composite':
|
elif cc['datatype'] == 'composite':
|
||||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_template_delegate)
|
self.setItemDelegateForColumn(cm.index(colhead), self.cc_template_delegate)
|
||||||
elif cc['datatype'] == 'enumeration':
|
elif cc['datatype'] == 'enumeration':
|
||||||
@ -1126,6 +1128,7 @@ class DeviceBooksView(BooksView): # {{{
|
|||||||
self.can_add_columns = False
|
self.can_add_columns = False
|
||||||
self.resize_on_select = False
|
self.resize_on_select = False
|
||||||
self.rating_delegate = None
|
self.rating_delegate = None
|
||||||
|
self.half_rating_delegate = None
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
self.setItemDelegateForColumn(i, TextDelegate(self))
|
self.setItemDelegateForColumn(i, TextDelegate(self))
|
||||||
self.setDragDropMode(self.NoDragDrop)
|
self.setDragDropMode(self.NoDragDrop)
|
||||||
|
@ -13,12 +13,12 @@ from datetime import date, datetime
|
|||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
Qt, QDateTimeEdit, pyqtSignal, QMessageBox, QIcon, QToolButton, QWidget,
|
Qt, QDateTimeEdit, pyqtSignal, QMessageBox, QIcon, QToolButton, QWidget,
|
||||||
QLabel, QGridLayout, QApplication, QDoubleSpinBox, QListWidgetItem, QSize,
|
QLabel, QGridLayout, QApplication, QDoubleSpinBox, QListWidgetItem, QSize,
|
||||||
QPixmap, QDialog, QMenu, QSpinBox, QLineEdit, QSizePolicy, QKeySequence,
|
QPixmap, QDialog, QMenu, QLineEdit, QSizePolicy, QKeySequence,
|
||||||
QDialogButtonBox, QAction, QCalendarWidget, QDate, QDateTime, QUndoCommand,
|
QDialogButtonBox, QAction, QCalendarWidget, QDate, QDateTime, QUndoCommand,
|
||||||
QUndoStack, QVBoxLayout, QPlainTextEdit)
|
QUndoStack, QVBoxLayout, QPlainTextEdit)
|
||||||
|
|
||||||
from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView
|
from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView
|
||||||
from calibre.gui2.widgets2 import access_key, populate_standard_spinbox_context_menu, RightClickButton, Dialog
|
from calibre.gui2.widgets2 import access_key, populate_standard_spinbox_context_menu, RightClickButton, Dialog, RatingEditor
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.ebooks.metadata import (
|
from calibre.ebooks.metadata import (
|
||||||
@ -1231,7 +1231,7 @@ class CommentsEdit(Editor, ToMetadataMixin): # {{{
|
|||||||
db.set_comment(id_, self.current_val, notify=False, commit=False)
|
db.set_comment(id_, self.current_val, notify=False, commit=False)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class RatingEdit(make_undoable(QSpinBox), ToMetadataMixin): # {{{
|
class RatingEdit(RatingEditor, ToMetadataMixin): # {{{
|
||||||
LABEL = _('&Rating:')
|
LABEL = _('&Rating:')
|
||||||
TOOLTIP = _('Rating of this book. 0-5 stars')
|
TOOLTIP = _('Rating of this book. 0-5 stars')
|
||||||
FIELD_NAME = 'rating'
|
FIELD_NAME = 'rating'
|
||||||
@ -1240,40 +1240,26 @@ class RatingEdit(make_undoable(QSpinBox), ToMetadataMixin): # {{{
|
|||||||
super(RatingEdit, self).__init__(parent)
|
super(RatingEdit, self).__init__(parent)
|
||||||
self.setToolTip(self.TOOLTIP)
|
self.setToolTip(self.TOOLTIP)
|
||||||
self.setWhatsThis(self.TOOLTIP)
|
self.setWhatsThis(self.TOOLTIP)
|
||||||
self.setMaximum(5)
|
|
||||||
self.setSuffix(' ' + _('stars'))
|
|
||||||
self.setSpecialValueText(_('Not rated'))
|
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def current_val(self):
|
def current_val(self):
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return self.value()
|
return self.rating_value
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
if val is None:
|
self.rating_value = val
|
||||||
val = 0
|
|
||||||
val = int(val)
|
|
||||||
if val < 0:
|
|
||||||
val = 0
|
|
||||||
if val > 5:
|
|
||||||
val = 5
|
|
||||||
self.set_spinbox_value(val)
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
val = db.rating(id_, index_is_id=True)
|
val = db.rating(id_, index_is_id=True)
|
||||||
if val > 0:
|
|
||||||
val = int(val/2.)
|
|
||||||
else:
|
|
||||||
val = 0
|
|
||||||
self.current_val = val
|
self.current_val = val
|
||||||
self.original_val = self.current_val
|
self.original_val = self.current_val
|
||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
db.set_rating(id_, 2*self.current_val, notify=False, commit=False)
|
db.set_rating(id_, self.current_val, notify=False, commit=False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def zero(self):
|
def zero(self):
|
||||||
self.setValue(0)
|
self.setCurrentIndex(0)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -436,10 +436,7 @@ class MetadataSingleDialogBase(QDialog):
|
|||||||
elif update_sorts and not mi.is_null('authors'):
|
elif update_sorts and not mi.is_null('authors'):
|
||||||
self.author_sort.auto_generate()
|
self.author_sort.auto_generate()
|
||||||
if not mi.is_null('rating'):
|
if not mi.is_null('rating'):
|
||||||
try:
|
self.rating.set_value(mi.rating * 2)
|
||||||
self.rating.set_value(mi.rating)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not mi.is_null('publisher'):
|
if not mi.is_null('publisher'):
|
||||||
self.publisher.set_value(mi.publisher)
|
self.publisher.set_value(mi.publisher)
|
||||||
if not mi.is_null('tags'):
|
if not mi.is_null('tags'):
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=UTF-8
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
@ -168,6 +171,8 @@ class CreateCustomColumn(QDialog):
|
|||||||
self.comments_heading_position.setCurrentIndex(idx)
|
self.comments_heading_position.setCurrentIndex(idx)
|
||||||
idx = max(0, self.comments_type.findData(c['display'].get('interpret_as', 'html')))
|
idx = max(0, self.comments_type.findData(c['display'].get('interpret_as', 'html')))
|
||||||
self.comments_type.setCurrentIndex(idx)
|
self.comments_type.setCurrentIndex(idx)
|
||||||
|
elif ct == 'rating':
|
||||||
|
self.allow_half_stars.setChecked(bool(c['display'].get('allow_half_stars', False)))
|
||||||
self.datatype_changed()
|
self.datatype_changed()
|
||||||
if ct in ['text', 'composite', 'enumeration']:
|
if ct in ['text', 'composite', 'enumeration']:
|
||||||
self.use_decorations.setChecked(c['display'].get('use_decorations', False))
|
self.use_decorations.setChecked(c['display'].get('use_decorations', False))
|
||||||
@ -350,6 +355,11 @@ class CreateCustomColumn(QDialog):
|
|||||||
l.addWidget(ec), l.addWidget(la, 1, 1)
|
l.addWidget(ec), l.addWidget(la, 1, 1)
|
||||||
self.enum_label = add_row(_('&Values'), l)
|
self.enum_label = add_row(_('&Values'), l)
|
||||||
|
|
||||||
|
# Rating allow half stars
|
||||||
|
self.allow_half_stars = ahs = QCheckBox(_('Allow half stars'))
|
||||||
|
ahs.setToolTip(_('Allow half star ratings, for example: ') + '<span style="font-family:calibre Symbols">★★★½</span>')
|
||||||
|
add_row(None, ahs)
|
||||||
|
|
||||||
# Composite display properties
|
# Composite display properties
|
||||||
l = QHBoxLayout()
|
l = QHBoxLayout()
|
||||||
self.composite_sort_by_label = la = QLabel(_("&Sort/search column by"))
|
self.composite_sort_by_label = la = QLabel(_("&Sort/search column by"))
|
||||||
@ -427,6 +437,7 @@ class CreateCustomColumn(QDialog):
|
|||||||
self.comments_heading_position_label.setVisible(is_comments)
|
self.comments_heading_position_label.setVisible(is_comments)
|
||||||
self.comments_type.setVisible(is_comments)
|
self.comments_type.setVisible(is_comments)
|
||||||
self.comments_type_label.setVisible(is_comments)
|
self.comments_type_label.setVisible(is_comments)
|
||||||
|
self.allow_half_stars.setVisible(col_type == 'rating')
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
col = unicode(self.column_name_box.text()).strip()
|
col = unicode(self.column_name_box.text()).strip()
|
||||||
@ -524,6 +535,8 @@ class CreateCustomColumn(QDialog):
|
|||||||
elif col_type == 'comments':
|
elif col_type == 'comments':
|
||||||
display_dict['heading_position'] = type(u'')(self.comments_heading_position.currentData())
|
display_dict['heading_position'] = type(u'')(self.comments_heading_position.currentData())
|
||||||
display_dict['interpret_as'] = type(u'')(self.comments_type.currentData())
|
display_dict['interpret_as'] = type(u'')(self.comments_type.currentData())
|
||||||
|
elif col_type == 'rating':
|
||||||
|
display_dict['allow_half_stars'] = bool(self.allow_half_stars.isChecked())
|
||||||
|
|
||||||
if col_type in ['text', 'composite', 'enumeration'] and not is_multiple:
|
if col_type in ['text', 'composite', 'enumeration'] and not is_multiple:
|
||||||
display_dict['use_decorations'] = self.use_decorations.checkState()
|
display_dict['use_decorations'] = self.use_decorations.checkState()
|
||||||
|
@ -6,11 +6,16 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
import weakref
|
||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QPushButton, QPixmap, QIcon, QColor, Qt, QColorDialog, pyqtSignal,
|
QPushButton, QPixmap, QIcon, QColor, Qt, QColorDialog, pyqtSignal,
|
||||||
QKeySequence, QToolButton, QDialog, QDialogButtonBox)
|
QKeySequence, QToolButton, QDialog, QDialogButtonBox, QComboBox, QFont,
|
||||||
|
QAbstractListModel, QModelIndex, QApplication, QStyledItemDelegate,
|
||||||
|
QUndoCommand, QUndoStack)
|
||||||
|
|
||||||
from calibre.gui2 import gprefs
|
from calibre.ebooks.metadata import rating_to_stars
|
||||||
|
from calibre.gui2 import gprefs, rating_font
|
||||||
from calibre.gui2.complete2 import LineEdit, EditWithComplete
|
from calibre.gui2.complete2 import LineEdit, EditWithComplete
|
||||||
from calibre.gui2.widgets import history
|
from calibre.gui2.widgets import history
|
||||||
|
|
||||||
@ -180,3 +185,106 @@ class Dialog(QDialog):
|
|||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
raise NotImplementedError('You must implement this method in Dialog subclasses')
|
raise NotImplementedError('You must implement this method in Dialog subclasses')
|
||||||
|
|
||||||
|
|
||||||
|
class RatingModel(QAbstractListModel):
|
||||||
|
|
||||||
|
def __init__(self, parent=None, is_half_star=False):
|
||||||
|
QAbstractListModel.__init__(self, parent)
|
||||||
|
self.is_half_star = is_half_star
|
||||||
|
self.rating_font = QFont(rating_font())
|
||||||
|
|
||||||
|
def rowCount(self, parent=QModelIndex()):
|
||||||
|
return 11 if self.is_half_star else 6
|
||||||
|
|
||||||
|
def data(self, index, role=Qt.DisplayRole):
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
val = index.row() * (1 if self.is_half_star else 2)
|
||||||
|
return rating_to_stars(val, self.is_half_star) or _('Not rated')
|
||||||
|
if role == Qt.FontRole:
|
||||||
|
return QApplication.instance().font() if index.row() == 0 else self.rating_font
|
||||||
|
|
||||||
|
class UndoCommand(QUndoCommand):
|
||||||
|
|
||||||
|
def __init__(self, widget, val):
|
||||||
|
QUndoCommand.__init__(self)
|
||||||
|
self.widget = weakref.ref(widget)
|
||||||
|
self.undo_val = widget.rating_value
|
||||||
|
self.redo_val = val
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
w = self.widget()
|
||||||
|
w.setCurrentIndex(self.undo_val)
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
w = self.widget()
|
||||||
|
w.setCurrentIndex(self.redo_val)
|
||||||
|
|
||||||
|
class RatingEditor(QComboBox):
|
||||||
|
|
||||||
|
def __init__(self, parent=None, is_half_star=False):
|
||||||
|
QComboBox.__init__(self, parent)
|
||||||
|
self.undo_stack = QUndoStack(self)
|
||||||
|
self.undo, self.redo = self.undo_stack.undo, self.undo_stack.redo
|
||||||
|
self.allow_undo = False
|
||||||
|
self.is_half_star = is_half_star
|
||||||
|
self._model = RatingModel(is_half_star=is_half_star, parent=self)
|
||||||
|
self.setModel(self._model)
|
||||||
|
self.delegate = QStyledItemDelegate(self)
|
||||||
|
self.view().setItemDelegate(self.delegate)
|
||||||
|
self.view().setStyleSheet('QListView { background: palette(window) }\nQListView::item { padding: 6px }')
|
||||||
|
self.setMaxVisibleItems(self.count())
|
||||||
|
self.currentIndexChanged.connect(self.update_font)
|
||||||
|
|
||||||
|
def update_font(self):
|
||||||
|
if self.currentIndex() == 0:
|
||||||
|
self.setFont(QApplication.instance().font())
|
||||||
|
else:
|
||||||
|
self.setFont(self._model.rating_font)
|
||||||
|
|
||||||
|
def clear_to_undefined(self):
|
||||||
|
self.setCurrentIndex(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rating_value(self):
|
||||||
|
' An integer from 0 to 10 '
|
||||||
|
ans = self.currentIndex()
|
||||||
|
if not self.is_half_star:
|
||||||
|
ans *= 2
|
||||||
|
return ans
|
||||||
|
|
||||||
|
@rating_value.setter
|
||||||
|
def rating_value(self, val):
|
||||||
|
val = max(0, min(int(val or 0), 10))
|
||||||
|
if self.allow_undo:
|
||||||
|
cmd = UndoCommand(self, val)
|
||||||
|
self.undo_stack.push(cmd)
|
||||||
|
else:
|
||||||
|
self.undo_stack.clear()
|
||||||
|
if not self.is_half_star:
|
||||||
|
val //= 2
|
||||||
|
self.setCurrentIndex(val)
|
||||||
|
|
||||||
|
def keyPressEvent(self, ev):
|
||||||
|
if ev == QKeySequence.Undo:
|
||||||
|
self.undo()
|
||||||
|
return ev.accept()
|
||||||
|
if ev == QKeySequence.Redo:
|
||||||
|
self.redo()
|
||||||
|
return ev.accept()
|
||||||
|
k = ev.key()
|
||||||
|
num = {getattr(Qt, 'Key_%d'%i):i for i in range(6)}.get(k)
|
||||||
|
if num is None:
|
||||||
|
return QComboBox.keyPressEvent(self, ev)
|
||||||
|
ev.accept()
|
||||||
|
if self.is_half_star:
|
||||||
|
num *= 2
|
||||||
|
self.setCurrentIndex(num)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.gui2 import Application
|
||||||
|
app = Application([])
|
||||||
|
app.load_builtin_fonts()
|
||||||
|
q = RatingEditor(is_half_star=True)
|
||||||
|
q.rating_value = 7
|
||||||
|
q.show()
|
||||||
|
app.exec_()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user