Krita Source Code Documentation
Loading...
Searching...
No Matches
comics_template_dialog.py
Go to the documentation of this file.
1"""
2SPDX-FileCopyrightText: 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3
4This file is part of the Comics Project Management Tools(CPMT).
5
6SPDX-License-Identifier: GPL-3.0-or-later
7"""
8
9"""
10Template dialog
11"""
12import os
13import shutil
14try:
15 from PyQt6.QtWidgets import QDialog, QComboBox, QDialogButtonBox, QVBoxLayout, QFormLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget, QColorDialog, QProgressDialog, QApplication
16 from PyQt6.QtCore import QLocale, Qt, QByteArray, QRectF
17 from PyQt6.QtGui import QImage, QPainter, QPixmap, QColor
18except:
19 from PyQt5.QtWidgets import QDialog, QComboBox, QDialogButtonBox, QVBoxLayout, QFormLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget, QColorDialog, QProgressDialog, QApplication
20 from PyQt5.QtCore import QLocale, Qt, QByteArray, QRectF
21 from PyQt5.QtGui import QImage, QPainter, QPixmap, QColor
22from krita import FileDialog, InfoObject
23from builtins import i18n, Application
24"""
25Quick and dirty QComboBox subclassing that handles unitconversion for us.
26"""
27
28
29class simpleUnitBox(QComboBox):
30 pixels = i18n("Pixels")
31 inches = i18n("Inches")
32 centimeter = i18n("Centimeter")
33 millimeter = i18n("millimeter")
34
35 def __init__(self):
36 super(simpleUnitBox, self).__init__()
37 self.addItem(self.pixels)
38 self.addItem(self.inches)
39 self.addItem(self.centimeter)
40 self.addItem(self.millimeter)
41
42 if QLocale().system().measurementSystem() is QLocale.MeasurementSystem.MetricSystem:
43 self.setCurrentIndex(2) # set to centimeter if metric system.
44 else:
45 self.setCurrentIndex(1)
46
47 def pixelsForUnit(self, unit, DPI):
48 if (self.currentText() == self.pixels):
49 return unit
50 elif (self.currentText() == self.inches):
51 return self.inchesToPixels(unit, DPI)
52 elif (self.currentText() == self.centimeter):
53 return self.centimeterToPixels(unit, DPI)
54 elif (self.currentText() == self.millimeter):
55 return self.millimeterToPixels(unit, DPI)
56
57 def inchesToPixels(self, inches, DPI):
58 return DPI * inches
59
60 def centimeterToInches(self, cm):
61 return cm / 2.54
62
63 def centimeterToPixels(self, cm, DPI):
64 return self.inchesToPixels(self.centimeterToInches(cm), DPI)
65
67 return mm / 10
68
69 def millimeterToPixels(self, mm, DPI):
70 return self.inchesToPixels(self.centimeterToInches(self.millimeterToCentimeter(mm)), DPI)
71
72
74 templateDirectory = str()
75 templates = QComboBox()
76 buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
77
78 def __init__(self, templateDirectory):
79 super().__init__()
80 self.templateDirectory = templateDirectory
81 self.setWindowTitle(i18n("Add new Template"))
82 self.setLayout(QVBoxLayout())
83
84 self.templates = QComboBox()
85 self.templates.setEnabled(False)
86
87 self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
88 self.buttons.accepted.connect(self.accept)
89 self.buttons.rejected.connect(self.reject)
90 self.buttons.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
91 mainWidget = QWidget()
92 self.layout().addWidget(mainWidget)
93 self.layout().addWidget(self.buttons)
94 mainWidget.setLayout(QVBoxLayout())
95
96 btn_create = QPushButton(i18n("Create Template"))
97 btn_create.clicked.connect(self.slot_create_templateslot_create_template)
98 btn_import = QPushButton(i18n("Import Templates"))
99 btn_import.clicked.connect(self.slot_import_templateslot_import_template)
100 mainWidget.layout().addWidget(self.templates)
101 mainWidget.layout().addWidget(btn_create)
102 mainWidget.layout().addWidget(btn_import)
103 self.fill_templates()
104
105 def fill_templates(self):
106 self.templates.clear()
107 for entry in os.scandir(self.templateDirectory):
108 if entry.name.endswith('.kra') and entry.is_file():
109 name = os.path.relpath(entry.path, self.templateDirectory)
110 self.templates.addItem(name)
111 if self.templates.model().rowCount() > 0:
112 self.templates.setEnabled(True)
113 self.buttons.button(QDialogButtonBox.StandardButton.Ok).setEnabled(True)
114
117
118 if create.exec() == QDialog.DialogCode.Accepted:
119 if (create.prepare_krita_file()):
120 self.fill_templates()
121
123 filenames = FileDialog.getOpenFileNames(caption=i18n("Which files should be added to the template folder?"), directory=self.templateDirectory, filter=str(i18n("Krita files") + "(*.kra)"))
124 if not filenames: return
125 for file in filenames:
126 shutil.copy2(file, self.templateDirectory)
127 self.fill_templates()
128
129 def url(self):
130 return os.path.join(self.templateDirectory, self.templates.currentText())
131
132
134 urlSavedTemplate = str()
135 templateDirectory = str()
136 currentColor = QColor(Qt.GlobalColor.white)
137
138 def __init__(self, templateDirectory):
139 super().__init__()
140 self.templateDirectory = templateDirectory
141 self.setWindowTitle(i18n("Create new Template"))
142 self.setLayout(QVBoxLayout())
143 buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
144 buttons.accepted.connect(self.accept)
145 buttons.rejected.connect(self.reject)
146 mainWidget = QWidget()
147 explanation = QLabel(i18n("This allows you to make a template document with guides.\nThe width and height are the size of the live-area, the safe area is the live area minus the margins, and the full image is the live area plus the bleeds."))
148 explanation.setWordWrap(True)
149 self.layout().addWidget(explanation)
150 self.layout().addWidget(mainWidget)
151 self.layout().addWidget(buttons)
152 mainWidget.setLayout(QHBoxLayout())
153 elements = QWidget()
154 elements.setLayout(QVBoxLayout())
155 previewLayout = QWidget()
156 previewLayout.setLayout(QVBoxLayout())
157 mainWidget.layout().addWidget(elements)
158 mainWidget.layout().addWidget(previewLayout)
159 self.imagePreview = QLabel()
160 self.imagePreview.setMinimumSize(256, 256)
161 previewLayout.layout().addWidget(self.imagePreview)
162
163 self.templateName = QLineEdit()
164 self.templateName.setPlaceholderText("...")
165 elements.layout().addWidget(self.templateName)
166
167 templateBGColor = QPushButton(i18n("Select background color"))
168 templateBGColor.clicked.connect(self.slot_call_color_dialogslot_call_color_dialog)
169 previewLayout.layout().addWidget(templateBGColor)
170
171 self.DPI = QSpinBox()
172 self.DPI.setMaximum(1200)
173 self.DPI.setValue(300)
174 self.spn_width = QDoubleSpinBox()
175 self.spn_width.setMaximum(10000)
176 self.spn_height = QDoubleSpinBox()
177 self.spn_height.setMaximum(10000)
180
181 widgetSize = QWidget()
182 sizeForm = QFormLayout()
183 sizeForm.addRow(i18n("DPI:"), self.DPI)
184 widthLayout = QHBoxLayout()
185 widthLayout.addWidget(self.spn_width)
186 widthLayout.addWidget(self.widthUnit)
187 sizeForm.addRow(i18n("Width:"), widthLayout)
188 heightLayout = QHBoxLayout()
189 heightLayout.addWidget(self.spn_height)
190 heightLayout.addWidget(self.heightUnit)
191 sizeForm.addRow(i18n("Height:"), heightLayout)
192 widgetSize.setLayout(sizeForm)
193 elements.layout().addWidget(widgetSize)
194
195 marginAndBleed = QTabWidget()
196 elements.layout().addWidget(marginAndBleed)
197
198 margins = QWidget()
199 marginForm = QGridLayout()
200 margins.setLayout(marginForm)
201 self.marginLeft = QDoubleSpinBox()
202 self.marginLeft.setMaximum(1000)
204 self.marginRight = QDoubleSpinBox()
205 self.marginRight.setMaximum(1000)
207 self.marginTop = QDoubleSpinBox()
208 self.marginTop.setMaximum(1000)
210 self.marginBottom = QDoubleSpinBox()
211 self.marginBottom.setMaximum(1000)
213 marginForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignmentFlag.AlignRight)
214 marginForm.addWidget(self.marginLeft, 0, 1)
215 marginForm.addWidget(self.marginLeftUnit, 0, 2)
216 marginForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignmentFlag.AlignRight)
217 marginForm.addWidget(self.marginTop, 1, 1)
218 marginForm.addWidget(self.marginTopUnit, 1, 2)
219 marginForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignmentFlag.AlignRight)
220 marginForm.addWidget(self.marginRight, 2, 1)
221 marginForm.addWidget(self.marginRightUnit, 2, 2)
222 marginForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignmentFlag.AlignRight)
223 marginForm.addWidget(self.marginBottom, 3, 1)
224 marginForm.addWidget(self.marginBottomUnit, 3, 2)
225 marginAndBleed.addTab(margins, i18n("Margins"))
226
227 bleeds = QWidget()
228 bleedsForm = QGridLayout()
229 bleeds.setLayout(bleedsForm)
230 self.bleedLeft = QDoubleSpinBox()
231 self.bleedLeft.setMaximum(1000)
233 self.bleedRight = QDoubleSpinBox()
234 self.bleedRight.setMaximum(1000)
236 self.bleedTop = QDoubleSpinBox()
237 self.bleedTop.setMaximum(1000)
239 self.bleedBottom = QDoubleSpinBox()
240 self.bleedBottom.setMaximum(1000)
242 bleedsForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignmentFlag.AlignRight)
243 bleedsForm.addWidget(self.bleedLeft, 0, 1)
244 bleedsForm.addWidget(self.bleedLeftUnit, 0, 2)
245 bleedsForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignmentFlag.AlignRight)
246 bleedsForm.addWidget(self.bleedTop, 1, 1)
247 bleedsForm.addWidget(self.bleedTopUnit, 1, 2)
248 bleedsForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignmentFlag.AlignRight)
249 bleedsForm.addWidget(self.bleedRight, 2, 1)
250 bleedsForm.addWidget(self.bleedRightUnit, 2, 2)
251 bleedsForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignmentFlag.AlignRight)
252 bleedsForm.addWidget(self.bleedBottom, 3, 1)
253 bleedsForm.addWidget(self.bleedBottomUnit, 3, 2)
254
255 marginAndBleed.addTab(bleeds, i18n("Bleeds"))
256
257 if QLocale().system().measurementSystem() is QLocale.MeasurementSystem.MetricSystem:
258 self.setDefaults("European")
259 else:
260 self.setDefaults("American")
261
262 self.spn_width.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
263 self.widthUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
264 self.spn_height.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
265 self.heightUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
266 self.marginLeft.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
267 self.marginLeftUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
268 self.marginRight.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
269 self.marginRightUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
270 self.marginTop.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
271 self.marginTopUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
272 self.marginBottom.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
273 self.marginBottomUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
274 self.bleedLeft.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
275 self.bleedLeftUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
276 self.bleedRight.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
277 self.bleedRightUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
278 self.bleedTop.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
279 self.bleedTopUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
280 self.bleedBottom.valueChanged.connect(self.updateImagePreviewupdateImagePreview)
281 self.bleedBottomUnit.currentIndexChanged.connect(self.updateImagePreviewupdateImagePreview)
282
284
286 dialog = QColorDialog(self)
287 dialog.setCurrentColor(self.currentColor)
288
289 if dialog.exec() == QDialog.DialogCode.Accepted:
290 self.currentColor = dialog.currentColor()
292
294 wBase = max(self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()), 1)
295 bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value())
296 bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value())
297 mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value())
298 mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value())
299
300 hBase = max(self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()), 1)
301 bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value())
302 bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value())
303 mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value())
304 mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value())
305
306 template = Application.createDocument(int(wBase + bL + bR), int(hBase + bT + bB), self.templateName.text(), "RGBA", "U8", "sRGB built-in", self.DPI.value())
307
308 backgroundName = i18n("Background")
309 if len(template.topLevelNodes()) > 0:
310 backgroundNode = template.topLevelNodes()[0]
311 backgroundNode.setName(backgroundName)
312 else:
313 backgroundNode = template.createNode(backgroundName, "paintlayer")
314 template.rootNode().addChildNode(backgroundNode, None)
315
316 pixelByteArray = QByteArray()
317 if self.currentColor == Qt.GlobalColor.white:
318 pixelByteArray = backgroundNode.pixelData(0, 0, template.width(), template.height())
319 pixelByteArray.fill(int(255).to_bytes(1, byteorder='little'))
320 else:
321 red = int(self.currentColor.redF()*255).to_bytes(1, byteorder='little')
322 green = int(self.currentColor.greenF()*255).to_bytes(1, byteorder='little')
323 blue = int(self.currentColor.blueF()*255).to_bytes(1, byteorder='little')
324 alpha = int(self.currentColor.alphaF()*255).to_bytes(1, byteorder='little')
325
326 progress = QProgressDialog(i18n("Creating template"), str(), 0, template.width()*template.height())
327 progress.setCancelButton(None)
328 progress.show()
329 QApplication.instance().processEvents()
330 for byteNumber in range(template.width()*template.height()):
331 pixelByteArray.append( blue)
332 pixelByteArray.append(green)
333 pixelByteArray.append( red)
334 pixelByteArray.append(alpha)
335 progress.setValue(byteNumber)
336 backgroundNode.setPixelData(pixelByteArray, 0, 0, template.width(), template.height())
337 backgroundNode.setOpacity(255)
338 backgroundNode.setLocked(True)
339
340 sketchNode = template.createNode(i18n("Sketch"), "paintlayer")
341 template.rootNode().addChildNode(sketchNode, backgroundNode)
342
343 verticalGuides = []
344 verticalGuides.append(bL)
345 verticalGuides.append(bL + mL)
346 verticalGuides.append((bL + wBase) - mR)
347 verticalGuides.append(bL + wBase)
348
349 horizontalGuides = []
350 horizontalGuides.append(bT)
351 horizontalGuides.append(bT + mT)
352 horizontalGuides.append((bT + hBase) - mB)
353 horizontalGuides.append(bT + hBase)
354
355 template.setHorizontalGuides(horizontalGuides)
356 template.setVerticalGuides(verticalGuides)
357 template.setGuidesVisible(True)
358 template.setGuidesLocked(True)
359 template.refreshProjection()
360
361 self.urlSavedTemplate = os.path.join(self.templateDirectory, self.templateName.text() + ".kra")
362 success = template.exportImage(self.urlSavedTemplate, InfoObject())
363 print("CPMT: Template", self.templateName.text(), "made and saved.")
364 template.waitForDone()
365 template.close()
366
367 return success
368
370 maxSize = 256
371
372 wBase = max(self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()), 1)
373 bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value())
374 bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value())
375 mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value())
376 mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value())
377
378 hBase = max(self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()), 1)
379 bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value())
380 bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value())
381 mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value())
382 mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value())
383
384 scaleRatio = maxSize / (hBase + bT + bB)
385 if wBase > hBase:
386 scaleRatio = maxSize / (wBase + bR + bL)
387
388 width = (wBase + bL + bR) * scaleRatio
389 height = (hBase + bT + bB) * scaleRatio
390 topLeft = [max((maxSize - width) / 2, 0), max((maxSize - height) / 2, 0)]
391
392 image = QImage(maxSize, maxSize, QImage.Format.Format_ARGB32)
393 image.fill(Qt.GlobalColor.transparent)
394
395 p = QPainter(image)
396
397 p.setBrush(self.currentColor)
398
399 CanvasSize = QRectF(topLeft[0], topLeft[1], width, height)
400 p.drawRect(CanvasSize.toRect())
401
402 # Draw bleeds.
403 PageSize = CanvasSize
404 PageSize.setWidth(width - (bR * scaleRatio))
405 PageSize.setHeight(height - (bB * scaleRatio))
406 PageSize.setX(PageSize.x() + (bL * scaleRatio))
407 PageSize.setY(PageSize.y() + (bT * scaleRatio))
408
409 p.setPen(Qt.GlobalColor.blue)
410 p.setBrush(Qt.GlobalColor.transparent)
411 p.drawRect(PageSize.toRect())
412
413 # Draw liveArea
414 LiveArea = PageSize
415 LiveArea.setWidth(LiveArea.width() - (mR * scaleRatio))
416 LiveArea.setHeight(LiveArea.height() - (mB * scaleRatio))
417 LiveArea.setX(LiveArea.x() + (mL * scaleRatio))
418 LiveArea.setY(LiveArea.y() + (mT * scaleRatio))
419
420 p.setPen(Qt.GlobalColor.blue)
421 p.drawRect(LiveArea.toRect())
422
423 p.end()
424
425 self.imagePreview.setPixmap(QPixmap.fromImage(image))
426
427 def setDefaults(self, type):
428
429 if type == "American":
430 # American 11x17 inch
431 self.spn_width.setValue(11)
432 self.widthUnit.setCurrentIndex(1)
433 self.spn_height.setValue(17)
434 self.heightUnit.setCurrentIndex(1)
435 self.bleedBottom.setValue(1)
436 self.bleedBottomUnit.setCurrentIndex(1)
437 self.bleedTop.setValue(1)
438 self.bleedTopUnit.setCurrentIndex(1)
439 self.bleedLeft.setValue(0.5)
440 self.bleedLeftUnit.setCurrentIndex(1)
441 self.bleedRight.setValue(0.5)
442 self.bleedRightUnit.setCurrentIndex(1)
443 self.marginBottom.setValue(0.745)
444 self.marginBottomUnit.setCurrentIndex(1)
445 self.marginTop.setValue(0.745)
446 self.marginTopUnit.setCurrentIndex(1)
447 self.marginRight.setValue(0.5)
448 self.marginRightUnit.setCurrentIndex(1)
449 self.marginLeft.setValue(0.5)
450 self.marginLeftUnit.setCurrentIndex(1)
451 if type == "European":
452 # European A4
453 self.spn_width.setValue(21)
454 self.widthUnit.setCurrentIndex(2)
455 self.spn_height.setValue(29.7)
456 self.heightUnit.setCurrentIndex(2)
457 self.bleedBottom.setValue(5)
458 self.bleedBottomUnit.setCurrentIndex(3)
459 self.bleedTop.setValue(5)
460 self.bleedTopUnit.setCurrentIndex(3)
461 self.bleedLeft.setValue(5)
462 self.bleedLeftUnit.setCurrentIndex(3)
463 self.bleedRight.setValue(5)
464 self.bleedRightUnit.setCurrentIndex(3)
465 self.marginBottom.setValue(1.5)
466 self.marginBottomUnit.setCurrentIndex(2)
467 self.marginTop.setValue(1.5)
468 self.marginTopUnit.setCurrentIndex(2)
469 self.marginRight.setValue(1)
470 self.marginRightUnit.setCurrentIndex(2)
471 self.marginLeft.setValue(1)
472 self.marginLeftUnit.setCurrentIndex(2)
473
474 def url(self):
475 return self.urlSavedTemplate
float value(const T *src, size_t ch)
static QStringList getOpenFileNames(QWidget *parent=nullptr, const QString &caption=QString(), const QString &directory=QString(), const QString &filter=QString(), const QString &selectedFilter=QString(), const QString &dialogName=QString())
Create and show a file dialog and return the name of multiple existing files selected by the user.
QString button(const QWheelEvent &ev)