1############################################################################# 2## 3## Copyright (C) 2013 Riverbank Computing Limited. 4## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 5## All rights reserved. 6## 7## This file is part of the examples of PyQt. 8## 9## $QT_BEGIN_LICENSE:LGPL$ 10## Commercial Usage 11## Licensees holding valid Qt Commercial licenses may use this file in 12## accordance with the Qt Commercial License Agreement provided with the 13## Software or, alternatively, in accordance with the terms contained in 14## a written agreement between you and Nokia. 15## 16## GNU Lesser General Public License Usage 17## Alternatively, this file may be used under the terms of the GNU Lesser 18## General Public License version 2.1 as published by the Free Software 19## Foundation and appearing in the file LICENSE.LGPL included in the 20## packaging of this file. Please review the following information to 21## ensure the GNU Lesser General Public License version 2.1 requirements 22## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 23## 24## In addition, as a special exception, Nokia gives you certain additional 25## rights. These rights are described in the Nokia Qt LGPL Exception 26## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 27## 28## GNU General Public License Usage 29## Alternatively, this file may be used under the terms of the GNU 30## General Public License version 3.0 as published by the Free Software 31## Foundation and appearing in the file LICENSE.GPL included in the 32## packaging of this file. Please review the following information to 33## ensure the GNU General Public License version 3.0 requirements will be 34## met: http://www.gnu.org/copyleft/gpl.html. 35## 36## If you have questions regarding the use of this file, please contact 37## Nokia at qt-info@nokia.com. 38## $QT_END_LICENSE$ 39## 40############################################################################# 41 42 43from PyQt5.QtCore import QPointF, QRect, QRectF, QSize, Qt 44from PyQt5.QtGui import (QColor, QImage, QLinearGradient, QPainter, 45 QPainterPath, QPen) 46 47from colors import Colors 48from demoitem import DemoItem 49from demoitemanimation import DemoItemAnimation 50from demotextitem import DemoTextItem 51from scanitem import ScanItem 52 53 54class ButtonBackground(DemoItem): 55 def __init__(self, type, highlighted, pressed, logicalSize, parent): 56 super(ButtonBackground, self).__init__(parent) 57 58 self.type = type 59 self.highlighted = highlighted 60 self.pressed = pressed 61 self.logicalSize = logicalSize 62 self.useSharedImage('%s%d%d%d' % (__file__, type, highlighted, pressed)) 63 64 def createImage(self, transform): 65 if self.type in (TextButton.SIDEBAR, TextButton.PANEL): 66 return self.createRoundButtonBackground(transform) 67 else: 68 return self.createArrowBackground(transform) 69 70 def createRoundButtonBackground(self, transform): 71 scaledRect = transform.mapRect(QRect(0, 0, 72 self.logicalSize.width(), self.logicalSize.height())) 73 74 image = QImage(scaledRect.width(), scaledRect.height(), 75 QImage.Format_ARGB32_Premultiplied) 76 image.fill(QColor(0, 0, 0, 0).rgba()) 77 painter = QPainter(image) 78 painter.setRenderHint(QPainter.SmoothPixmapTransform) 79 painter.setRenderHint(QPainter.Antialiasing) 80 painter.setPen(Qt.NoPen) 81 82 if Colors.useEightBitPalette: 83 painter.setPen(QColor(120, 120, 120)) 84 if self.pressed: 85 painter.setBrush(QColor(60, 60, 60)) 86 elif self.highlighted: 87 painter.setBrush(QColor(100, 100, 100)) 88 else: 89 painter.setBrush(QColor(80, 80, 80)) 90 else: 91 outlinebrush = QLinearGradient(0, 0, 0, scaledRect.height()) 92 brush = QLinearGradient(0, 0, 0, scaledRect.height()) 93 94 brush.setSpread(QLinearGradient.PadSpread) 95 highlight = QColor(255, 255, 255, 70) 96 shadow = QColor(0, 0, 0, 70) 97 sunken = QColor(220, 220, 220, 30) 98 99 if self.type == TextButton.PANEL: 100 normal1 = QColor(200, 170, 160, 50) 101 normal2 = QColor(50, 10, 0, 50) 102 else: 103 normal1 = QColor(255, 255, 245, 60) 104 normal2 = QColor(255, 255, 235, 10) 105 106 if self.pressed: 107 outlinebrush.setColorAt(0, shadow) 108 outlinebrush.setColorAt(1, highlight) 109 brush.setColorAt(0, sunken) 110 painter.setPen(Qt.NoPen) 111 else: 112 outlinebrush.setColorAt(1, shadow) 113 outlinebrush.setColorAt(0, highlight) 114 brush.setColorAt(0, normal1) 115 if not self.highlighted: 116 brush.setColorAt(1, normal2) 117 painter.setPen(QPen(outlinebrush, 1)) 118 119 painter.setBrush(brush) 120 121 if self.type == TextButton.PANEL: 122 painter.drawRect(0, 0, scaledRect.width(), scaledRect.height()) 123 else: 124 painter.drawRoundedRect(0, 0, scaledRect.width(), 125 scaledRect.height(), 10, 90, Qt.RelativeSize) 126 127 return image 128 129 def createArrowBackground(self, transform): 130 scaledRect = transform.mapRect(QRect(0, 0, 131 self.logicalSize.width(), self.logicalSize.height())) 132 133 image = QImage(scaledRect.width(), scaledRect.height(), 134 QImage.Format_ARGB32_Premultiplied) 135 image.fill(QColor(0, 0, 0, 0).rgba()) 136 painter = QPainter(image) 137 painter.setRenderHint(QPainter.SmoothPixmapTransform) 138 painter.setRenderHint(QPainter.Antialiasing) 139 painter.setPen(Qt.NoPen) 140 141 if Colors.useEightBitPalette: 142 painter.setPen(QColor(120, 120, 120)) 143 if self.pressed: 144 painter.setBrush(QColor(60, 60, 60)) 145 elif self.highlighted: 146 painter.setBrush(QColor(100, 100, 100)) 147 else: 148 painter.setBrush(QColor(80, 80, 80)) 149 else: 150 outlinebrush = QLinearGradient(0, 0, 0, scaledRect.height()) 151 brush = QLinearGradient(0, 0, 0, scaledRect.height()) 152 153 brush.setSpread(QLinearGradient.PadSpread) 154 highlight = QColor(255, 255, 255, 70) 155 shadow = QColor(0, 0, 0, 70) 156 sunken = QColor(220, 220, 220, 30) 157 normal1 = QColor(200, 170, 160, 50) 158 normal2 = QColor(50, 10, 0, 50) 159 160 if self.pressed: 161 outlinebrush.setColorAt(0, shadow) 162 outlinebrush.setColorAt(1, highlight) 163 brush.setColorAt(0, sunken) 164 painter.setPen(Qt.NoPen) 165 else: 166 outlinebrush.setColorAt(1, shadow) 167 outlinebrush.setColorAt(0, highlight) 168 brush.setColorAt(0, normal1) 169 if not self.highlighted: 170 brush.setColorAt(1, normal2) 171 painter.setPen(QPen(outlinebrush, 1)) 172 173 painter.setBrush(brush); 174 175 painter.drawRect(0, 0, scaledRect.width(), scaledRect.height()) 176 177 xOff = scaledRect.width() / 2 178 yOff = scaledRect.height() / 2 179 sizex = 3.0 * transform.m11() 180 sizey = 1.5 * transform.m22() 181 if self.type == TextButton.UP: 182 sizey *= -1 183 path = QPainterPath() 184 path.moveTo(xOff, yOff + (5 * sizey)) 185 path.lineTo(xOff - (4 * sizex), yOff - (3 * sizey)) 186 path.lineTo(xOff + (4 * sizex), yOff - (3 * sizey)) 187 path.lineTo(xOff, yOff + (5 * sizey)) 188 painter.drawPath(path) 189 190 return image 191 192 193class TextButton(DemoItem): 194 BUTTON_WIDTH = 180 195 BUTTON_HEIGHT = 19 196 197 LEFT, RIGHT = range(2) 198 199 SIDEBAR, PANEL, UP, DOWN = range(4) 200 201 ON, OFF, HIGHLIGHT, DISABLED = range(4) 202 203 def __init__(self, text, align=LEFT, userCode=0, parent=None, type=SIDEBAR): 204 super(TextButton, self).__init__(parent) 205 206 # Prevent a circular import. 207 from menumanager import MenuManager 208 self._menu_manager = MenuManager.instance() 209 210 self.menuString = text 211 self.buttonLabel = text 212 self.alignment = align 213 self.buttonType = type 214 self.userCode = userCode 215 self.scanAnim = None 216 self.bgOn = None 217 self.bgOff = None 218 self.bgHighlight = None 219 self.bgDisabled = None 220 self.state = TextButton.OFF 221 222 self.setAcceptHoverEvents(True) 223 self.setCursor(Qt.PointingHandCursor) 224 225 # Calculate the button size. 226 if type in (TextButton.SIDEBAR, TextButton.PANEL): 227 self.logicalSize = QSize(TextButton.BUTTON_WIDTH, TextButton.BUTTON_HEIGHT) 228 else: 229 self.logicalSize = QSize(int((TextButton.BUTTON_WIDTH / 2.0) - 5), int(TextButton.BUTTON_HEIGHT * 1.5)) 230 231 self._prepared = False 232 233 def setMenuString(self, menu): 234 self.menuString = menu 235 236 def prepare(self): 237 if not self._prepared: 238 self.setupHoverText() 239 self.setupScanItem() 240 self.setupButtonBg() 241 self._prepared = True 242 243 def boundingRect(self): 244 return QRectF(0, 0, self.logicalSize.width(), 245 self.logicalSize.height()) 246 247 def setupHoverText(self): 248 if not self.buttonLabel: 249 return 250 251 textItem = DemoTextItem(self.buttonLabel, Colors.buttonFont(), 252 Colors.buttonText, -1, self) 253 textItem.setZValue(self.zValue() + 2) 254 textItem.setPos(16, 0) 255 256 def setupScanItem(self): 257 if Colors.useButtonBalls: 258 scanItem = ScanItem(self) 259 scanItem.setZValue(self.zValue() + 1) 260 261 self.scanAnim = DemoItemAnimation(scanItem) 262 263 x = 1.0 264 y = 1.5 265 stop = TextButton.BUTTON_WIDTH - scanItem.boundingRect().width() - x 266 if self.alignment == TextButton.LEFT: 267 self.scanAnim.setDuration(2500) 268 self.scanAnim.setKeyValueAt(0.0, QPointF(x, y)) 269 self.scanAnim.setKeyValueAt(0.5, QPointF(x, y)) 270 self.scanAnim.setKeyValueAt(0.7, QPointF(stop, y)) 271 self.scanAnim.setKeyValueAt(1.0, QPointF(x, y)) 272 scanItem.setPos(QPointF(x, y)) 273 else: 274 self.scanAnim.setKeyValueAt(0.0, QPointF(stop, y)) 275 self.scanAnim.setKeyValueAt(0.5, QPointF(x, y)) 276 self.scanAnim.setKeyValueAt(1.0, QPointF(stop, y)) 277 scanItem.setPos(QPointF(stop, y)) 278 279 def setState(self, state): 280 self.state = state 281 self.bgOn.setRecursiveVisible(state == TextButton.ON) 282 self.bgOff.setRecursiveVisible(state == TextButton.OFF) 283 self.bgHighlight.setRecursiveVisible(state == TextButton.HIGHLIGHT) 284 self.bgDisabled.setRecursiveVisible(state == TextButton.DISABLED) 285 if state == TextButton.DISABLED: 286 self.setCursor(Qt.ArrowCursor) 287 else: 288 self.setCursor(Qt.PointingHandCursor) 289 290 def setupButtonBg(self): 291 self.bgOn = ButtonBackground(self.buttonType, True, True, 292 self.logicalSize, self) 293 self.bgOff = ButtonBackground(self.buttonType, False, False, 294 self.logicalSize, self) 295 self.bgHighlight = ButtonBackground(self.buttonType, True, False, 296 self.logicalSize, self) 297 self.bgDisabled = ButtonBackground(self.buttonType, True, True, 298 self.logicalSize, self) 299 self.setState(TextButton.OFF) 300 301 def hoverEnterEvent(self, event): 302 if not self.isEnabled() or self.state == TextButton.DISABLED: 303 return 304 305 if self.state == TextButton.OFF: 306 self.setState(TextButton.HIGHLIGHT) 307 308 if Colors.noAnimations and Colors.useButtonBalls: 309 # Wait a bit in the beginning to enhance the effect. We have 310 # to do this here so that the adaption can be dynamic. 311 self.scanAnim.setDuration(1000) 312 self.scanAnim.setKeyValueAt(0.2, self.scanAnim.posAt(0)) 313 314 if (self._menu_manager.window.fpsMedian > 10 or Colors.noAdapt or 315 Colors.noTimerUpdate): 316 if Colors.useButtonBalls: 317 self.scanAnim.play(True, True) 318 319 def hoverLeaveEvent(self, event): 320 if self.state == TextButton.DISABLED: 321 return 322 323 self.setState(TextButton.OFF) 324 325 if Colors.noAnimations and Colors.useButtonBalls: 326 self.scanAnim.stop() 327 328 def mousePressEvent(self, event): 329 if self.state == TextButton.DISABLED: 330 return 331 332 if self.state == TextButton.HIGHLIGHT or self.state == TextButton.OFF: 333 self.setState(TextButton.ON) 334 335 def mouseReleaseEvent(self, event): 336 if self.state == TextButton.ON: 337 self.setState(TextButton.OFF) 338 if self.isEnabled() and self.boundingRect().contains(event.pos()): 339 self._menu_manager.itemSelected(self.userCode, self.menuString) 340 341 def animationStarted(self, _): 342 if self.state == TextButton.DISABLED: 343 return 344 345 self.setState(TextButton.OFF) 346