Skip to content

Commit 69982d9

Browse files
authored
Add ColorPicker dialog with checkerboard alpha (#3813)
1 parent 3aa3c15 commit 69982d9

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

src/windows/color_picker.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""
2+
@file
3+
@brief A non-modal Qt color picker dialog launcher
4+
@author FeRD (Frank Dana) <[email protected]>
5+
6+
@section LICENSE
7+
8+
Copyright (c) 2008-2020 OpenShot Studios, LLC
9+
(http://www.openshotstudios.com). This file is part of
10+
OpenShot Video Editor (http://www.openshot.org), an open-source project
11+
dedicated to delivering high quality video editing and animation solutions
12+
to the world.
13+
14+
OpenShot Video Editor is free software: you can redistribute it and/or modify
15+
it under the terms of the GNU General Public License as published by
16+
the Free Software Foundation, either version 3 of the License, or
17+
(at your option) any later version.
18+
19+
OpenShot Video Editor is distributed in the hope that it will be useful,
20+
but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
GNU General Public License for more details.
23+
24+
You should have received a copy of the GNU General Public License
25+
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
26+
"""
27+
28+
from classes.logger import log
29+
30+
from PyQt5.QtCore import Qt, QTimer, QRect, QPoint
31+
from PyQt5.QtGui import QColor, QBrush, QPen, QPalette, QPainter, QPixmap
32+
from PyQt5.QtWidgets import QColorDialog, QWidget, QLabel
33+
34+
35+
class ColorPicker(QWidget):
36+
"""Show a non-modal color picker.
37+
QColorDialog.colorSelected(QColor) is emitted when the user picks a color"""
38+
39+
def __init__(self, initial_color: QColor, callback, extra_options=0,
40+
parent=None, title=None, *args, **kwargs):
41+
super().__init__(parent=parent, *args, **kwargs)
42+
self.setObjectName("ColorPicker")
43+
# Merge any additional user-supplied options with our own
44+
options = QColorDialog.DontUseNativeDialog
45+
if extra_options > 0:
46+
options = options | extra_options
47+
# Set up non-modal color dialog (to avoid blocking the eyedropper)
48+
log.debug(
49+
"Loading QColorDialog with start value %s",
50+
initial_color.getRgb())
51+
self.dialog = CPDialog(initial_color, parent)
52+
self.dialog.setObjectName("CPDialog")
53+
if title:
54+
self.dialog.setWindowTitle(title)
55+
self.dialog.setWindowFlags(Qt.Tool)
56+
self.dialog.setOptions(options)
57+
# Avoid signal loops
58+
self.dialog.blockSignals(True)
59+
self.dialog.colorSelected.connect(callback)
60+
self.dialog.finished.connect(self.dialog.deleteLater)
61+
self.dialog.finished.connect(self.deleteLater)
62+
self.dialog.setCurrentColor(initial_color)
63+
self.dialog.blockSignals(False)
64+
self.dialog.open()
65+
# Seems to help if this is done AFTER init() returns
66+
QTimer.singleShot(0, self.add_alpha)
67+
def add_alpha(self):
68+
self.dialog.replace_label()
69+
70+
71+
class CPDialog(QColorDialog):
72+
"""Show a modified QColorDialog which supports checkerboard alpha"""
73+
def __init__(self, *args, **kwargs):
74+
super().__init__(*args, **kwargs)
75+
log.debug("CPDialog initialized")
76+
self.alpha_label = CPAlphaShowLabel(self)
77+
self.alpha_label.setObjectName("alpha_label")
78+
self.currentColorChanged.connect(self.alpha_label.updateColor)
79+
def replace_label(self):
80+
log.debug("Beginning discovery for QColorShowLabel widget")
81+
# Find the dialog widget used to display the current
82+
# color, so we can replace it with our implementation
83+
try:
84+
qcs = [
85+
a for a in self.children()
86+
if hasattr(a, "metaObject")
87+
and a.metaObject().className() == 'QColorShower'
88+
][0]
89+
log.debug("Found QColorShower: %s", qcs)
90+
qcsl = [
91+
b for b in qcs.children()
92+
if hasattr(b, "metaObject")
93+
and b.metaObject().className() == 'QColorShowLabel'
94+
][0]
95+
log.debug("Found QColorShowLabel: %s", qcsl)
96+
except IndexError as ex:
97+
child_list = [
98+
a.metaObject().className()
99+
for a in self.children()
100+
if hasattr(a, "metaObject")
101+
]
102+
log.debug("%d children of CPDialog %s", len(child_list), child_list)
103+
raise RuntimeError("Could not find label to replace!") from ex
104+
qcslay = qcs.layout()
105+
log.debug(
106+
"QColorShowLabel found at layout index %d", qcslay.indexOf(qcsl))
107+
qcs.layout().replaceWidget(qcsl, self.alpha_label)
108+
log.debug("Replaced QColorShowLabel widget, hiding original")
109+
# Make sure it doesn't receive signals while hidden
110+
qcsl.blockSignals(True)
111+
qcsl.hide()
112+
self.alpha_label.show()
113+
114+
115+
class CPAlphaShowLabel(QLabel):
116+
"""A replacement for QColorDialog's QColorShowLabel which
117+
displays the currently-active color using checkerboard alpha"""
118+
def __init__(self, *args, **kwargs):
119+
super().__init__(*args, **kwargs)
120+
# Length in pixels of a side of the checkerboard squares
121+
# (Pattern is made up of 2×2 squares, total size 2n × 2n)
122+
self.checkerboard_size = 8
123+
# Start out transparent by default
124+
self.color = super().parent().currentColor()
125+
self.build_pattern()
126+
log.debug("CPAlphaShowLabel initialized, creating brush")
127+
def updateColor(self, color: QColor):
128+
self.color = color
129+
log.debug("Label color set to %s", str(color.getRgb()))
130+
self.repaint()
131+
def build_pattern(self) -> QPixmap:
132+
"""Construct tileable checkerboard pattern for paint events"""
133+
# Brush will be an n×n checkerboard pattern
134+
n = self.checkerboard_size
135+
pat = QPixmap(2 * n, 2 * n)
136+
p = QPainter(pat)
137+
p.setPen(Qt.NoPen)
138+
# Paint a checkerboard pattern for the color to be overlaid on
139+
self.bg0 = QColor("#aaa")
140+
self.bg1 = QColor("#ccc")
141+
p.fillRect(pat.rect(), self.bg0)
142+
p.fillRect(QRect(0, 0, n, n), self.bg1)
143+
p.fillRect(QRect(n, n, 2 * n, 2 * n), self.bg1)
144+
p.end()
145+
log.debug("Constructed %s checkerboard brush", pat.size())
146+
self.pattern = pat
147+
def paintEvent(self, event):
148+
"""Show the current color, with checkerboard alpha"""
149+
event.accept()
150+
p = QPainter(self)
151+
p.setPen(Qt.NoPen)
152+
if self.color.alphaF() < 1.0:
153+
# Draw a checkerboard pattern under the color
154+
p.drawTiledPixmap(event.rect(), self.pattern, QPoint(4,4))
155+
p.fillRect(event.rect(), self.color)
156+
p.end()

0 commit comments

Comments
 (0)