Coverage for pyPhotoAlbum/dialogs/style_dialogs.py: 0%
182 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-20 12:55 +0000
1"""
2Style dialogs for pyPhotoAlbum
4Dialogs for configuring image styling options:
5- Corner radius
6- Border (width and color)
7- Drop shadow
8"""
10from typing import Tuple
11from PyQt6.QtWidgets import (
12 QDialog,
13 QVBoxLayout,
14 QHBoxLayout,
15 QLabel,
16 QSlider,
17 QSpinBox,
18 QDoubleSpinBox,
19 QPushButton,
20 QCheckBox,
21 QColorDialog,
22 QGroupBox,
23 QFormLayout,
24 QWidget,
25)
26from PyQt6.QtCore import Qt
27from PyQt6.QtGui import QColor
30class CornerRadiusDialog(QDialog):
31 """Dialog for setting corner radius"""
33 def __init__(self, parent, current_radius: float = 0.0):
34 super().__init__(parent)
35 self.setWindowTitle("Corner Radius")
36 self.setMinimumWidth(300)
38 layout = QVBoxLayout(self)
40 # Slider with label
41 slider_layout = QHBoxLayout()
42 slider_layout.addWidget(QLabel("Radius:"))
44 self.slider = QSlider(Qt.Orientation.Horizontal)
45 self.slider.setMinimum(0)
46 self.slider.setMaximum(50)
47 self.slider.setValue(int(current_radius))
48 self.slider.valueChanged.connect(self._on_slider_changed)
49 slider_layout.addWidget(self.slider)
51 self.value_label = QLabel(f"{int(current_radius)}%")
52 self.value_label.setMinimumWidth(40)
53 slider_layout.addWidget(self.value_label)
55 layout.addLayout(slider_layout)
57 # Preset buttons
58 preset_layout = QHBoxLayout()
59 for value, label in [(0, "None"), (5, "Slight"), (15, "Medium"), (25, "Large"), (50, "Circle")]:
60 btn = QPushButton(label)
61 btn.clicked.connect(lambda checked, v=value: self.slider.setValue(v))
62 preset_layout.addWidget(btn)
63 layout.addLayout(preset_layout)
65 # OK/Cancel buttons
66 button_layout = QHBoxLayout()
67 ok_btn = QPushButton("OK")
68 ok_btn.clicked.connect(self.accept)
69 cancel_btn = QPushButton("Cancel")
70 cancel_btn.clicked.connect(self.reject)
71 button_layout.addStretch()
72 button_layout.addWidget(ok_btn)
73 button_layout.addWidget(cancel_btn)
74 layout.addLayout(button_layout)
76 def _on_slider_changed(self, value):
77 self.value_label.setText(f"{value}%")
79 def get_value(self) -> float:
80 return float(self.slider.value())
83class ColorButton(QPushButton):
84 """Button that shows a color and opens color picker on click"""
86 def __init__(self, color: Tuple[int, int, int], parent=None):
87 super().__init__(parent)
88 self.setFixedSize(40, 25)
89 self._color = color
90 self._update_style()
91 self.clicked.connect(self._pick_color)
93 def _update_style(self):
94 r, g, b = self._color
95 self.setStyleSheet(f"background-color: rgb({r}, {g}, {b}); border: 1px solid #666;")
97 def _pick_color(self):
98 r, g, b = self._color
99 initial = QColor(r, g, b)
100 color = QColorDialog.getColor(initial, self, "Select Color")
101 if color.isValid():
102 self._color = (color.red(), color.green(), color.blue())
103 self._update_style()
105 def get_color(self) -> Tuple[int, int, int]:
106 return self._color
109class BorderDialog(QDialog):
110 """Dialog for configuring border"""
112 def __init__(
113 self,
114 parent,
115 current_width: float = 0.0,
116 current_color: Tuple[int, int, int] = (0, 0, 0),
117 ):
118 super().__init__(parent)
119 self.setWindowTitle("Border Settings")
120 self.setMinimumWidth(300)
122 layout = QVBoxLayout(self)
124 # Border width
125 width_layout = QHBoxLayout()
126 width_layout.addWidget(QLabel("Width (mm):"))
127 self.width_spin = QDoubleSpinBox()
128 self.width_spin.setRange(0, 20)
129 self.width_spin.setSingleStep(0.5)
130 self.width_spin.setValue(current_width)
131 self.width_spin.setDecimals(1)
132 width_layout.addWidget(self.width_spin)
133 layout.addLayout(width_layout)
135 # Border color
136 color_layout = QHBoxLayout()
137 color_layout.addWidget(QLabel("Color:"))
138 self.color_btn = ColorButton(current_color)
139 color_layout.addWidget(self.color_btn)
140 color_layout.addStretch()
141 layout.addLayout(color_layout)
143 # Preset buttons
144 preset_layout = QHBoxLayout()
145 presets = [
146 ("None", 0, (0, 0, 0)),
147 ("Thin Black", 0.5, (0, 0, 0)),
148 ("White", 2, (255, 255, 255)),
149 ("Gold", 1.5, (212, 175, 55)),
150 ]
151 for label, width, color in presets:
152 btn = QPushButton(label)
153 btn.clicked.connect(lambda checked, w=width, c=color: self._apply_preset(w, c))
154 preset_layout.addWidget(btn)
155 layout.addLayout(preset_layout)
157 # OK/Cancel buttons
158 button_layout = QHBoxLayout()
159 ok_btn = QPushButton("OK")
160 ok_btn.clicked.connect(self.accept)
161 cancel_btn = QPushButton("Cancel")
162 cancel_btn.clicked.connect(self.reject)
163 button_layout.addStretch()
164 button_layout.addWidget(ok_btn)
165 button_layout.addWidget(cancel_btn)
166 layout.addLayout(button_layout)
168 def _apply_preset(self, width, color):
169 self.width_spin.setValue(width)
170 self.color_btn._color = color
171 self.color_btn._update_style()
173 def get_values(self) -> Tuple[float, Tuple[int, int, int]]:
174 return self.width_spin.value(), self.color_btn.get_color()
177class ShadowDialog(QDialog):
178 """Dialog for configuring drop shadow"""
180 def __init__(
181 self,
182 parent,
183 enabled: bool = False,
184 offset: Tuple[float, float] = (2.0, 2.0),
185 blur: float = 3.0,
186 color: Tuple[int, int, int, int] = (0, 0, 0, 128),
187 ):
188 super().__init__(parent)
189 self.setWindowTitle("Shadow Settings")
190 self.setMinimumWidth(350)
192 layout = QVBoxLayout(self)
194 # Enable checkbox
195 self.enabled_check = QCheckBox("Enable Drop Shadow")
196 self.enabled_check.setChecked(enabled)
197 self.enabled_check.stateChanged.connect(self._update_controls)
198 layout.addWidget(self.enabled_check)
200 # Settings group
201 self.settings_group = QGroupBox("Shadow Settings")
202 form = QFormLayout(self.settings_group)
204 # Offset X
205 self.offset_x = QDoubleSpinBox()
206 self.offset_x.setRange(-20, 20)
207 self.offset_x.setSingleStep(0.5)
208 self.offset_x.setValue(offset[0])
209 self.offset_x.setDecimals(1)
210 form.addRow("Offset X (mm):", self.offset_x)
212 # Offset Y
213 self.offset_y = QDoubleSpinBox()
214 self.offset_y.setRange(-20, 20)
215 self.offset_y.setSingleStep(0.5)
216 self.offset_y.setValue(offset[1])
217 self.offset_y.setDecimals(1)
218 form.addRow("Offset Y (mm):", self.offset_y)
220 # Blur
221 self.blur_spin = QDoubleSpinBox()
222 self.blur_spin.setRange(0, 20)
223 self.blur_spin.setSingleStep(0.5)
224 self.blur_spin.setValue(blur)
225 self.blur_spin.setDecimals(1)
226 form.addRow("Blur (mm):", self.blur_spin)
228 # Color
229 color_widget = QWidget()
230 color_layout = QHBoxLayout(color_widget)
231 color_layout.setContentsMargins(0, 0, 0, 0)
232 self.color_btn = ColorButton(color[:3])
233 color_layout.addWidget(self.color_btn)
234 color_layout.addStretch()
235 form.addRow("Color:", color_widget)
237 # Opacity
238 self.opacity_slider = QSlider(Qt.Orientation.Horizontal)
239 self.opacity_slider.setRange(0, 255)
240 self.opacity_slider.setValue(color[3] if len(color) > 3 else 128)
241 opacity_layout = QHBoxLayout()
242 opacity_layout.addWidget(self.opacity_slider)
243 self.opacity_label = QLabel(f"{self.opacity_slider.value()}")
244 self.opacity_label.setMinimumWidth(30)
245 opacity_layout.addWidget(self.opacity_label)
246 self.opacity_slider.valueChanged.connect(lambda v: self.opacity_label.setText(str(v)))
247 form.addRow("Opacity:", opacity_layout)
249 layout.addWidget(self.settings_group)
251 # Preset buttons
252 preset_layout = QHBoxLayout()
253 presets = [
254 ("Subtle", True, (1.0, 1.0), 2.0, (0, 0, 0, 60)),
255 ("Normal", True, (2.0, 2.0), 3.0, (0, 0, 0, 100)),
256 ("Strong", True, (3.0, 3.0), 5.0, (0, 0, 0, 150)),
257 ]
258 for label, en, off, bl, col in presets:
259 btn = QPushButton(label)
260 btn.clicked.connect(lambda checked, e=en, o=off, b=bl, c=col: self._apply_preset(e, o, b, c))
261 preset_layout.addWidget(btn)
262 layout.addLayout(preset_layout)
264 # OK/Cancel buttons
265 button_layout = QHBoxLayout()
266 ok_btn = QPushButton("OK")
267 ok_btn.clicked.connect(self.accept)
268 cancel_btn = QPushButton("Cancel")
269 cancel_btn.clicked.connect(self.reject)
270 button_layout.addStretch()
271 button_layout.addWidget(ok_btn)
272 button_layout.addWidget(cancel_btn)
273 layout.addLayout(button_layout)
275 self._update_controls()
277 def _update_controls(self):
278 self.settings_group.setEnabled(self.enabled_check.isChecked())
280 def _apply_preset(self, enabled, offset, blur, color):
281 self.enabled_check.setChecked(enabled)
282 self.offset_x.setValue(offset[0])
283 self.offset_y.setValue(offset[1])
284 self.blur_spin.setValue(blur)
285 self.color_btn._color = color[:3]
286 self.color_btn._update_style()
287 self.opacity_slider.setValue(color[3] if len(color) > 3 else 128)
289 def get_values(self) -> Tuple[bool, Tuple[float, float], float, Tuple[int, int, int, int]]:
290 color_rgb = self.color_btn.get_color()
291 color_rgba = color_rgb + (self.opacity_slider.value(),)
292 return (
293 self.enabled_check.isChecked(),
294 (self.offset_x.value(), self.offset_y.value()),
295 self.blur_spin.value(),
296 color_rgba,
297 )