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
|
||||
Weight: Medium
|
||||
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
|
||||
ItalicAngle: 0
|
||||
UnderlinePosition: -100
|
||||
UnderlineWidth: 50
|
||||
Ascent: 800
|
||||
Descent: 200
|
||||
InvalidEm: 0
|
||||
LayerCount: 2
|
||||
Layer: 0 0 "Back" 1
|
||||
Layer: 1 0 "Fore" 0
|
||||
NeedsXUIDChange: 1
|
||||
Layer: 0 0 "Back" 1
|
||||
Layer: 1 0 "Fore" 0
|
||||
XUID: [1021 913 325894820 11538708]
|
||||
StyleMap: 0x0000
|
||||
FSType: 0
|
||||
OS2Version: 0
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 1
|
||||
CreationTime: 1330331997
|
||||
ModificationTime: 1330487767
|
||||
ModificationTime: 1472969125
|
||||
OS2TypoAscent: 0
|
||||
OS2TypoAOffset: 1
|
||||
OS2TypoDescent: 0
|
||||
@ -44,10 +45,10 @@ DisplaySize: -24
|
||||
AntiAlias: 1
|
||||
FitToEm: 1
|
||||
WidthSeparation: 150
|
||||
WinInfo: 9600 75 22
|
||||
WinInfo: 0 152 34
|
||||
BeginPrivate: 0
|
||||
EndPrivate
|
||||
BeginChars: 1114112 3
|
||||
BeginChars: 1114112 4
|
||||
|
||||
StartChar: uni2605
|
||||
Encoding: 9733 9733 0
|
||||
@ -91,7 +92,7 @@ SplineSet
|
||||
485.545 635.457 493.518 604.173 506.689 547.357 c 2
|
||||
551.923 352.862 l 1
|
||||
EndSplineSet
|
||||
Validated: 524289
|
||||
Validated: 1
|
||||
EndChar
|
||||
|
||||
StartChar: zero
|
||||
@ -148,5 +149,35 @@ SplineSet
|
||||
EndSplineSet
|
||||
Validated: 1
|
||||
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
|
||||
EndSplineFont
|
||||
|
Binary file not shown.
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=utf-8
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@ -393,3 +394,14 @@ def check_doi(doi):
|
||||
return doi_check.group()
|
||||
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':
|
||||
val = getattr(mi, field)
|
||||
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,
|
||||
u'<td class="title">%s</td><td class="rating value" '
|
||||
'style=\'font-family:"%s"\'>%s</td>'%(
|
||||
name, rating_font, u'\u2605'*int(val))))
|
||||
name, rating_font, star_string)))
|
||||
elif metadata['datatype'] == 'composite':
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
|
@ -21,6 +21,7 @@ from calibre.utils.config import tweaks
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox
|
||||
from calibre.gui2.widgets2 import RatingEditor
|
||||
|
||||
class Base(object):
|
||||
|
||||
@ -158,27 +159,18 @@ class Float(Int):
|
||||
val = self.widgets[1].minimum()
|
||||
self.widgets[1].setValue(val)
|
||||
|
||||
class Rating(Int):
|
||||
class Rating(Base):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
Int.setup_ui(self, parent)
|
||||
w = self.widgets[1]
|
||||
w.setRange(0, 5)
|
||||
w.setSuffix(' '+_('star(s)'))
|
||||
w.setSpecialValueText(_('Not rated'))
|
||||
allow_half_stars = self.col_metadata['display'].get('allow_half_stars', False)
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), RatingEditor(parent=parent, is_half_star=allow_half_stars)]
|
||||
|
||||
def setter(self, val):
|
||||
if val is None:
|
||||
val = 0
|
||||
self.widgets[1].setValue(int(round(val/2.)))
|
||||
val = max(0, min(int(val or 0), 10))
|
||||
self.widgets[1].rating_value = val
|
||||
|
||||
def getter(self):
|
||||
val = self.widgets[1].value()
|
||||
if val == 0:
|
||||
val = None
|
||||
else:
|
||||
val *= 2
|
||||
return val
|
||||
return self.widgets[1].rating_value or None
|
||||
|
||||
class DateTimeEdit(QDateTimeEdit):
|
||||
|
||||
|
@ -12,10 +12,11 @@ from PyQt5.Qt import (Qt, QApplication, QStyle, QIcon, QDoubleSpinBox, QStyleOp
|
||||
QAbstractTextDocumentLayout, QFont, QFontInfo, QDate, QDateTimeEdit, QDateTime,
|
||||
QStyleOptionComboBox, QStyleOptionSpinBox, QLocale, QSize, QLineEdit)
|
||||
|
||||
from calibre.ebooks.metadata import rating_to_stars
|
||||
from calibre.gui2 import UNDEFINED_QDATETIME, rating_font
|
||||
from calibre.constants import iswindows
|
||||
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.utils.date import now, format_date, qt_to_dt, is_date_undefined
|
||||
from calibre.utils.config import tweaks
|
||||
@ -176,6 +177,7 @@ class RatingDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
||||
|
||||
def __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.rf = QFont(rating_font())
|
||||
self.em = Qt.ElideMiddle
|
||||
@ -184,33 +186,25 @@ class RatingDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
||||
delta = 2
|
||||
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):
|
||||
val = editor.maximum()
|
||||
text = editor.textFromValue(val) + editor.suffix()
|
||||
srect = style.itemTextRect(fm, editor.geometry(), Qt.AlignLeft, False,
|
||||
text + u'M')
|
||||
return srect.width()
|
||||
return editor.sizeHint().width()
|
||||
|
||||
def displayText(self, value, locale):
|
||||
r = int(value)
|
||||
if r < 0 or r > 5:
|
||||
r = 0
|
||||
return u'\u2605'*r
|
||||
return rating_to_stars(value, self.is_half_star)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
return RatingEditor(parent, is_half_star=self.is_half_star)
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
if check_key_modifier(Qt.ControlModifier):
|
||||
val = 0
|
||||
else:
|
||||
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):
|
||||
option.font = self.rf
|
||||
@ -224,6 +218,7 @@ class RatingDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class DateDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{
|
||||
|
||||
def __init__(self, parent, tweak_name='gui_timestamp_display_format',
|
||||
|
@ -709,6 +709,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
return img
|
||||
|
||||
def build_data_convertors(self):
|
||||
rating_fields = {}
|
||||
|
||||
def renderer(field, decorator=False):
|
||||
idfunc = self.db.id
|
||||
@ -776,8 +777,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
def func(idx):
|
||||
return (QDateTime(as_local_time(fffunc(field_obj, idfunc(idx), default_value=UNDEFINED_DATE))))
|
||||
elif dt == 'rating':
|
||||
rating_fields[field] = m['display'].get('allow_half_stars', False)
|
||||
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':
|
||||
sidx_field = self.db.new_api.fields[field + '_index']
|
||||
def func(idx):
|
||||
@ -817,8 +819,20 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
elif dt == '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
|
||||
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]
|
||||
|
||||
def data(self, index, role):
|
||||
@ -850,7 +864,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
return None
|
||||
self.icon_cache[id_][cache_index] = None
|
||||
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())
|
||||
elif role == Qt.BackgroundRole:
|
||||
if self.id(index) in self.ids_to_highlight_set:
|
||||
@ -996,9 +1012,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
elif typ == 'bool':
|
||||
val = value if value is None else bool(value)
|
||||
elif typ == 'rating':
|
||||
val = int(value)
|
||||
val = 0 if val < 0 else 5 if val > 5 else val
|
||||
val *= 2
|
||||
val = max(0, min(int(value or 0), 10))
|
||||
elif typ in ('int', 'float'):
|
||||
if value == 0:
|
||||
val = '0'
|
||||
@ -1089,8 +1103,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
id = self.db.id(row)
|
||||
books_to_refresh = set([id])
|
||||
if column == 'rating':
|
||||
val = 0 if val < 0 else 5 if val > 5 else val
|
||||
val *= 2
|
||||
val = max(0, min(int(val or 0), 10))
|
||||
self.db.set_rating(id, val)
|
||||
elif column == 'series':
|
||||
val = val.strip()
|
||||
|
@ -224,6 +224,7 @@ class BooksView(QTableView): # {{{
|
||||
self.setWordWrap(False)
|
||||
|
||||
self.rating_delegate = RatingDelegate(self)
|
||||
self.half_rating_delegate = RatingDelegate(self, is_half_star=True)
|
||||
self.timestamp_delegate = DateDelegate(self)
|
||||
self.pubdate_delegate = PubDateDelegate(self)
|
||||
self.last_modified_delegate = DateDelegate(self,
|
||||
@ -760,9 +761,9 @@ class BooksView(QTableView): # {{{
|
||||
def database_changed(self, db):
|
||||
db.data.add_marked_listener(self.marked_changed_listener)
|
||||
for i in range(self.model().columnCount(None)):
|
||||
if self.itemDelegateForColumn(i) in (self.rating_delegate,
|
||||
self.timestamp_delegate, self.pubdate_delegate,
|
||||
self.last_modified_delegate, self.languages_delegate):
|
||||
if self.itemDelegateForColumn(i) in (
|
||||
self.rating_delegate, self.timestamp_delegate, self.pubdate_delegate,
|
||||
self.last_modified_delegate, self.languages_delegate, self.half_rating_delegate):
|
||||
self.setItemDelegateForColumn(i, self.itemDelegate())
|
||||
|
||||
cm = self.column_map
|
||||
@ -799,7 +800,8 @@ class BooksView(QTableView): # {{{
|
||||
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)
|
||||
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':
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_template_delegate)
|
||||
elif cc['datatype'] == 'enumeration':
|
||||
@ -1126,6 +1128,7 @@ class DeviceBooksView(BooksView): # {{{
|
||||
self.can_add_columns = False
|
||||
self.resize_on_select = False
|
||||
self.rating_delegate = None
|
||||
self.half_rating_delegate = None
|
||||
for i in range(10):
|
||||
self.setItemDelegateForColumn(i, TextDelegate(self))
|
||||
self.setDragDropMode(self.NoDragDrop)
|
||||
|
@ -13,12 +13,12 @@ from datetime import date, datetime
|
||||
from PyQt5.Qt import (
|
||||
Qt, QDateTimeEdit, pyqtSignal, QMessageBox, QIcon, QToolButton, QWidget,
|
||||
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,
|
||||
QUndoStack, QVBoxLayout, QPlainTextEdit)
|
||||
|
||||
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.config import tweaks, prefs
|
||||
from calibre.ebooks.metadata import (
|
||||
@ -1231,7 +1231,7 @@ class CommentsEdit(Editor, ToMetadataMixin): # {{{
|
||||
db.set_comment(id_, self.current_val, notify=False, commit=False)
|
||||
# }}}
|
||||
|
||||
class RatingEdit(make_undoable(QSpinBox), ToMetadataMixin): # {{{
|
||||
class RatingEdit(RatingEditor, ToMetadataMixin): # {{{
|
||||
LABEL = _('&Rating:')
|
||||
TOOLTIP = _('Rating of this book. 0-5 stars')
|
||||
FIELD_NAME = 'rating'
|
||||
@ -1240,40 +1240,26 @@ class RatingEdit(make_undoable(QSpinBox), ToMetadataMixin): # {{{
|
||||
super(RatingEdit, self).__init__(parent)
|
||||
self.setToolTip(self.TOOLTIP)
|
||||
self.setWhatsThis(self.TOOLTIP)
|
||||
self.setMaximum(5)
|
||||
self.setSuffix(' ' + _('stars'))
|
||||
self.setSpecialValueText(_('Not rated'))
|
||||
|
||||
@dynamic_property
|
||||
def current_val(self):
|
||||
def fget(self):
|
||||
return self.value()
|
||||
return self.rating_value
|
||||
def fset(self, val):
|
||||
if val is None:
|
||||
val = 0
|
||||
val = int(val)
|
||||
if val < 0:
|
||||
val = 0
|
||||
if val > 5:
|
||||
val = 5
|
||||
self.set_spinbox_value(val)
|
||||
self.rating_value = val
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
def initialize(self, db, id_):
|
||||
val = db.rating(id_, index_is_id=True)
|
||||
if val > 0:
|
||||
val = int(val/2.)
|
||||
else:
|
||||
val = 0
|
||||
self.current_val = val
|
||||
self.original_val = self.current_val
|
||||
|
||||
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
|
||||
|
||||
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'):
|
||||
self.author_sort.auto_generate()
|
||||
if not mi.is_null('rating'):
|
||||
try:
|
||||
self.rating.set_value(mi.rating)
|
||||
except:
|
||||
pass
|
||||
self.rating.set_value(mi.rating * 2)
|
||||
if not mi.is_null('publisher'):
|
||||
self.publisher.set_value(mi.publisher)
|
||||
if not mi.is_null('tags'):
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=UTF-8
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
@ -168,6 +171,8 @@ class CreateCustomColumn(QDialog):
|
||||
self.comments_heading_position.setCurrentIndex(idx)
|
||||
idx = max(0, self.comments_type.findData(c['display'].get('interpret_as', 'html')))
|
||||
self.comments_type.setCurrentIndex(idx)
|
||||
elif ct == 'rating':
|
||||
self.allow_half_stars.setChecked(bool(c['display'].get('allow_half_stars', False)))
|
||||
self.datatype_changed()
|
||||
if ct in ['text', 'composite', 'enumeration']:
|
||||
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)
|
||||
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
|
||||
l = QHBoxLayout()
|
||||
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_type.setVisible(is_comments)
|
||||
self.comments_type_label.setVisible(is_comments)
|
||||
self.allow_half_stars.setVisible(col_type == 'rating')
|
||||
|
||||
def accept(self):
|
||||
col = unicode(self.column_name_box.text()).strip()
|
||||
@ -524,6 +535,8 @@ class CreateCustomColumn(QDialog):
|
||||
elif col_type == 'comments':
|
||||
display_dict['heading_position'] = type(u'')(self.comments_heading_position.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:
|
||||
display_dict['use_decorations'] = self.use_decorations.checkState()
|
||||
|
@ -6,11 +6,16 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import weakref
|
||||
|
||||
from PyQt5.Qt import (
|
||||
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.widgets import history
|
||||
|
||||
@ -180,3 +185,106 @@ class Dialog(QDialog):
|
||||
def setup_ui(self):
|
||||
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