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