1#!/usr/bin/env python 2 3 4############################################################################# 5## 6## Copyright (C) 2013 Riverbank Computing Limited 7## Copyright (C) 2010 Hans-Peter Jansen <hpj@urpla.net>. 8## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 9## All rights reserved. 10## 11## This file is part of the examples of PyQt. 12## 13## $QT_BEGIN_LICENSE:BSD$ 14## You may use this file under the terms of the BSD license as follows: 15## 16## "Redistribution and use in source and binary forms, with or without 17## modification, are permitted provided that the following conditions are 18## met: 19## * Redistributions of source code must retain the above copyright 20## notice, this list of conditions and the following disclaimer. 21## * Redistributions in binary form must reproduce the above copyright 22## notice, this list of conditions and the following disclaimer in 23## the documentation and/or other materials provided with the 24## distribution. 25## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor 26## the names of its contributors may be used to endorse or promote 27## products derived from this software without specific prior written 28## permission. 29## 30## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 31## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 32## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 33## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 34## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 36## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 37## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 38## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 39## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 40## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 41## $QT_END_LICENSE$ 42## 43############################################################################# 44 45 46import math 47 48from PyQt5.QtCore import pyqtSignal, QPointF, QSize, Qt 49from PyQt5.QtGui import QPainter, QPolygonF 50from PyQt5.QtWidgets import (QAbstractItemView, QApplication, QStyle, 51 QStyledItemDelegate, QTableWidget, QTableWidgetItem, QWidget) 52 53 54class StarRating(object): 55 # enum EditMode 56 Editable, ReadOnly = range(2) 57 58 PaintingScaleFactor = 20 59 60 def __init__(self, starCount=1, maxStarCount=5): 61 self._starCount = starCount 62 self._maxStarCount = maxStarCount 63 64 self.starPolygon = QPolygonF([QPointF(1.0, 0.5)]) 65 for i in range(5): 66 self.starPolygon << QPointF(0.5 + 0.5 * math.cos(0.8 * i * math.pi), 67 0.5 + 0.5 * math.sin(0.8 * i * math.pi)) 68 69 self.diamondPolygon = QPolygonF() 70 self.diamondPolygon << QPointF(0.4, 0.5) \ 71 << QPointF(0.5, 0.4) \ 72 << QPointF(0.6, 0.5) \ 73 << QPointF(0.5, 0.6) \ 74 << QPointF(0.4, 0.5) 75 76 def starCount(self): 77 return self._starCount 78 79 def maxStarCount(self): 80 return self._maxStarCount 81 82 def setStarCount(self, starCount): 83 self._starCount = starCount 84 85 def setMaxStarCount(self, maxStarCount): 86 self._maxStarCount = maxStarCount 87 88 def sizeHint(self): 89 return self.PaintingScaleFactor * QSize(self._maxStarCount, 1) 90 91 def paint(self, painter, rect, palette, editMode): 92 painter.save() 93 94 painter.setRenderHint(QPainter.Antialiasing, True) 95 painter.setPen(Qt.NoPen) 96 97 if editMode == StarRating.Editable: 98 painter.setBrush(palette.highlight()) 99 else: 100 painter.setBrush(palette.windowText()) 101 102 yOffset = (rect.height() - self.PaintingScaleFactor) / 2 103 painter.translate(rect.x(), rect.y() + yOffset) 104 painter.scale(self.PaintingScaleFactor, self.PaintingScaleFactor) 105 106 for i in range(self._maxStarCount): 107 if i < self._starCount: 108 painter.drawPolygon(self.starPolygon, Qt.WindingFill) 109 elif editMode == StarRating.Editable: 110 painter.drawPolygon(self.diamondPolygon, Qt.WindingFill) 111 112 painter.translate(1.0, 0.0) 113 114 painter.restore() 115 116 117class StarEditor(QWidget): 118 119 editingFinished = pyqtSignal() 120 121 def __init__(self, parent = None): 122 super(StarEditor, self).__init__(parent) 123 124 self._starRating = StarRating() 125 126 self.setMouseTracking(True) 127 self.setAutoFillBackground(True) 128 129 def setStarRating(self, starRating): 130 self._starRating = starRating 131 132 def starRating(self): 133 return self._starRating 134 135 def sizeHint(self): 136 return self._starRating.sizeHint() 137 138 def paintEvent(self, event): 139 painter = QPainter(self) 140 self._starRating.paint(painter, self.rect(), self.palette(), 141 StarRating.Editable) 142 143 def mouseMoveEvent(self, event): 144 star = self.starAtPosition(event.x()) 145 146 if star != self._starRating.starCount() and star != -1: 147 self._starRating.setStarCount(star) 148 self.update() 149 150 def mouseReleaseEvent(self, event): 151 self.editingFinished.emit() 152 153 def starAtPosition(self, x): 154 # Enable a star, if pointer crosses the center horizontally. 155 starwidth = self._starRating.sizeHint().width() // self._starRating.maxStarCount() 156 star = (x + starwidth / 2) // starwidth 157 if 0 <= star <= self._starRating.maxStarCount(): 158 return star 159 160 return -1 161 162 163class StarDelegate(QStyledItemDelegate): 164 def paint(self, painter, option, index): 165 starRating = index.data() 166 if isinstance(starRating, StarRating): 167 if option.state & QStyle.State_Selected: 168 painter.fillRect(option.rect, option.palette.highlight()) 169 170 starRating.paint(painter, option.rect, option.palette, 171 StarRating.ReadOnly) 172 else: 173 super(StarDelegate, self).paint(painter, option, index) 174 175 def sizeHint(self, option, index): 176 starRating = index.data() 177 if isinstance(starRating, StarRating): 178 return starRating.sizeHint() 179 else: 180 return super(StarDelegate, self).sizeHint(option, index) 181 182 def createEditor(self, parent, option, index): 183 starRating = index.data() 184 if isinstance(starRating, StarRating): 185 editor = StarEditor(parent) 186 editor.editingFinished.connect(self.commitAndCloseEditor) 187 return editor 188 else: 189 return super(StarDelegate, self).createEditor(parent, option, index) 190 191 def setEditorData(self, editor, index): 192 starRating = index.data() 193 if isinstance(starRating, StarRating): 194 editor.setStarRating(starRating) 195 else: 196 super(StarDelegate, self).setEditorData(editor, index) 197 198 def setModelData(self, editor, model, index): 199 starRating = index.data() 200 if isinstance(starRating, StarRating): 201 model.setData(index, editor.starRating()) 202 else: 203 super(StarDelegate, self).setModelData(editor, model, index) 204 205 def commitAndCloseEditor(self): 206 editor = self.sender() 207 self.commitData.emit(editor) 208 self.closeEditor.emit(editor) 209 210 211def populateTableWidget(tableWidget): 212 staticData = ( 213 ("Mass in B-Minor", "Baroque", "J.S. Bach", 5), 214 ("Three More Foxes", "Jazz", "Maynard Ferguson", 4), 215 ("Sex Bomb", "Pop", "Tom Jones", 3), 216 ("Barbie Girl", "Pop", "Aqua", 5), 217 ) 218 219 for row, (title, genre, artist, rating) in enumerate(staticData): 220 item0 = QTableWidgetItem(title) 221 item1 = QTableWidgetItem(genre) 222 item2 = QTableWidgetItem(artist) 223 item3 = QTableWidgetItem() 224 item3.setData(0, StarRating(rating)) 225 tableWidget.setItem(row, 0, item0) 226 tableWidget.setItem(row, 1, item1) 227 tableWidget.setItem(row, 2, item2) 228 tableWidget.setItem(row, 3, item3) 229 230 231if __name__ == '__main__': 232 233 import sys 234 235 app = QApplication(sys.argv) 236 237 tableWidget = QTableWidget(4, 4) 238 tableWidget.setItemDelegate(StarDelegate()) 239 tableWidget.setEditTriggers( 240 QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked) 241 tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) 242 243 headerLabels = ("Title", "Genre", "Artist", "Rating") 244 tableWidget.setHorizontalHeaderLabels(headerLabels) 245 246 populateTableWidget(tableWidget) 247 248 tableWidget.resizeColumnsToContents() 249 tableWidget.resize(500, 300) 250 tableWidget.show() 251 252 sys.exit(app.exec_()) 253