1#!/usr/bin/env python 2 3 4############################################################################# 5## 6## Copyright (C) 2013 Riverbank Computing Limited. 7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 8## All rights reserved. 9## 10## This file is part of the examples of PyQt. 11## 12## $QT_BEGIN_LICENSE:BSD$ 13## You may use this file under the terms of the BSD license as follows: 14## 15## "Redistribution and use in source and binary forms, with or without 16## modification, are permitted provided that the following conditions are 17## met: 18## * Redistributions of source code must retain the above copyright 19## notice, this list of conditions and the following disclaimer. 20## * Redistributions in binary form must reproduce the above copyright 21## notice, this list of conditions and the following disclaimer in 22## the documentation and/or other materials provided with the 23## distribution. 24## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor 25## the names of its contributors may be used to endorse or promote 26## products derived from this software without specific prior written 27## permission. 28## 29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 40## $QT_END_LICENSE$ 41## 42############################################################################# 43 44 45from PyQt5.QtCore import QFileInfo, QRegExp, QSize, Qt 46from PyQt5.QtGui import QIcon, QImage, QPalette, QPixmap 47from PyQt5.QtWidgets import (QAbstractItemView, QAction, QActionGroup, 48 QApplication, QComboBox, QFileDialog, QFrame, QGridLayout, QGroupBox, 49 QHBoxLayout, QHeaderView, QItemDelegate, QLabel, QMainWindow, 50 QMessageBox, QRadioButton, QSizePolicy, QSpinBox, QStyle, 51 QStyleFactory, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget) 52 53 54class IconSizeSpinBox(QSpinBox): 55 @staticmethod 56 def valueFromText(text): 57 regExp = QRegExp("(\\d+)(\\s*[xx]\\s*\\d+)?") 58 59 if regExp.exactMatch(text): 60 return int(regExp.cap(1)) 61 else: 62 return 0 63 64 @staticmethod 65 def textFromValue(value): 66 return "%d x %d" % (value, value) 67 68 69class ImageDelegate(QItemDelegate): 70 def createEditor(self, parent, option, index): 71 comboBox = QComboBox(parent) 72 if index.column() == 1: 73 comboBox.addItem("Normal") 74 comboBox.addItem("Active") 75 comboBox.addItem("Disabled") 76 comboBox.addItem("Selected") 77 elif index.column() == 2: 78 comboBox.addItem("Off") 79 comboBox.addItem("On") 80 81 comboBox.activated.connect(self.emitCommitData) 82 83 return comboBox 84 85 def setEditorData(self, editor, index): 86 comboBox = editor 87 if not comboBox: 88 return 89 90 pos = comboBox.findText(index.model().data(index), Qt.MatchExactly) 91 comboBox.setCurrentIndex(pos) 92 93 def setModelData(self, editor, model, index): 94 comboBox = editor 95 if not comboBox: 96 return 97 98 model.setData(index, comboBox.currentText()) 99 100 def emitCommitData(self): 101 self.commitData.emit(self.sender()) 102 103 104class IconPreviewArea(QWidget): 105 def __init__(self, parent=None): 106 super(IconPreviewArea, self).__init__(parent) 107 108 mainLayout = QGridLayout() 109 self.setLayout(mainLayout) 110 111 self.icon = QIcon() 112 self.size = QSize() 113 self.stateLabels = [] 114 self.modeLabels = [] 115 self.pixmapLabels = [] 116 117 self.stateLabels.append(self.createHeaderLabel("Off")) 118 self.stateLabels.append(self.createHeaderLabel("On")) 119 120 self.modeLabels.append(self.createHeaderLabel("Normal")) 121 self.modeLabels.append(self.createHeaderLabel("Active")) 122 self.modeLabels.append(self.createHeaderLabel("Disabled")) 123 self.modeLabels.append(self.createHeaderLabel("Selected")) 124 125 for j, label in enumerate(self.stateLabels): 126 mainLayout.addWidget(label, j + 1, 0) 127 128 for i, label in enumerate(self.modeLabels): 129 mainLayout.addWidget(label, 0, i + 1) 130 131 self.pixmapLabels.append([]) 132 for j in range(len(self.stateLabels)): 133 self.pixmapLabels[i].append(self.createPixmapLabel()) 134 mainLayout.addWidget(self.pixmapLabels[i][j], j + 1, i + 1) 135 136 def setIcon(self, icon): 137 self.icon = icon 138 self.updatePixmapLabels() 139 140 def setSize(self, size): 141 if size != self.size: 142 self.size = size 143 self.updatePixmapLabels() 144 145 def createHeaderLabel(self, text): 146 label = QLabel("<b>%s</b>" % text) 147 label.setAlignment(Qt.AlignCenter) 148 return label 149 150 def createPixmapLabel(self): 151 label = QLabel() 152 label.setEnabled(False) 153 label.setAlignment(Qt.AlignCenter) 154 label.setFrameShape(QFrame.Box) 155 label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 156 label.setBackgroundRole(QPalette.Base) 157 label.setAutoFillBackground(True) 158 label.setMinimumSize(132, 132) 159 return label 160 161 def updatePixmapLabels(self): 162 for i in range(len(self.modeLabels)): 163 if i == 0: 164 mode = QIcon.Normal 165 elif i == 1: 166 mode = QIcon.Active 167 elif i == 2: 168 mode = QIcon.Disabled 169 else: 170 mode = QIcon.Selected 171 172 for j in range(len(self.stateLabels)): 173 state = QIcon.Off if j == 0 else QIcon.On 174 pixmap = self.icon.pixmap(self.size, mode, state) 175 self.pixmapLabels[i][j].setPixmap(pixmap) 176 self.pixmapLabels[i][j].setEnabled(not pixmap.isNull()) 177 178 179class MainWindow(QMainWindow): 180 def __init__(self, parent=None): 181 super(MainWindow, self).__init__(parent) 182 183 self.centralWidget = QWidget() 184 self.setCentralWidget(self.centralWidget) 185 186 self.createPreviewGroupBox() 187 self.createImagesGroupBox() 188 self.createIconSizeGroupBox() 189 190 self.createActions() 191 self.createMenus() 192 self.createContextMenu() 193 194 mainLayout = QGridLayout() 195 mainLayout.addWidget(self.previewGroupBox, 0, 0, 1, 2) 196 mainLayout.addWidget(self.imagesGroupBox, 1, 0) 197 mainLayout.addWidget(self.iconSizeGroupBox, 1, 1) 198 self.centralWidget.setLayout(mainLayout) 199 200 self.setWindowTitle("Icons") 201 self.checkCurrentStyle() 202 self.otherRadioButton.click() 203 204 self.resize(self.minimumSizeHint()) 205 206 def about(self): 207 QMessageBox.about(self, "About Icons", 208 "The <b>Icons</b> example illustrates how Qt renders an icon " 209 "in different modes (active, normal, disabled and selected) " 210 "and states (on and off) based on a set of images.") 211 212 def changeStyle(self, checked): 213 if not checked: 214 return 215 216 action = self.sender() 217 style = QStyleFactory.create(action.data()) 218 if not style: 219 return 220 221 QApplication.setStyle(style) 222 223 self.setButtonText(self.smallRadioButton, "Small (%d x %d)", 224 style, QStyle.PM_SmallIconSize) 225 self.setButtonText(self.largeRadioButton, "Large (%d x %d)", 226 style, QStyle.PM_LargeIconSize) 227 self.setButtonText(self.toolBarRadioButton, "Toolbars (%d x %d)", 228 style, QStyle.PM_ToolBarIconSize) 229 self.setButtonText(self.listViewRadioButton, "List views (%d x %d)", 230 style, QStyle.PM_ListViewIconSize) 231 self.setButtonText(self.iconViewRadioButton, "Icon views (%d x %d)", 232 style, QStyle.PM_IconViewIconSize) 233 self.setButtonText(self.tabBarRadioButton, "Tab bars (%d x %d)", 234 style, QStyle.PM_TabBarIconSize) 235 236 self.changeSize() 237 238 @staticmethod 239 def setButtonText(button, label, style, metric): 240 metric_value = style.pixelMetric(metric) 241 button.setText(label % (metric_value, metric_value)) 242 243 def changeSize(self, checked=True): 244 if not checked: 245 return 246 247 if self.otherRadioButton.isChecked(): 248 extent = self.otherSpinBox.value() 249 else: 250 if self.smallRadioButton.isChecked(): 251 metric = QStyle.PM_SmallIconSize 252 elif self.largeRadioButton.isChecked(): 253 metric = QStyle.PM_LargeIconSize 254 elif self.toolBarRadioButton.isChecked(): 255 metric = QStyle.PM_ToolBarIconSize 256 elif self.listViewRadioButton.isChecked(): 257 metric = QStyle.PM_ListViewIconSize 258 elif self.iconViewRadioButton.isChecked(): 259 metric = QStyle.PM_IconViewIconSize 260 else: 261 metric = QStyle.PM_TabBarIconSize 262 263 extent = QApplication.style().pixelMetric(metric) 264 265 self.previewArea.setSize(QSize(extent, extent)) 266 self.otherSpinBox.setEnabled(self.otherRadioButton.isChecked()) 267 268 def changeIcon(self): 269 icon = QIcon() 270 271 for row in range(self.imagesTable.rowCount()): 272 item0 = self.imagesTable.item(row, 0) 273 item1 = self.imagesTable.item(row, 1) 274 item2 = self.imagesTable.item(row, 2) 275 276 if item0.checkState() == Qt.Checked: 277 if item1.text() == "Normal": 278 mode = QIcon.Normal 279 elif item1.text() == "Active": 280 mode = QIcon.Active 281 elif item1.text() == "Disabled": 282 mode = QIcon.Disabled 283 else: 284 mode = QIcon.Selected 285 286 if item2.text() == "On": 287 state = QIcon.On 288 else: 289 state = QIcon.Off 290 291 fileName = item0.data(Qt.UserRole) 292 image = QImage(fileName) 293 if not image.isNull(): 294 icon.addPixmap(QPixmap.fromImage(image), mode, state) 295 296 self.previewArea.setIcon(icon) 297 298 def addImage(self): 299 fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Images", '', 300 "Images (*.png *.xpm *.jpg);;All Files (*)") 301 302 for fileName in fileNames: 303 row = self.imagesTable.rowCount() 304 self.imagesTable.setRowCount(row + 1) 305 306 imageName = QFileInfo(fileName).baseName() 307 item0 = QTableWidgetItem(imageName) 308 item0.setData(Qt.UserRole, fileName) 309 item0.setFlags(item0.flags() & ~Qt.ItemIsEditable) 310 311 item1 = QTableWidgetItem("Normal") 312 item2 = QTableWidgetItem("Off") 313 314 if self.guessModeStateAct.isChecked(): 315 if '_act' in fileName: 316 item1.setText("Active") 317 elif '_dis' in fileName: 318 item1.setText("Disabled") 319 elif '_sel' in fileName: 320 item1.setText("Selected") 321 322 if '_on' in fileName: 323 item2.setText("On") 324 325 self.imagesTable.setItem(row, 0, item0) 326 self.imagesTable.setItem(row, 1, item1) 327 self.imagesTable.setItem(row, 2, item2) 328 self.imagesTable.openPersistentEditor(item1) 329 self.imagesTable.openPersistentEditor(item2) 330 331 item0.setCheckState(Qt.Checked) 332 333 def removeAllImages(self): 334 self.imagesTable.setRowCount(0) 335 self.changeIcon() 336 337 def createPreviewGroupBox(self): 338 self.previewGroupBox = QGroupBox("Preview") 339 340 self.previewArea = IconPreviewArea() 341 342 layout = QVBoxLayout() 343 layout.addWidget(self.previewArea) 344 self.previewGroupBox.setLayout(layout) 345 346 def createImagesGroupBox(self): 347 self.imagesGroupBox = QGroupBox("Images") 348 349 self.imagesTable = QTableWidget() 350 self.imagesTable.setSelectionMode(QAbstractItemView.NoSelection) 351 self.imagesTable.setItemDelegate(ImageDelegate(self)) 352 353 self.imagesTable.horizontalHeader().setDefaultSectionSize(90) 354 self.imagesTable.setColumnCount(3) 355 self.imagesTable.setHorizontalHeaderLabels(("Image", "Mode", "State")) 356 self.imagesTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) 357 self.imagesTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.Fixed) 358 self.imagesTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) 359 self.imagesTable.verticalHeader().hide() 360 361 self.imagesTable.itemChanged.connect(self.changeIcon) 362 363 layout = QVBoxLayout() 364 layout.addWidget(self.imagesTable) 365 self.imagesGroupBox.setLayout(layout) 366 367 def createIconSizeGroupBox(self): 368 self.iconSizeGroupBox = QGroupBox("Icon Size") 369 370 self.smallRadioButton = QRadioButton() 371 self.largeRadioButton = QRadioButton() 372 self.toolBarRadioButton = QRadioButton() 373 self.listViewRadioButton = QRadioButton() 374 self.iconViewRadioButton = QRadioButton() 375 self.tabBarRadioButton = QRadioButton() 376 self.otherRadioButton = QRadioButton("Other:") 377 378 self.otherSpinBox = IconSizeSpinBox() 379 self.otherSpinBox.setRange(8, 128) 380 self.otherSpinBox.setValue(64) 381 382 self.smallRadioButton.toggled.connect(self.changeSize) 383 self.largeRadioButton.toggled.connect(self.changeSize) 384 self.toolBarRadioButton.toggled.connect(self.changeSize) 385 self.listViewRadioButton.toggled.connect(self.changeSize) 386 self.iconViewRadioButton.toggled.connect(self.changeSize) 387 self.tabBarRadioButton.toggled.connect(self.changeSize) 388 self.otherRadioButton.toggled.connect(self.changeSize) 389 self.otherSpinBox.valueChanged.connect(self.changeSize) 390 391 otherSizeLayout = QHBoxLayout() 392 otherSizeLayout.addWidget(self.otherRadioButton) 393 otherSizeLayout.addWidget(self.otherSpinBox) 394 otherSizeLayout.addStretch() 395 396 layout = QGridLayout() 397 layout.addWidget(self.smallRadioButton, 0, 0) 398 layout.addWidget(self.largeRadioButton, 1, 0) 399 layout.addWidget(self.toolBarRadioButton, 2, 0) 400 layout.addWidget(self.listViewRadioButton, 0, 1) 401 layout.addWidget(self.iconViewRadioButton, 1, 1) 402 layout.addWidget(self.tabBarRadioButton, 2, 1) 403 layout.addLayout(otherSizeLayout, 3, 0, 1, 2) 404 layout.setRowStretch(4, 1) 405 self.iconSizeGroupBox.setLayout(layout) 406 407 def createActions(self): 408 self.addImagesAct = QAction("&Add Images...", self, shortcut="Ctrl+A", 409 triggered=self.addImage) 410 411 self.removeAllImagesAct = QAction("&Remove All Images", self, 412 shortcut="Ctrl+R", triggered=self.removeAllImages) 413 414 self.exitAct = QAction("&Quit", self, shortcut="Ctrl+Q", 415 triggered=self.close) 416 417 self.styleActionGroup = QActionGroup(self) 418 for styleName in QStyleFactory.keys(): 419 action = QAction(self.styleActionGroup, 420 text="%s Style" % styleName, checkable=True, 421 triggered=self.changeStyle) 422 action.setData(styleName) 423 424 self.guessModeStateAct = QAction("&Guess Image Mode/State", self, 425 checkable=True, checked=True) 426 427 self.aboutAct = QAction("&About", self, triggered=self.about) 428 429 self.aboutQtAct = QAction("About &Qt", self, 430 triggered=QApplication.instance().aboutQt) 431 432 def createMenus(self): 433 self.fileMenu = self.menuBar().addMenu("&File") 434 self.fileMenu.addAction(self.addImagesAct) 435 self.fileMenu.addAction(self.removeAllImagesAct) 436 self.fileMenu.addSeparator() 437 self.fileMenu.addAction(self.exitAct) 438 439 self.viewMenu = self.menuBar().addMenu("&View") 440 for action in self.styleActionGroup.actions(): 441 self.viewMenu.addAction(action) 442 self.viewMenu.addSeparator() 443 self.viewMenu.addAction(self.guessModeStateAct) 444 445 self.menuBar().addSeparator() 446 447 self.helpMenu = self.menuBar().addMenu("&Help") 448 self.helpMenu.addAction(self.aboutAct) 449 self.helpMenu.addAction(self.aboutQtAct) 450 451 def createContextMenu(self): 452 self.imagesTable.setContextMenuPolicy(Qt.ActionsContextMenu) 453 self.imagesTable.addAction(self.addImagesAct) 454 self.imagesTable.addAction(self.removeAllImagesAct) 455 456 def checkCurrentStyle(self): 457 for action in self.styleActionGroup.actions(): 458 styleName = action.data() 459 candidate = QStyleFactory.create(styleName) 460 461 if candidate is None: 462 return 463 464 if candidate.metaObject().className() == QApplication.style().metaObject().className(): 465 action.trigger() 466 467 468if __name__ == '__main__': 469 470 import sys 471 472 app = QApplication(sys.argv) 473 mainWin = MainWindow() 474 mainWin.show() 475 sys.exit(app.exec_()) 476