1 """

2 Style dialogs for pyPhotoAlbum

3

4 Dialogs for configuring image styling options:

5 - Corner radius

6 - Border (width and color)

7 - Drop shadow

8 """

9

10 from typing import Tuple

11 from 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 )

26 from PyQt6.QtCore import Qt

27 from PyQt6.QtGui import QColor

28

29

30 class CornerRadiusDialog(QDialog):

31 """Dialog for setting corner radius"""

32

33 def __init__(self, parent, current_radius: float = 0.0):

34 super().__init__(parent)

35 self.setWindowTitle("Corner Radius")

36 self.setMinimumWidth(300)

37

38 layout = QVBoxLayout(self)

39

40 # Slider with label

41 slider_layout = QHBoxLayout()

42 slider_layout.addWidget(QLabel("Radius:"))

43

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)

50

51 self.value_label = QLabel(f"{int(current_radius)}%")

52 self.value_label.setMinimumWidth(40)

53 slider_layout.addWidget(self.value_label)

54

55 layout.addLayout(slider_layout)

56

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)

64

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)

75

76 def _on_slider_changed(self, value):

77 self.value_label.setText(f"{value}%")

78

79 def get_value(self) -> float:

80 return float(self.slider.value())

81

82

83 class ColorButton(QPushButton):

84 """Button that shows a color and opens color picker on click"""

85

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)

92

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;")

96

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()

104

105 def get_color(self) -> Tuple[int, int, int]:

106 return self._color

107

108

109 class BorderDialog(QDialog):

110 """Dialog for configuring border"""

111

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)

121

122 layout = QVBoxLayout(self)

123

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)

134

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)

142

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)

156

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)

167

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()

172

173 def get_values(self) -> Tuple[float, Tuple[int, int, int]]:

174 return self.width_spin.value(), self.color_btn.get_color()

175

176

177 class ShadowDialog(QDialog):

178 """Dialog for configuring drop shadow"""

179

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)

191

192 layout = QVBoxLayout(self)

193

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)

199

200 # Settings group

201 self.settings_group = QGroupBox("Shadow Settings")

202 form = QFormLayout(self.settings_group)

203

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)

211

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)

219

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)

227

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)

236

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)

248

249 layout.addWidget(self.settings_group)

250

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)

263

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)

274

275 self._update_controls()

276

277 def _update_controls(self):

278 self.settings_group.setEnabled(self.enabled_check.isChecked())

279

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)

288

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 )