diff --git a/plugins/python/assignprofiledialog/assignprofiledialog.py b/plugins/python/assignprofiledialog/assignprofiledialog.py index 36e57198f5..443a4dde59 100644 --- a/plugins/python/assignprofiledialog/assignprofiledialog.py +++ b/plugins/python/assignprofiledialog/assignprofiledialog.py @@ -1,60 +1,60 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QDialogButtonBox, QDialog, QMessageBox, QComboBox, QVBoxLayout) from krita import Extension class AssignProfileDialog(Extension): def __init__(self, parent): super(AssignProfileDialog, self).__init__(parent) def assignProfile(self): doc = Application.activeDocument() if doc is None: - QMessageBox.information(Application.activeWindow().qwindow(), "Assign Profile", "There is no active document.") + QMessageBox.information(Application.activeWindow().qwindow(), i18n("Assign Profile"), i18n("There is no active document.")) return self.dialog = QDialog(Application.activeWindow().qwindow()) self.cmbProfile = QComboBox(self.dialog) for profile in sorted(Application.profiles(doc.colorModel(), doc.colorDepth())): self.cmbProfile.addItem(profile) vbox = QVBoxLayout(self.dialog) vbox.addWidget(self.cmbProfile) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.dialog.reject) vbox.addWidget(self.buttonBox) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() def accept(self): doc = Application.activeDocument() doc.setColorProfile(self.cmbProfile.currentText()) def setup(self): pass def createActions(self, window): - action = window.createAction("assing_profile_to_image", "Assign Profile to Image") + action = window.createAction("assing_profile_to_image", i18n("Assign Profile to Image")) action.triggered.connect(self.assignProfile) Scripter.addExtension(AssignProfileDialog(Application)) diff --git a/plugins/python/colorspace/colorspace.py b/plugins/python/colorspace/colorspace.py index 3d4c4bd8ab..fc5a4ab710 100644 --- a/plugins/python/colorspace/colorspace.py +++ b/plugins/python/colorspace/colorspace.py @@ -1,34 +1,34 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import krita from . import uicolorspace class ColorSpaceExtension(krita.Extension): def __init__(self, parent): super(ColorSpaceExtension, self).__init__(parent) def setup(self): pass def createActions(self, window): - action = window.createAction("color_space", "Color Space") - action.setToolTip("Plugin to change color space to selected documents") + action = window.createAction("color_space", i18n("Color Space")) + action.setToolTip(i18n("Plugin to change color space of selected documents.")) action.triggered.connect(self.initialize) def initialize(self): self.uicolorspace = uicolorspace.UIColorSpace() self.uicolorspace.initialize() Scripter.addExtension(ColorSpaceExtension(krita.Krita.instance())) diff --git a/plugins/python/colorspace/uicolorspace.py b/plugins/python/colorspace/uicolorspace.py index f7c9a8378e..d757a190f3 100644 --- a/plugins/python/colorspace/uicolorspace.py +++ b/plugins/python/colorspace/uicolorspace.py @@ -1,129 +1,129 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from . import colorspacedialog from .components import colormodelcombobox, colordepthcombobox, colorprofilecombobox from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QFormLayout, QListWidget, QListWidgetItem, QAbstractItemView, QComboBox, QDialogButtonBox, QVBoxLayout, QFrame, QMessageBox, QPushButton, QHBoxLayout, QAbstractScrollArea) from PyQt5.QtGui import QIcon import krita from . import resources_rc class UIColorSpace(object): def __init__(self): self.mainDialog = colorspacedialog.ColorSpaceDialog() self.mainLayout = QVBoxLayout(self.mainDialog) self.formLayout = QFormLayout() self.documentLayout = QVBoxLayout() - self.refreshButton = QPushButton(QIcon(':/icons/refresh.svg'), "Refresh") + self.refreshButton = QPushButton(QIcon(':/icons/refresh.svg'), i18n("Refresh")) self.widgetDocuments = QListWidget() self.colorModelComboBox = colormodelcombobox.ColorModelComboBox(self) self.colorDepthComboBox = colordepthcombobox.ColorDepthComboBox(self) self.colorProfileComboBox = colorprofilecombobox.ColorProfileComboBox(self) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.kritaInstance = krita.Krita.instance() self.documentsList = [] self.colorModelsList = [] self.colorDepthsList = [] self.colorProfilesList = [] self.refreshButton.clicked.connect(self.refreshButtonClicked) self.buttonBox.accepted.connect(self.confirmButton) self.buttonBox.rejected.connect(self.mainDialog.close) self.mainDialog.setWindowModality(Qt.NonModal) self.widgetDocuments.setSelectionMode(QAbstractItemView.MultiSelection) self.widgetDocuments.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) def initialize(self): self.loadDocuments() self.loadColorModels() self.loadColorDepths() self.loadColorProfiles() self.documentLayout.addWidget(self.widgetDocuments) self.documentLayout.addWidget(self.refreshButton) - self.formLayout.addRow('Documents', self.documentLayout) - self.formLayout.addRow('Color Model', self.colorModelComboBox) - self.formLayout.addRow('Color Depth', self.colorDepthComboBox) - self.formLayout.addRow('Color Profile', self.colorProfileComboBox) + self.formLayout.addRow(i18n("Documents:"), self.documentLayout) + self.formLayout.addRow(i18n("Color model:"), self.colorModelComboBox) + self.formLayout.addRow(i18n("Color depth:"), self.colorDepthComboBox) + self.formLayout.addRow(i18n("Color profile:"), self.colorProfileComboBox) self.line = QFrame() self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.mainLayout.addLayout(self.formLayout) self.mainLayout.addWidget(self.line) self.mainLayout.addWidget(self.buttonBox) self.mainDialog.resize(500, 300) - self.mainDialog.setWindowTitle("Color Space") + self.mainDialog.setWindowTitle(i18n("Color Space")) self.mainDialog.setSizeGripEnabled(True) self.mainDialog.show() self.mainDialog.activateWindow() def loadColorModels(self): self.colorModelsList = sorted(self.kritaInstance.colorModels()) self.colorModelComboBox.addItems(self.colorModelsList) def loadColorDepths(self): self.colorDepthComboBox.clear() colorModel = self.colorModelComboBox.currentText() self.colorDepthsList = sorted(self.kritaInstance.colorDepths(colorModel)) self.colorDepthComboBox.addItems(self.colorDepthsList) def loadColorProfiles(self): self.colorProfileComboBox.clear() colorModel = self.colorModelComboBox.currentText() colorDepth = self.colorDepthComboBox.currentText() self.colorProfilesList = sorted(self.kritaInstance.profiles(colorModel, colorDepth)) self.colorProfileComboBox.addItems(self.colorProfilesList) def loadDocuments(self): self.widgetDocuments.clear() self.documentsList = [document for document in self.kritaInstance.documents() if document.fileName()] for document in self.documentsList: self.widgetDocuments.addItem(document.fileName()) def refreshButtonClicked(self): self.loadDocuments() def confirmButton(self): selectedPaths = [item.text() for item in self.widgetDocuments.selectedItems()] selectedDocuments = [document for document in self.documentsList for path in selectedPaths if path == document.fileName()] self.msgBox = QMessageBox(self.mainDialog) if selectedDocuments: self.convertColorSpace(selectedDocuments) - self.msgBox.setText("The selected documents has been converted.") + self.msgBox.setText(i18n("The selected documents has been converted.")) else: - self.msgBox.setText("Select at least one document.") + self.msgBox.setText(i18n("Select at least one document.")) self.msgBox.exec_() def convertColorSpace(self, documents): for document in documents: document.setColorSpace(self.colorModelComboBox.currentText(), self.colorDepthComboBox.currentText(), self.colorProfileComboBox.currentText()) diff --git a/plugins/python/comics_project_management_tools/comics_export_dialog.py b/plugins/python/comics_project_management_tools/comics_export_dialog.py index 2d69c7680b..4e77a45aed 100644 --- a/plugins/python/comics_project_management_tools/comics_export_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_export_dialog.py @@ -1,767 +1,767 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ A dialog for editing the exporter settings. """ from enum import IntEnum from PyQt5.QtGui import QStandardItem, QStandardItemModel, QColor, QFont, QIcon, QPixmap from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGroupBox, QFormLayout, QCheckBox, QComboBox, QSpinBox, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QPushButton, QLineEdit, QLabel, QListView, QTableView, QFontComboBox, QSpacerItem, QColorDialog, QStyledItemDelegate from PyQt5.QtCore import Qt, QUuid from krita import * """ A generic widget to make selecting size easier. It works by initialising with a config name(like "scale"), and then optionally setting the config with a dictionary. Then, afterwards, you get the config with a dictionary, with the config name being the entry the values are under. """ class comic_export_resize_widget(QGroupBox): configName = "" def __init__(self, configName, batch=False, fileType=True): super().__init__() self.configName = configName - self.setTitle("Adjust Workingfile") + self.setTitle(i18n("Adjust Working File")) formLayout = QFormLayout() self.setLayout(formLayout) - self.crop = QCheckBox(i18n("Crop files before resize.")) + self.crop = QCheckBox(i18n("Crop files before resize")) self.cmbFile = QComboBox() self.cmbFile.addItems(["png", "jpg", "webp"]) self.resizeMethod = QComboBox() self.resizeMethod.addItems([i18n("Percentage"), i18n("DPI"), i18n("Maximum Width"), i18n("Maximum Height")]) self.resizeMethod.currentIndexChanged.connect(self.slot_set_enabled) self.spn_DPI = QSpinBox() self.spn_DPI.setMaximum(1200) self.spn_DPI.setSuffix(i18n(" DPI")) self.spn_DPI.setValue(72) self.spn_PER = QSpinBox() if batch is True: self.spn_PER.setMaximum(1000) else: self.spn_PER.setMaximum(100) self.spn_PER.setSuffix(" %") self.spn_PER.setValue(100) self.spn_width = QSpinBox() self.spn_width.setMaximum(99999) self.spn_width.setSuffix(" px") self.spn_width.setValue(800) self.spn_height = QSpinBox() self.spn_height.setMaximum(99999) self.spn_height.setSuffix(" px") self.spn_height.setValue(800) if batch is False: formLayout.addRow("", self.crop) if fileType is True and configName != "TIFF": formLayout.addRow(i18n("File Type"), self.cmbFile) formLayout.addRow(i18n("Method:"), self.resizeMethod) formLayout.addRow(i18n("DPI:"), self.spn_DPI) formLayout.addRow(i18n("Percentage:"), self.spn_PER) formLayout.addRow(i18n("Width:"), self.spn_width) formLayout.addRow(i18n("Height:"), self.spn_height) self.slot_set_enabled() def slot_set_enabled(self): method = self.resizeMethod.currentIndex() self.spn_DPI.setEnabled(False) self.spn_PER.setEnabled(False) self.spn_width.setEnabled(False) self.spn_height.setEnabled(False) if method is 0: self.spn_PER.setEnabled(True) if method is 1: self.spn_DPI.setEnabled(True) if method is 2: self.spn_width.setEnabled(True) if method is 3: self.spn_height.setEnabled(True) def set_config(self, config): if self.configName in config.keys(): mConfig = config[self.configName] if "Method" in mConfig.keys(): self.resizeMethod.setCurrentIndex(mConfig["Method"]) if "FileType" in mConfig.keys(): self.cmbFile.setCurrentText(mConfig["FileType"]) if "Crop" in mConfig.keys(): self.crop.setChecked(mConfig["Crop"]) if "DPI" in mConfig.keys(): self.spn_DPI.setValue(mConfig["DPI"]) if "Percentage" in mConfig.keys(): self.spn_PER.setValue(mConfig["Percentage"]) if "Width" in mConfig.keys(): self.spn_width.setValue(mConfig["Width"]) if "Height" in mConfig.keys(): self.spn_height.setValue(mConfig["Height"]) self.slot_set_enabled() def get_config(self, config): mConfig = {} mConfig["Method"] = self.resizeMethod.currentIndex() if self.configName == "TIFF": mConfig["FileType"] = "tiff" else: mConfig["FileType"] = self.cmbFile.currentText() mConfig["Crop"] = self.crop.isChecked() mConfig["DPI"] = self.spn_DPI.value() mConfig["Percentage"] = self.spn_PER.value() mConfig["Width"] = self.spn_width.value() mConfig["Height"] = self.spn_height.value() config[self.configName] = mConfig return config """ Quick combobox for selecting the color label. """ class labelSelector(QComboBox): def __init__(self): super(labelSelector, self).__init__() lisOfColors = [] lisOfColors.append(Qt.transparent) lisOfColors.append(QColor(91, 173, 220)) lisOfColors.append(QColor(151, 202, 63)) lisOfColors.append(QColor(247, 229, 61)) lisOfColors.append(QColor(255, 170, 63)) lisOfColors.append(QColor(177, 102, 63)) lisOfColors.append(QColor(238, 50, 51)) lisOfColors.append(QColor(191, 106, 209)) lisOfColors.append(QColor(118, 119, 114)) self.itemModel = QStandardItemModel() for color in lisOfColors: item = QStandardItem() item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setCheckState(Qt.Unchecked) item.setText(" ") item.setData(color, Qt.BackgroundColorRole) self.itemModel.appendRow(item) self.setModel(self.itemModel) def getLabels(self): listOfIndexes = [] for i in range(self.itemModel.rowCount()): index = self.itemModel.index(i, 0) item = self.itemModel.itemFromIndex(index) if item.checkState(): listOfIndexes.append(i) return listOfIndexes def setLabels(self, listOfIndexes): for i in listOfIndexes: index = self.itemModel.index(i, 0) item = self.itemModel.itemFromIndex(index) item.setCheckState(True) """ Little Enum to keep track of where in the item we add styles. """ class styleEnum(IntEnum): FONT = Qt.UserRole + 1 FONTLIST = Qt.UserRole + 2 FONTGENERIC = Qt.UserRole + 3 BOLD = Qt.UserRole + 4 ITALIC = Qt.UserRole + 5 """ A simple delegate to allows editing fonts with a QFontComboBox """ class font_list_delegate(QStyledItemDelegate): def __init__(self, parent=None): super(QStyledItemDelegate, self).__init__(parent) def createEditor(self, parent, option, index): editor = QFontComboBox(parent) return editor """ The comic export settings dialog will allow configuring the export. This config consists of... * Crop settings. for removing bleeds. * Selecting layer labels to remove. * Choosing which formats to export to. * Choosing how to resize these * Whether to crop. * Which file type to use. And for ACBF, it gives the ability to edit acbf document info. """ class comic_export_setting_dialog(QDialog): acbfStylesList = ["speech", "commentary", "formal", "letter", "code", "heading", "audio", "thought", "sign", "sound", "emphasis", "strong"] def __init__(self): super().__init__() self.setLayout(QVBoxLayout()) - self.setWindowTitle(i18n("Export settings")) + self.setWindowTitle(i18n("Export Settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) # Set basic crop settings # Set which layers to remove before export. mainExportSettings = QWidget() mainExportSettings.setLayout(QVBoxLayout()) - groupExportCrop = QGroupBox(i18n("Crop settings")) + groupExportCrop = QGroupBox(i18n("Crop Settings")) formCrop = QFormLayout() groupExportCrop.setLayout(formCrop) self.chk_toOutmostGuides = QCheckBox(i18n("Crop to outmost guides")) self.chk_toOutmostGuides.setChecked(True) self.chk_toOutmostGuides.setToolTip(i18n("This will crop to the outmost guides if possible and otherwise use the underlying crop settings.")) formCrop.addRow("", self.chk_toOutmostGuides) - btn_fromSelection = QPushButton(i18n("Set margins from active selection")) + btn_fromSelection = QPushButton(i18n("Set Margins from Active Selection")) btn_fromSelection.clicked.connect(self.slot_set_margin_from_selection) # This doesn't work. formCrop.addRow("", btn_fromSelection) self.spn_marginLeft = QSpinBox() self.spn_marginLeft.setMaximum(99999) self.spn_marginLeft.setSuffix(" px") formCrop.addRow(i18n("Left:"), self.spn_marginLeft) self.spn_marginTop = QSpinBox() self.spn_marginTop.setMaximum(99999) self.spn_marginTop.setSuffix(" px") formCrop.addRow(i18n("Top:"), self.spn_marginTop) self.spn_marginRight = QSpinBox() self.spn_marginRight.setMaximum(99999) self.spn_marginRight.setSuffix(" px") formCrop.addRow(i18n("Right:"), self.spn_marginRight) self.spn_marginBottom = QSpinBox() self.spn_marginBottom.setMaximum(99999) self.spn_marginBottom.setSuffix(" px") formCrop.addRow(i18n("Bottom:"), self.spn_marginBottom) groupExportLayers = QGroupBox(i18n("Layers")) formLayers = QFormLayout() groupExportLayers.setLayout(formLayers) self.cmbLabelsRemove = labelSelector() formLayers.addRow(i18n("Label for removal:"), self.cmbLabelsRemove) self.ln_text_layer_name = QLineEdit() self.ln_text_layer_name.setToolTip(i18n("These are keywords that can be used to identify text layers. A layer only needs to contain the keyword to be recognized. Keywords should be comma separated.")) self.ln_panel_layer_name = QLineEdit() self.ln_panel_layer_name.setToolTip(i18n("These are keywords that can be used to identify panel layers. A layer only needs to contain the keyword to be recognized. Keywords should be comma separated.")) formLayers.addRow(i18n("Text Layer Key:"), self.ln_text_layer_name) formLayers.addRow(i18n("Panel Layer Key:"), self.ln_panel_layer_name) mainExportSettings.layout().addWidget(groupExportCrop) mainExportSettings.layout().addWidget(groupExportLayers) mainWidget.addTab(mainExportSettings, i18n("General")) # CBZ, crop, resize, which metadata to add. CBZexportSettings = QWidget() CBZexportSettings.setLayout(QVBoxLayout()) self.CBZactive = QCheckBox(i18n("Export to CBZ")) CBZexportSettings.layout().addWidget(self.CBZactive) self.CBZgroupResize = comic_export_resize_widget("CBZ") CBZexportSettings.layout().addWidget(self.CBZgroupResize) self.CBZactive.clicked.connect(self.CBZgroupResize.setEnabled) - CBZgroupMeta = QGroupBox(i18n("Metadata to add")) + CBZgroupMeta = QGroupBox(i18n("Metadata to Add")) # CBZexportSettings.layout().addWidget(CBZgroupMeta) CBZgroupMeta.setLayout(QFormLayout()) - mainWidget.addTab(CBZexportSettings, "CBZ") + mainWidget.addTab(CBZexportSettings, i18n("CBZ")) # ACBF, crop, resize, creator name, version history, panel layer, text layers. ACBFExportSettings = QWidget() ACBFform = QFormLayout() ACBFExportSettings.setLayout(QVBoxLayout()) ACBFdocInfo = QGroupBox() ACBFdocInfo.setTitle(i18n("ACBF Document Info")) ACBFdocInfo.setLayout(ACBFform) self.lnACBFSource = QLineEdit() self.lnACBFSource.setToolTip(i18n("Whether the acbf file is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here.")) self.lnACBFID = QLabel() - self.lnACBFID.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the json, but this is advanced usage.")) + self.lnACBFID.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the JSON, but this is advanced usage.")) self.spnACBFVersion = QSpinBox() self.ACBFhistoryModel = QStandardItemModel() acbfHistoryList = QListView() acbfHistoryList.setModel(self.ACBFhistoryModel) - btn_add_history = QPushButton(i18n("Add history entry")) + btn_add_history = QPushButton(i18n("Add History Entry")) btn_add_history.clicked.connect(self.slot_add_history_item) self.chkIncludeTranslatorComments = QCheckBox() - self.chkIncludeTranslatorComments.setText(i18n("Include Translator's Comments")) + self.chkIncludeTranslatorComments.setText(i18n("Include translator's comments")) self.chkIncludeTranslatorComments.setToolTip(i18n("A PO file can contain translator's comments. If this is checked, the translations comments will be added as references into the ACBF file.")) self.lnTranslatorHeader = QLineEdit() ACBFform.addRow(i18n("Source:"), self.lnACBFSource) ACBFform.addRow(i18n("ACBF UID:"), self.lnACBFID) ACBFform.addRow(i18n("Version:"), self.spnACBFVersion) - ACBFform.addRow(i18n("Version History:"), acbfHistoryList) + ACBFform.addRow(i18n("Version history:"), acbfHistoryList) ACBFform.addRow("", btn_add_history) ACBFform.addRow("", self.chkIncludeTranslatorComments) - ACBFform.addRow(i18n("Translator Header:"), self.lnTranslatorHeader) + ACBFform.addRow(i18n("Translator header:"), self.lnTranslatorHeader) ACBFAuthorInfo = QWidget() acbfAVbox = QVBoxLayout(ACBFAuthorInfo) infoLabel = QLabel(i18n("The people responsible for the generation of the CBZ/ACBF files.")) infoLabel.setWordWrap(True) ACBFAuthorInfo.layout().addWidget(infoLabel) self.ACBFauthorModel = QStandardItemModel(0, 6) labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Email"), i18n("Homepage")] self.ACBFauthorModel.setHorizontalHeaderLabels(labels) self.ACBFauthorTable = QTableView() acbfAVbox.addWidget(self.ACBFauthorTable) self.ACBFauthorTable.setModel(self.ACBFauthorModel) self.ACBFauthorTable.verticalHeader().setDragEnabled(True) self.ACBFauthorTable.verticalHeader().setDropIndicatorShown(True) self.ACBFauthorTable.verticalHeader().setSectionsMovable(True) self.ACBFauthorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual) AuthorButtons = QHBoxLayout() - btn_add_author = QPushButton(i18n("Add author")) + btn_add_author = QPushButton(i18n("Add Author")) btn_add_author.clicked.connect(self.slot_add_author) AuthorButtons.addWidget(btn_add_author) - btn_remove_author = QPushButton(i18n("Remove author")) + btn_remove_author = QPushButton(i18n("Remove Author")) btn_remove_author.clicked.connect(self.slot_remove_author) AuthorButtons.addWidget(btn_remove_author) acbfAVbox.addLayout(AuthorButtons) ACBFStyle = QWidget() ACBFStyle.setLayout(QHBoxLayout()) self.ACBFStylesModel = QStandardItemModel() self.ACBFStyleClass = QListView() self.ACBFStyleClass.setModel(self.ACBFStylesModel) ACBFStyle.layout().addWidget(self.ACBFStyleClass) ACBFStyleEdit = QWidget() ACBFStyleEditVB = QVBoxLayout(ACBFStyleEdit) - self.ACBFuseFont = QCheckBox(i18n("Use Font")) + self.ACBFuseFont = QCheckBox(i18n("Use font")) self.ACBFFontList = QListView() self.ACBFFontList.setItemDelegate(font_list_delegate()) self.ACBFuseFont.toggled.connect(self.font_slot_enable_font_view) self.ACBFFontListModel = QStandardItemModel() self.ACBFFontListModel.rowsRemoved.connect(self.slot_font_current_style) self.ACBFFontListModel.itemChanged.connect(self.slot_font_current_style) self.btnAcbfAddFont = QPushButton() self.btnAcbfAddFont.setIcon(Application.icon("list-add")) self.btnAcbfAddFont.clicked.connect(self.font_slot_add_font) self.btn_acbf_remove_font = QPushButton() self.btn_acbf_remove_font.setIcon(Application.icon("edit-delete")) self.btn_acbf_remove_font.clicked.connect(self.font_slot_remove_font) self.ACBFFontList.setModel(self.ACBFFontListModel) self.ACBFdefaultFont = QComboBox() self.ACBFdefaultFont.addItems(["sans-serif", "serif", "monospace", "cursive", "fantasy"]) acbfFontButtons = QHBoxLayout() acbfFontButtons.addWidget(self.btnAcbfAddFont) acbfFontButtons.addWidget(self.btn_acbf_remove_font) self.ACBFBold = QCheckBox(i18n("Bold")) self.ACBFItal = QCheckBox(i18n("Italic")) self.ACBFStyleClass.clicked.connect(self.slot_set_style) self.ACBFStyleClass.selectionModel().selectionChanged.connect(self.slot_set_style) self.ACBFStylesModel.itemChanged.connect(self.slot_set_style) self.ACBFBold.toggled.connect(self.slot_font_current_style) self.ACBFItal.toggled.connect(self.slot_font_current_style) colorWidget = QGroupBox(self) colorWidget.setTitle(i18n("Text Colors")) colorWidget.setLayout(QVBoxLayout()) self.regularColor = QColorDialog() self.invertedColor = QColorDialog() self.btn_acbfRegColor = QPushButton(i18n("Regular Text"), self) self.btn_acbfRegColor.clicked.connect(self.slot_change_regular_color) self.btn_acbfInvColor = QPushButton(i18n("Inverted Text"), self) self.btn_acbfInvColor.clicked.connect(self.slot_change_inverted_color) colorWidget.layout().addWidget(self.btn_acbfRegColor) colorWidget.layout().addWidget(self.btn_acbfInvColor) ACBFStyleEditVB.addWidget(colorWidget) ACBFStyleEditVB.addWidget(self.ACBFuseFont) ACBFStyleEditVB.addWidget(self.ACBFFontList) ACBFStyleEditVB.addLayout(acbfFontButtons) ACBFStyleEditVB.addWidget(self.ACBFdefaultFont) ACBFStyleEditVB.addWidget(self.ACBFBold) ACBFStyleEditVB.addWidget(self.ACBFItal) ACBFStyleEditVB.addStretch() ACBFStyle.layout().addWidget(ACBFStyleEdit) ACBFTabwidget = QTabWidget() ACBFTabwidget.addTab(ACBFdocInfo, i18n("Document Info")) ACBFTabwidget.addTab(ACBFAuthorInfo, i18n("Author Info")) ACBFTabwidget.addTab(ACBFStyle, i18n("Style Sheet")) ACBFExportSettings.layout().addWidget(ACBFTabwidget) mainWidget.addTab(ACBFExportSettings, i18n("ACBF")) # Epub export, crop, resize, other questions. EPUBexportSettings = QWidget() EPUBexportSettings.setLayout(QVBoxLayout()) self.EPUBactive = QCheckBox(i18n("Export to EPUB")) EPUBexportSettings.layout().addWidget(self.EPUBactive) self.EPUBgroupResize = comic_export_resize_widget("EPUB") EPUBexportSettings.layout().addWidget(self.EPUBgroupResize) self.EPUBactive.clicked.connect(self.EPUBgroupResize.setEnabled) - mainWidget.addTab(EPUBexportSettings, "EPUB") + mainWidget.addTab(EPUBexportSettings, i18n("EPUB")) # For Print. Crop, no resize. TIFFExportSettings = QWidget() TIFFExportSettings.setLayout(QVBoxLayout()) self.TIFFactive = QCheckBox(i18n("Export to TIFF")) TIFFExportSettings.layout().addWidget(self.TIFFactive) self.TIFFgroupResize = comic_export_resize_widget("TIFF") TIFFExportSettings.layout().addWidget(self.TIFFgroupResize) self.TIFFactive.clicked.connect(self.TIFFgroupResize.setEnabled) - mainWidget.addTab(TIFFExportSettings, "TIFF") + mainWidget.addTab(TIFFExportSettings, i18n("TIFF")) # SVG, crop, resize, embed vs link. #SVGExportSettings = QWidget() - #mainWidget.addTab(SVGExportSettings, "SVG") + #mainWidget.addTab(SVGExportSettings, i18n("SVG")) """ Add a history item to the acbf version history list. """ def slot_add_history_item(self): newItem = QStandardItem() - newItem.setText("v" + str(self.spnACBFVersion.value()) + "-" + i18n("in this version...")) + newItem.setText(str(i18n("v{version}-in this version...")).format(version=str(self.spnACBFVersion.value()))) self.ACBFhistoryModel.appendRow(newItem) """ Get the margins by treating the active selection in a document as the trim area. This allows people to snap selections to a vector or something, and then get the margins. """ def slot_set_margin_from_selection(self): doc = Application.activeDocument() if doc is not None: if doc.selection() is not None: self.spn_marginLeft.setValue(doc.selection().x()) self.spn_marginTop.setValue(doc.selection().y()) self.spn_marginRight.setValue(doc.width() - (doc.selection().x() + doc.selection().width())) self.spn_marginBottom.setValue(doc.height() - (doc.selection().y() + doc.selection().height())) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage self.ACBFauthorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.ACBFauthorModel.removeRow(self.ACBFauthorTable.currentIndex().row()) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] for i in range(self.ACBFauthorTable.verticalHeader().count()): headerLabelList.append(str(i)) for i in range(self.ACBFauthorTable.verticalHeader().count()): logicalI = self.ACBFauthorTable.verticalHeader().logicalIndex(i) headerLabelList[logicalI] = str(i + 1) self.ACBFauthorModel.setVerticalHeaderLabels(headerLabelList) """ Set the style item to the gui item's style. """ def slot_set_style(self): index = self.ACBFStyleClass.currentIndex() if index.isValid(): item = self.ACBFStylesModel.item(index.row()) fontUsed = item.data(role=styleEnum.FONT) if fontUsed is not None: self.ACBFuseFont.setChecked(fontUsed) else: self.ACBFuseFont.setChecked(False) self.font_slot_enable_font_view() fontList = item.data(role=styleEnum.FONTLIST) self.ACBFFontListModel.clear() for font in fontList: NewItem = QStandardItem(font) NewItem.setEditable(True) self.ACBFFontListModel.appendRow(NewItem) self.ACBFdefaultFont.setCurrentText(str(item.data(role=styleEnum.FONTGENERIC))) bold = item.data(role=styleEnum.BOLD) if bold is not None: self.ACBFBold.setChecked(bold) else: self.ACBFBold.setChecked(False) italic = item.data(role=styleEnum.ITALIC) if italic is not None: self.ACBFItal.setChecked(italic) else: self.ACBFItal.setChecked(False) """ Set the gui items to the currently selected style. """ def slot_font_current_style(self): index = self.ACBFStyleClass.currentIndex() if index.isValid(): item = self.ACBFStylesModel.item(index.row()) fontList = [] for row in range(self.ACBFFontListModel.rowCount()): font = self.ACBFFontListModel.item(row) fontList.append(font.text()) item.setData(self.ACBFuseFont.isChecked(), role=styleEnum.FONT) item.setData(fontList, role=styleEnum.FONTLIST) item.setData(self.ACBFdefaultFont.currentText(), role=styleEnum.FONTGENERIC) item.setData(self.ACBFBold.isChecked(), role=styleEnum.BOLD) item.setData(self.ACBFItal.isChecked(), role=styleEnum.ITALIC) self.ACBFStylesModel.setItem(index.row(), item) """ Change the regular color """ def slot_change_regular_color(self): if (self.regularColor.exec_() == QDialog.Accepted): square = QPixmap(32, 32) square.fill(self.regularColor.currentColor()) self.btn_acbfRegColor.setIcon(QIcon(square)) """ change the inverted color """ def slot_change_inverted_color(self): if (self.invertedColor.exec_() == QDialog.Accepted): square = QPixmap(32, 32) square.fill(self.invertedColor.currentColor()) self.btn_acbfInvColor.setIcon(QIcon(square)) def font_slot_enable_font_view(self): self.ACBFFontList.setEnabled(self.ACBFuseFont.isChecked()) self.btn_acbf_remove_font.setEnabled(self.ACBFuseFont.isChecked()) self.btnAcbfAddFont.setEnabled(self.ACBFuseFont.isChecked()) self.ACBFdefaultFont.setEnabled(self.ACBFuseFont.isChecked()) if self.ACBFFontListModel.rowCount() < 2: self.btn_acbf_remove_font.setEnabled(False) def font_slot_add_font(self): NewItem = QStandardItem(QFont().family()) NewItem.setEditable(True) self.ACBFFontListModel.appendRow(NewItem) def font_slot_remove_font(self): index = self.ACBFFontList.currentIndex() if index.isValid(): self.ACBFFontListModel.removeRow(index.row()) if self.ACBFFontListModel.rowCount() < 2: self.btn_acbf_remove_font.setEnabled(False) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "cropToGuides" in config.keys(): self.chk_toOutmostGuides.setChecked(config["cropToGuides"]) if "cropLeft" in config.keys(): self.spn_marginLeft.setValue(config["cropLeft"]) if "cropTop" in config.keys(): self.spn_marginTop.setValue(config["cropTop"]) if "cropRight" in config.keys(): self.spn_marginRight.setValue(config["cropRight"]) if "cropBottom" in config.keys(): self.spn_marginBottom.setValue(config["cropBottom"]) if "labelsToRemove" in config.keys(): self.cmbLabelsRemove.setLabels(config["labelsToRemove"]) if "textLayerNames" in config.keys(): self.ln_text_layer_name.setText(", ".join(config["textLayerNames"])) else: self.ln_text_layer_name.setText("text") if "panelLayerNames" in config.keys(): self.ln_panel_layer_name.setText(", ".join(config["panelLayerNames"])) else: self.ln_panel_layer_name.setText("panels") self.CBZgroupResize.set_config(config) if "CBZactive" in config.keys(): self.CBZactive.setChecked(config["CBZactive"]) self.EPUBgroupResize.set_config(config) if "EPUBactive" in config.keys(): self.EPUBactive.setChecked(config["EPUBactive"]) self.TIFFgroupResize.set_config(config) if "TIFFactive" in config.keys(): self.TIFFactive.setChecked(config["TIFFactive"]) if "acbfAuthor" in config.keys(): if isinstance(config["acbfAuthor"], list): for author in config["acbfAuthor"]: listItems = [] listItems.append(QStandardItem(author.get("nickname", ""))) listItems.append(QStandardItem(author.get("first-name", ""))) listItems.append(QStandardItem(author.get("initials", ""))) listItems.append(QStandardItem(author.get("last-name", ""))) listItems.append(QStandardItem(author.get("email", ""))) listItems.append(QStandardItem(author.get("homepage", ""))) self.ACBFauthorModel.appendRow(listItems) pass else: listItems = [] listItems.append(QStandardItem(config["acbfAuthor"])) # Nick name for i in range(0, 5): listItems.append(QStandardItem()) # First name self.ACBFauthorModel.appendRow(listItems) if "acbfSource" in config.keys(): self.lnACBFSource.setText(config["acbfSource"]) if "acbfID" in config.keys(): self.lnACBFID.setText(config["acbfID"]) else: self.lnACBFID.setText(QUuid.createUuid().toString()) if "acbfVersion" in config.keys(): self.spnACBFVersion.setValue(config["acbfVersion"]) if "acbfHistory" in config.keys(): for h in config["acbfHistory"]: item = QStandardItem() item.setText(h) self.ACBFhistoryModel.appendRow(item) if "acbfStyles" in config.keys(): styleDict = config.get("acbfStyles", {}) for key in self.acbfStylesList: keyDict = styleDict.get(key, {}) style = QStandardItem(key.title()) style.setCheckable(True) if key in styleDict.keys(): style.setCheckState(Qt.Checked) else: style.setCheckState(Qt.Unchecked) fontOn = False if "font" in keyDict.keys() or "genericfont" in keyDict.keys(): fontOn = True style.setData(fontOn, role=styleEnum.FONT) if "font" in keyDict: fontlist = keyDict["font"] if isinstance(fontlist, list): font = keyDict.get("font", QFont().family()) style.setData(font, role=styleEnum.FONTLIST) else: style.setData([fontlist], role=styleEnum.FONTLIST) else: style.setData([QFont().family()], role=styleEnum.FONTLIST) style.setData(keyDict.get("genericfont", "sans-serif"), role=styleEnum.FONTGENERIC) style.setData(keyDict.get("bold", False), role=styleEnum.BOLD) style.setData(keyDict.get("ital", False), role=styleEnum.ITALIC) self.ACBFStylesModel.appendRow(style) keyDict = styleDict.get("general", {}) self.regularColor.setCurrentColor(QColor(keyDict.get("color", "#000000"))) square = QPixmap(32, 32) square.fill(self.regularColor.currentColor()) self.btn_acbfRegColor.setIcon(QIcon(square)) keyDict = styleDict.get("inverted", {}) self.invertedColor.setCurrentColor(QColor(keyDict.get("color", "#FFFFFF"))) square.fill(self.invertedColor.currentColor()) self.btn_acbfInvColor.setIcon(QIcon(square)) else: for key in self.acbfStylesList: style = QStandardItem(key.title()) style.setCheckable(True) style.setCheckState(Qt.Unchecked) style.setData(False, role=styleEnum.FONT) style.setData(QFont().family(), role=styleEnum.FONTLIST) style.setData("sans-serif", role=styleEnum.FONTGENERIC) style.setData(False, role=styleEnum.BOLD) #Bold style.setData(False, role=styleEnum.ITALIC) #Italic self.ACBFStylesModel.appendRow(style) self.CBZgroupResize.setEnabled(self.CBZactive.isChecked()) self.lnTranslatorHeader.setText(config.get("translatorHeader", "Translator's Notes")) self.chkIncludeTranslatorComments.setChecked(config.get("includeTranslComment", False)) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["cropToGuides"] = self.chk_toOutmostGuides.isChecked() config["cropLeft"] = self.spn_marginLeft.value() config["cropTop"] = self.spn_marginTop.value() config["cropBottom"] = self.spn_marginRight.value() config["cropRight"] = self.spn_marginBottom.value() config["labelsToRemove"] = self.cmbLabelsRemove.getLabels() config["CBZactive"] = self.CBZactive.isChecked() config = self.CBZgroupResize.get_config(config) config["EPUBactive"] = self.EPUBactive.isChecked() config = self.EPUBgroupResize.get_config(config) config["TIFFactive"] = self.TIFFactive.isChecked() config = self.TIFFgroupResize.get_config(config) authorList = [] for row in range(self.ACBFauthorTable.verticalHeader().count()): logicalIndex = self.ACBFauthorTable.verticalHeader().logicalIndex(row) listEntries = ["nickname", "first-name", "initials", "last-name", "email", "homepage"] author = {} for i in range(len(listEntries)): entry = self.ACBFauthorModel.data(self.ACBFauthorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["acbfAuthor"] = authorList config["acbfSource"] = self.lnACBFSource.text() config["acbfID"] = self.lnACBFID.text() config["acbfVersion"] = self.spnACBFVersion.value() versionList = [] for r in range(self.ACBFhistoryModel.rowCount()): index = self.ACBFhistoryModel.index(r, 0) versionList.append(self.ACBFhistoryModel.data(index, Qt.DisplayRole)) config["acbfHistory"] = versionList acbfStylesDict = {} for row in range(0, self.ACBFStylesModel.rowCount()): entry = self.ACBFStylesModel.item(row) if entry.checkState() == Qt.Checked: key = entry.text().lower() style = {} if entry.data(role=styleEnum.FONT): font = entry.data(role=styleEnum.FONTLIST) if font is not None: style["font"] = font genericfont = entry.data(role=styleEnum.FONTGENERIC) if font is not None: style["genericfont"] = genericfont bold = entry.data(role=styleEnum.BOLD) if bold is not None: style["bold"] = bold italic = entry.data(role=styleEnum.ITALIC) if italic is not None: style["ital"] = italic acbfStylesDict[key] = style acbfStylesDict["general"] = {"color": self.regularColor.currentColor().name()} acbfStylesDict["inverted"] = {"color": self.invertedColor.currentColor().name()} config["acbfStyles"] = acbfStylesDict config["translatorHeader"] = self.lnTranslatorHeader.text() config["includeTranslComment"] = self.chkIncludeTranslatorComments.isChecked() # Turn this into something that retreives from a line-edit when string freeze is over. config["textLayerNames"] = self.ln_text_layer_name.text().split(",") config["panelLayerNames"] = self.ln_panel_layer_name.text().split(",") return config diff --git a/plugins/python/comics_project_management_tools/comics_exporter.py b/plugins/python/comics_project_management_tools/comics_exporter.py index 6b54f58d08..9d9856f9b4 100644 --- a/plugins/python/comics_project_management_tools/comics_exporter.py +++ b/plugins/python/comics_project_management_tools/comics_exporter.py @@ -1,647 +1,647 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ An exporter that take the comicsConfig and uses it to generate several files. """ import sys from pathlib import Path import zipfile from xml.dom import minidom from xml.etree import ElementTree as ET import types import re from PyQt5.QtWidgets import QLabel, QProgressDialog, QMessageBox, qApp # For the progress dialog. from PyQt5.QtCore import QElapsedTimer, QLocale, Qt, QRectF, QPointF from PyQt5.QtGui import QImage, QTransform, QPainterPath, QFontMetrics, QFont from krita import * from . import exporters """ The sizesCalculator is a convenience class for interpretting the resize configuration from the export settings dialog. It is also used for batch resize. """ class sizesCalculator(): def __init__(self): pass def get_scale_from_resize_config(self, config, listSizes): listScaleTo = listSizes oldWidth = listSizes[0] oldHeight = listSizes[1] oldXDPI = listSizes[2] oldYDPI = listSizes[3] if "Method" in config.keys(): method = config["Method"] if method is 0: # percentage percentage = config["Percentage"] / 100 listScaleTo[0] = round(oldWidth * percentage) listScaleTo[1] = round(oldHeight * percentage) if method is 1: # dpi DPI = config["DPI"] listScaleTo[0] = round((oldWidth / oldXDPI) * DPI) listScaleTo[1] = round((oldHeight / oldYDPI) * DPI) listScaleTo[2] = DPI listScaleTo[3] = DPI if method is 2: # maximum width width = config["Width"] listScaleTo[0] = width listScaleTo[1] = round((oldHeight / oldWidth) * width) if method is 3: # maximum height height = config["Height"] listScaleTo[1] = height listScaleTo[0] = round((oldWidth / oldHeight) * height) return listScaleTo """ The comicsExporter is a class that batch exports to all the requested formats. Make it, set_config with the right data, and then call up "export". The majority of the functions are meta-data encoding functions. """ class comicsExporter(): acbfLocation = str() acbfPageData = [] cometLocation = str() comicRackInfo = str() pagesLocationList = {} # set of keys used to define specific export behaviour for this page. pageKeys = ["acbf_title", "acbf_none", "acbf_fade", "acbf_blend", "acbf_horizontal", "acbf_vertical"] def __init__(self): pass """ The the configuration of the exporter. @param config: A dictionary containing all the config. @param projectUrl: the main location of the project folder. """ def set_config(self, config, projectURL): self.configDictionary = config self.projectURL = projectURL self.pagesLocationList = {} self.acbfLocation = str() self.acbfPageData = [] self.cometLocation = str() self.comicRackInfo = str() """ Export everything according to config and get yourself a coffee. This won't work if the config hasn't been set. """ def export(self): export_success = False path = Path(self.projectURL) if path.exists(): # Make a meta-data folder so we keep the export folder nice and clean. exportPath = path / self.configDictionary["exportLocation"] if Path(exportPath / "metadata").exists() is False: Path(exportPath / "metadata").mkdir() # Get to which formats to export, and set the sizeslist. lengthProcess = len(self.configDictionary["pages"]) sizesList = {} if "CBZ" in self.configDictionary.keys(): if self.configDictionary["CBZactive"]: lengthProcess += 5 sizesList["CBZ"] = self.configDictionary["CBZ"] if "EPUB" in self.configDictionary.keys(): if self.configDictionary["EPUBactive"]: lengthProcess += 1 sizesList["EPUB"] = self.configDictionary["EPUB"] if "TIFF" in self.configDictionary.keys(): if self.configDictionary["TIFFactive"]: sizesList["TIFF"] = self.configDictionary["TIFF"] # Export the pngs according to the sizeslist. # Create a progress dialog. - self.progress = QProgressDialog("Preparing export.", str(), 0, lengthProcess) - self.progress.setWindowTitle("Exporting comic...") + self.progress = QProgressDialog(i18n("Preparing export."), str(), 0, lengthProcess) + self.progress.setWindowTitle(i18n("Exporting Comic...")) self.progress.setCancelButton(None) self.timer = QElapsedTimer() self.timer.start() self.progress.show() qApp.processEvents() export_success = self.save_out_pngs(sizesList) # Export acbf metadata. if export_success: if "CBZ" in sizesList.keys(): title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] self.acbfLocation = str(exportPath / "metadata" / str(title + ".acbf")) locationStandAlone = str(exportPath / str(title + ".acbf")) - self.progress.setLabelText("Saving out ACBF and\nACBF standalone") + self.progress.setLabelText(i18n("Saving out ACBF and\nACBF standalone")) self.progress.setValue(self.progress.value()+2) export_success = exporters.ACBF.write_xml(self.configDictionary, self.acbfPageData, self.pagesLocationList["CBZ"], self.acbfLocation, locationStandAlone, self.projectURL) print("CPMT: Exported to ACBF", export_success) # Export and package CBZ and Epub. if export_success: if "CBZ" in sizesList.keys(): export_success = self.export_to_cbz(exportPath) print("CPMT: Exported to CBZ", export_success) if "EPUB" in sizesList.keys(): - self.progress.setLabelText("Saving out EPUB") + self.progress.setLabelText(i18n("Saving out EPUB")) self.progress.setValue(self.progress.value()+1) export_success = exporters.EPUB.export(self.configDictionary, self.projectURL, self.pagesLocationList["EPUB"]) print("CPMT: Exported to EPUB", export_success) else: - QMessageBox.warning(self, i18n("Export not possible"), i18n("Nothing to export,\nurl not set."), QMessageBox.Ok) + QMessageBox.warning(self, i18n("Export not Possible"), i18n("Nothing to export, URL not set."), QMessageBox.Ok) print("CPMT: Nothing to export, url not set.") return export_success """ This calls up all the functions necessary for making a cbz. """ def export_to_cbz(self, exportPath): title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] - self.progress.setLabelText("Saving out CoMet\nmetadata file") + self.progress.setLabelText(i18n("Saving out CoMet\nmetadata file")) self.progress.setValue(self.progress.value()+1) self.cometLocation = str(exportPath / "metadata" / str(title + " CoMet.xml")) export_success = exporters.CoMet.write_xml(self.configDictionary, self.pagesLocationList["CBZ"], self.cometLocation) self.comicRackInfo = str(exportPath / "metadata" / "ComicInfo.xml") - self.progress.setLabelText("Saving out Comicrack\nmetadata file") + self.progress.setLabelText(i18n("Saving out Comicrack\nmetadata file")) self.progress.setValue(self.progress.value()+1) export_success = exporters.comic_rack_xml.write_xml(self.configDictionary, self.pagesLocationList["CBZ"], self.comicRackInfo) self.package_cbz(exportPath) return export_success def save_out_pngs(self, sizesList): # A small fix to ensure crop to guides is set. if "cropToGuides" not in self.configDictionary.keys(): self.configDictionary["cropToGuides"] = False # Check if we have pages at all... if "pages" in self.configDictionary.keys(): # Check if there's export methods, and if so make sure the appropriate dictionaries are initialised. if len(sizesList.keys()) < 1: print("CPMT: Export failed because there's no export methods set.") return False else: for key in sizesList.keys(): self.pagesLocationList[key] = [] # Get the appropriate paths. path = Path(self.projectURL) exportPath = path / self.configDictionary["exportLocation"] pagesList = self.configDictionary["pages"] fileName = str(exportPath) """ Mini function to handle the setup of this string. """ def timeString(timePassed, timeEstimated): return str(i18n("Time passed: {passedString}\n Estimated: {estimated}")).format(passedString=timePassed, estimated=timeEstimated) for p in range(0, len(pagesList)): pagesDone = str(i18n("{pages} of {pagesTotal} done.")).format(pages=p, pagesTotal=len(pagesList)) # Update the label in the progress dialog. self.progress.setValue(p) timePassed = self.timer.elapsed() if p > 0: timeEstimated = (len(pagesList) - p) * (timePassed / p) estimatedString = self.parseTime(timeEstimated) else: estimatedString = str(u"\u221E") passedString = self.parseTime(timePassed) self.progress.setLabelText("\n".join([pagesDone, timeString(passedString, estimatedString), i18n("Opening next page")])) qApp.processEvents() # Get the appropriate url and open the page. url = str(Path(self.projectURL) / pagesList[p]) page = Application.openDocument(url) page.waitForDone() # Update the progress bar a little self.progress.setLabelText("\n".join([pagesDone, timeString(self.parseTime(self.timer.elapsed()), estimatedString), i18n("Cleaning up page")])) # remove layers and flatten. labelList = self.configDictionary["labelsToRemove"] panelsAndText = [] # These three lines are what is causing the page not to close. root = page.rootNode() self.getPanelsAndText(root, panelsAndText) self.removeLayers(labelList, root) page.refreshProjection() # We'll need the offset and scale for aligning the panels and text correctly. We're getting this from the CBZ pageData = {} pageData["vector"] = panelsAndText tree = ET.fromstring(page.documentInfo()) pageData["title"] = page.name() calligra = "{http://www.calligra.org/DTD/document-info}" about = tree.find(calligra + "about") keywords = about.find(calligra + "keyword") keys = str(keywords.text).split(",") pKeys = [] for key in keys: if key in self.pageKeys: pKeys.append(key) pageData["keys"] = pKeys page.flatten() page.waitForDone() batchsave = Application.batchmode() Application.setBatchmode(True) # Start making the format specific copy. for key in sizesList.keys(): # Update the progress bar a little self.progress.setLabelText("\n".join([pagesDone, timeString(self.parseTime(self.timer.elapsed()), estimatedString), str(i18n("Exporting for {key}")).format(key=key)])) w = sizesList[key] # copy over data projection = page.clone() projection.setBatchmode(True) # Crop. Cropping per guide only happens if said guides have been found. if w["Crop"] is True: listHGuides = [] listHGuides = page.horizontalGuides() listHGuides.sort() for i in range(len(listHGuides) - 1, 0, -1): if listHGuides[i] < 0 or listHGuides[i] > page.height(): listHGuides.pop(i) listVGuides = page.verticalGuides() listVGuides.sort() for i in range(len(listVGuides) - 1, 0, -1): if listVGuides[i] < 0 or listVGuides[i] > page.width(): listVGuides.pop(i) if self.configDictionary["cropToGuides"] and len(listVGuides) > 1: cropx = listVGuides[0] cropw = listVGuides[-1] - cropx else: cropx = self.configDictionary["cropLeft"] cropw = page.width() - self.configDictionary["cropRight"] - cropx if self.configDictionary["cropToGuides"] and len(listHGuides) > 1: cropy = listHGuides[0] croph = listHGuides[-1] - cropy else: cropy = self.configDictionary["cropTop"] croph = page.height() - self.configDictionary["cropBottom"] - cropy projection.crop(cropx, cropy, cropw, croph) projection.waitForDone() qApp.processEvents() # resize appropriately else: cropx = 0 cropy = 0 res = page.resolution() listScales = [projection.width(), projection.height(), res, res] projectionOldSize = [projection.width(), projection.height()] sizesCalc = sizesCalculator() listScales = sizesCalc.get_scale_from_resize_config(config=w, listSizes=listScales) projection.unlock() projection.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") projection.waitForDone() qApp.processEvents() # png, gif and other webformats should probably be in 8bit srgb at maximum. if key != "TIFF": if (projection.colorModel() != "RGBA" and projection.colorModel() != "GRAYA") or projection.colorDepth() != "U8": projection.setColorSpace("RGBA", "U8", "sRGB built-in") else: # Tiff on the other hand can handle all the colormodels, but can only handle integer bit depths. # Tiff is intended for print output, and 16 bit integer will be sufficient. if projection.colorDepth() != "U8" or projection.colorDepth() != "U16": projection.setColorSpace(page.colorModel(), "U16", page.colorProfile()) # save # Make sure the folder name for this export exists. It'll allow us to keep the # export folders nice and clean. folderName = str(key + "-" + w["FileType"]) if Path(exportPath / folderName).exists() is False: Path.mkdir(exportPath / folderName) # Get a nice and descriptive fle name. fn = str(Path(exportPath / folderName) / str("page_" + format(p, "03d") + "_" + str(listScales[0]) + "x" + str(listScales[1]) + "." + w["FileType"])) # Finally save and add the page to a list of pages. This will make it easy for the packaging function to # find the pages and store them. projection.exportImage(fn, InfoObject()) projection.waitForDone() qApp.processEvents() if key == "CBZ": transform = {} transform["offsetX"] = cropx transform["offsetY"] = cropy transform["resDiff"] = page.resolution() / 72 transform["scaleWidth"] = projection.width() / projectionOldSize[0] transform["scaleHeight"] = projection.height() / projectionOldSize[1] pageData["transform"] = transform self.pagesLocationList[key].append(fn) projection.close() self.acbfPageData.append(pageData) page.close() self.progress.setValue(len(pagesList)) Application.setBatchmode(batchsave) # TODO: Check what or whether memory leaks are still caused and otherwise remove the entry below. print("CPMT: Export has finished. If there are memory leaks, they are caused by file layers.") return True print("CPMT: Export not happening because there aren't any pages.") - QMessageBox.warning(self, i18n("Export not possible"), i18n("Export not happening because\nthere aren't any pages."), QMessageBox.Ok) + QMessageBox.warning(self, i18n("Export not Possible"), i18n("Export not happening because there are no pages."), QMessageBox.Ok) return False """ Function to get the panel and text data. """ def getPanelsAndText(self, node, list): textLayersToSearch = ["text"] panelLayersToSearch = ["panels"] if "textLayerNames" in self.configDictionary.keys(): textLayersToSearch = self.configDictionary["textLayerNames"] if "panelLayerNames" in self.configDictionary.keys(): panelLayersToSearch = self.configDictionary["panelLayerNames"] if node.type() == "vectorlayer": for name in panelLayersToSearch: if str(name).lower() in str(node.name()).lower(): for shape in node.shapes(): if (shape.type() == "groupshape"): self.getPanelsAndTextVector(shape, list) else: self.handleShapeDescription(shape, list) for name in textLayersToSearch: if str(name).lower() in str(node.name()).lower(): for shape in node.shapes(): if (shape.type() == "groupshape"): self.getPanelsAndTextVector(shape, list, True) else: self.handleShapeDescription(shape, list, True) else: if node.childNodes(): for child in node.childNodes(): self.getPanelsAndText(node=child, list=list) def parseTime(self, time = 0): timeList = [] timeList.append(str(int(time / 60000))) timeList.append(format(int((time%60000) / 1000), "02d")) timeList.append(format(int(time % 1000), "03d")) return ":".join(timeList) """ Function to get the panel and text data from a group shape """ def getPanelsAndTextVector(self, group, list, textOnly=False): for shape in group.shapes(): if (shape.type() == "groupshape"): self.getPanelsAndTextVector(shape, list, textOnly) else: self.handleShapeDescription(shape, list, textOnly) """ Function to get text and panels in a format that acbf will accept """ def handleShapeDescription(self, shape, list, textOnly=False): if (shape.type() != "KoSvgTextShapeID" and textOnly is True): return shapeDesc = {} shapeDesc["name"] = shape.name() rect = shape.boundingBox() listOfPoints = [rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft()] shapeDoc = minidom.parseString(shape.toSvg()) docElem = shapeDoc.documentElement svgRegExp = re.compile('[MLCSQHVATmlzcqshva]\d+\.?\d* \d+\.?\d*') transform = docElem.getAttribute("transform") coord = [] adjust = QTransform() # TODO: If we get global transform api, use that instead of parsing manually. if "translate" in transform: transform = transform.replace('translate(', '') for c in transform[:-1].split(" "): coord.append(float(c)) if len(coord) < 2: coord.append(coord[0]) adjust = QTransform(1, 0, 0, 1, coord[0], coord[1]) if "matrix" in transform: transform = transform.replace('matrix(', '') for c in transform[:-1].split(" "): coord.append(float(c)) adjust = QTransform(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]) path = QPainterPath() if docElem.localName == "path": dVal = docElem.getAttribute("d") listOfSvgStrings = [" "] listOfSvgStrings = svgRegExp.findall(dVal) if listOfSvgStrings: listOfPoints = [] for l in listOfSvgStrings: line = l[1:] coordinates = line.split(" ") if len(coordinates) < 2: coordinates.append(coordinates[0]) x = float(coordinates[-2]) y = float(coordinates[-1]) offset = QPointF() if l.islower(): offset = listOfPoints[0] if l.lower().startswith("m"): path.moveTo(QPointF(x, y) + offset) elif l.lower().startswith("h"): y = listOfPoints[-1].y() path.lineTo(QPointF(x, y) + offset) elif l.lower().startswith("v"): x = listOfPoints[-1].x() path.lineTo(QPointF(x, y) + offset) elif l.lower().startswith("c"): path.cubicTo(coordinates[0], coordinates[1], coordinates[2], coordinates[3], x, y) else: path.lineTo(QPointF(x, y) + offset) path.setFillRule(Qt.WindingFill) for polygon in path.simplified().toSubpathPolygons(adjust): for point in polygon: listOfPoints.append(point) elif docElem.localName == "rect": listOfPoints = [] if (docElem.hasAttribute("x")): x = float(docElem.getAttribute("x")) else: x = 0 if (docElem.hasAttribute("y")): y = float(docElem.getAttribute("y")) else: y = 0 w = float(docElem.getAttribute("width")) h = float(docElem.getAttribute("height")) path.addRect(QRectF(x, y, w, h)) for point in path.toFillPolygon(adjust): listOfPoints.append(point) elif docElem.localName == "ellipse": listOfPoints = [] if (docElem.hasAttribute("cx")): x = float(docElem.getAttribute("cx")) else: x = 0 if (docElem.hasAttribute("cy")): y = float(docElem.getAttribute("cy")) else: y = 0 ry = float(docElem.getAttribute("ry")) rx = float(docElem.getAttribute("rx")) path.addEllipse(QPointF(x, y), rx, ry) for point in path.toFillPolygon(adjust): listOfPoints.append(point) elif docElem.localName == "text": # NOTE: This only works for horizontal preformated text. Vertical text needs a different # ordering of the rects, and wraparound should try to take the shape it is wrapped in. family = "sans-serif" if docElem.hasAttribute("font-family"): family = docElem.getAttribute("font-family") size = "11" if docElem.hasAttribute("font-size"): size = docElem.getAttribute("font-size") multilineText = True for el in docElem.childNodes: if el.nodeType == minidom.Node.TEXT_NODE: multilineText = False if multilineText: listOfPoints = [] listOfRects = [] # First we collect all the possible line-rects. for el in docElem.childNodes: if docElem.hasAttribute("font-family"): family = docElem.getAttribute("font-family") if docElem.hasAttribute("font-size"): size = docElem.getAttribute("font-size") fontsize = int(size) font = QFont(family, fontsize) string = el.toxml() string = re.sub("\<.*?\>", " ", string) string = string.replace(" ", " ") width = min(QFontMetrics(font).width(string.strip()), rect.width()) height = QFontMetrics(font).height() anchor = "start" if docElem.hasAttribute("text-anchor"): anchor = docElem.getAttribute("text-anchor") top = rect.top() if len(listOfRects)>0: top = listOfRects[-1].bottom() if anchor == "start": spanRect = QRectF(rect.left(), top, width, height) listOfRects.append(spanRect) elif anchor == "end": spanRect = QRectF(rect.right()-width, top, width, height) listOfRects.append(spanRect) else: # Middle spanRect = QRectF(rect.center().x()-(width*0.5), top, width, height) listOfRects.append(spanRect) # Now we have all the rects, we can check each and draw a # polygon around them. heightAdjust = (rect.height()-(listOfRects[-1].bottom()-rect.top()))/len(listOfRects) for i in range(len(listOfRects)): span = listOfRects[i] addtionalHeight = i*heightAdjust if i == 0: listOfPoints.append(span.topLeft()) listOfPoints.append(span.topRight()) else: if listOfRects[i-1].width()< span.width(): listOfPoints.append(QPointF(span.right(), span.top()+addtionalHeight)) listOfPoints.insert(0, QPointF(span.left(), span.top()+addtionalHeight)) else: bottom = listOfRects[i-1].bottom()+addtionalHeight-heightAdjust listOfPoints.append(QPointF(listOfRects[i-1].right(), bottom)) listOfPoints.insert(0, QPointF(listOfRects[i-1].left(), bottom)) listOfPoints.append(QPointF(span.right(), rect.bottom())) listOfPoints.insert(0, QPointF(span.left(), rect.bottom())) path = QPainterPath() path.moveTo(listOfPoints[0]) for p in range(1, len(listOfPoints)): path.lineTo(listOfPoints[p]) path.closeSubpath() listOfPoints = [] for point in path.toFillPolygon(adjust): listOfPoints.append(point) shapeDesc["boundingBox"] = listOfPoints if (shape.type() == "KoSvgTextShapeID" and textOnly is True): shapeDesc["text"] = shape.toSvg() list.append(shapeDesc) """ Function to remove layers when they have the given labels. If not, but the node does have children, check those too. """ def removeLayers(self, labels, node): if node.colorLabel() in labels: node.remove() else: if node.childNodes(): for child in node.childNodes(): self.removeLayers(labels, node=child) """ package cbz puts all the meta-data and relevant files into an zip file ending with ".cbz" """ def package_cbz(self, exportPath): # Use the project name if there's no title to avoid sillyness with unnamed zipfiles. title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] # Get the appropriate path. url = str(exportPath / str(title + ".cbz")) # Create a zip file. cbzArchive = zipfile.ZipFile(url, mode="w", compression=zipfile.ZIP_STORED) # Add all the meta data files. cbzArchive.write(self.acbfLocation, Path(self.acbfLocation).name) cbzArchive.write(self.cometLocation, Path(self.cometLocation).name) cbzArchive.write(self.comicRackInfo, Path(self.comicRackInfo).name) comic_book_info_json_dump = str() - self.progress.setLabelText("Saving out Comicbook\ninfo metadata file") + self.progress.setLabelText(i18n("Saving out Comicbook\ninfo metadata file")) self.progress.setValue(self.progress.value()+1) comic_book_info_json_dump = exporters.comic_book_info.writeJson(self.configDictionary) cbzArchive.comment = comic_book_info_json_dump.encode("utf-8") # Add the pages. if "CBZ" in self.pagesLocationList.keys(): for page in self.pagesLocationList["CBZ"]: if (Path(page).exists()): cbzArchive.write(page, Path(page).name) - self.progress.setLabelText("Packaging CBZ") + self.progress.setLabelText(i18n("Packaging CBZ")) self.progress.setValue(self.progress.value()+1) # Close the zip file when done. cbzArchive.close() diff --git a/plugins/python/comics_project_management_tools/comics_metadata_dialog.py b/plugins/python/comics_project_management_tools/comics_metadata_dialog.py index c8fed7de9b..d1cf198a69 100644 --- a/plugins/python/comics_project_management_tools/comics_metadata_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_metadata_dialog.py @@ -1,757 +1,757 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ This is a metadata editor that helps out setting the proper metadata """ import sys import os # For finding the script location. import csv import re import types from pathlib import Path # For reading all the files in a directory. from PyQt5.QtGui import QStandardItem, QStandardItemModel, QImage, QIcon, QPixmap, QPainter, QPalette, QFontDatabase from PyQt5.QtWidgets import QComboBox, QCompleter, QStyledItemDelegate, QLineEdit, QDialog, QDialogButtonBox, QVBoxLayout, QFormLayout, QTabWidget, QWidget, QPlainTextEdit, QHBoxLayout, QSpinBox, QDateEdit, QPushButton, QLabel, QTableView from PyQt5.QtCore import QDir, QLocale, QStringListModel, Qt, QDate, QSize """ multi entry completer cobbled together from the two examples on stackoverflow:3779720 This allows us to let people type in comma-separated lists and get completion for those. """ class multi_entry_completer(QCompleter): punctuation = "," def __init__(self, parent=None): super(QCompleter, self).__init__(parent) def pathFromIndex(self, index): path = QCompleter.pathFromIndex(self, index) string = str(self.widget().text()) split = string.split(self.punctuation) if len(split) > 1: path = "%s, %s" % (",".join(split[:-1]), path) return path def splitPath(self, path): split = str(path.split(self.punctuation)[-1]) if split.startswith(" "): split = split[1:] if split.endswith(" "): split = split[:-1] return [split] """ Language combobox that can take locale codes and get the right language for it and visa-versa. """ class language_combo_box(QComboBox): languageList = [] codesList = [] def __init__(self, parent=None): super(QComboBox, self).__init__(parent) for i in range(1, 357): locale = QLocale(i) if locale and QLocale.languageToString(locale.language()) != "C": codeName = locale.name().split("_")[0] if codeName not in self.codesList: self.codesList.append(codeName) self.codesList.sort() for lang in self.codesList: locale = QLocale(lang) if locale: languageName = QLocale.languageToString(locale.language()) self.languageList.append(languageName.title()) self.setIconSize(QSize(32, 22)) codeIcon = QImage(self.iconSize(), QImage.Format_ARGB32) painter = QPainter(codeIcon) painter.setBrush(Qt.transparent) codeIcon.fill(Qt.transparent) font = QFontDatabase().systemFont(QFontDatabase.FixedFont) painter.setFont(font) painter.setPen(self.palette().color(QPalette.Text)) painter.drawText(codeIcon.rect(), Qt.AlignCenter,lang) painter.end() self.addItem(QIcon(QPixmap.fromImage(codeIcon)), languageName.title()) def codeForCurrentEntry(self): if self.currentText() in self.languageList: return self.codesList[self.languageList.index(self.currentText())] def setEntryToCode(self, code): if (code == "C" and "en" in self.codesList): self.setCurrentIndex(self.codesList.index("en")) if code in self.codesList: self.setCurrentIndex(self.codesList.index(code)) class country_combo_box(QComboBox): countryList = [] codesList = [] def __init__(self, parent=None): super(QComboBox, self).__init__(parent) def set_country_for_locale(self, languageCode): self.clear() self.codesList = [] self.countryList = [] for locale in QLocale.matchingLocales(QLocale(languageCode).language(), QLocale.AnyScript, QLocale.AnyCountry): codeName = locale.name().split("_")[-1] if codeName not in self.codesList: self.codesList.append(codeName) self.codesList.sort() for country in self.codesList: locale = QLocale(languageCode+"_"+country) if locale: countryName = locale.nativeCountryName() self.countryList.append(countryName.title()) self.setIconSize(QSize(32, 22)) codeIcon = QImage(self.iconSize(), QImage.Format_ARGB32) painter = QPainter(codeIcon) painter.setBrush(Qt.transparent) codeIcon.fill(Qt.transparent) font = QFontDatabase().systemFont(QFontDatabase.FixedFont) painter.setFont(font) painter.setPen(self.palette().color(QPalette.Text)) painter.drawText(codeIcon.rect(), Qt.AlignCenter,country) painter.end() self.addItem(QIcon(QPixmap.fromImage(codeIcon)), countryName.title()) def codeForCurrentEntry(self): if self.currentText() in self.countryList: return self.codesList[self.countryList.index(self.currentText())] def setEntryToCode(self, code): if code == "C": self.setCurrentIndex(0) elif code in self.codesList: self.setCurrentIndex(self.codesList.index(code)) """ A combobox that fills up with licenses from a CSV, and also sets tooltips from that csv. """ class license_combo_box(QComboBox): def __init__(self, parent=None): super(QComboBox, self).__init__(parent) mainP = os.path.dirname(__file__) languageP = os.path.join(mainP, "LicenseList.csv") model = QStandardItemModel() if (os.path.exists(languageP)): file = open(languageP, "r", newline="", encoding="utf8") languageReader = csv.reader(file) for row in languageReader: license = QStandardItem(row[0]) license.setToolTip(row[1]) model.appendRow(license) file.close() self.setModel(model) """ Allows us to set completers on the author roles. """ class author_delegate(QStyledItemDelegate): completerStrings = [] completerColumn = 0 languageColumn = 0 def __init__(self, parent=None): super(QStyledItemDelegate, self).__init__(parent) def setCompleterData(self, completerStrings=[str()], completerColumn=0): self.completerStrings = completerStrings self.completerColumn = completerColumn def setLanguageData(self, languageColumn=0): self.languageColumn = languageColumn def createEditor(self, parent, option, index): if index.column() != self.languageColumn: editor = QLineEdit(parent) else: editor = QComboBox(parent) editor.addItem("") for i in range(2, 356): if QLocale(i, QLocale.AnyScript, QLocale.AnyCountry) is not None: languagecode = QLocale(i, QLocale.AnyScript, QLocale.AnyCountry).name().split("_")[0] if languagecode != "C": editor.addItem(languagecode) editor.model().sort(0) if index.column() == self.completerColumn: editor.setCompleter(QCompleter(self.completerStrings)) editor.completer().setCaseSensitivity(False) return editor """ A comic project metadata editing dialog that can take our config diactionary and set all the relevant information. To help our user, the dialog loads up lists of keywords to populate several autocompletion methods. """ class comic_meta_data_editor(QDialog): configGroup = "ComicsProjectManagementTools" # Translatable genre dictionary that has it's translated entries added to the genrelist and from which the untranslated items are taken. acbfGenreList = {"science_fiction": str(i18n("Science Fiction")), "fantasy": str(i18n("Fantasy")), "adventure": str(i18n("Adventure")), "horror": str(i18n("Horror")), "mystery": str(i18n("Mystery")), "crime": str(i18n("Crime")), "military": str(i18n("Military")), "real_life": str(i18n("Real Life")), "superhero": str(i18n("Superhero")), "humor": str(i18n("Humor")), "western": str(i18n("Western")), "manga": str(i18n("Manga")), "politics": str(i18n("Politics")), "caricature": str(i18n("Caricature")), "sports": str(i18n("Sports")), "history": str(i18n("History")), "biography": str(i18n("Biography")), "education": str(i18n("Education")), "computer": str(i18n("Computer")), "religion": str(i18n("Religion")), "romance": str(i18n("Romance")), "children": str(i18n("Children")), "non-fiction": str(i18n("Non Fiction")), "adult": str(i18n("Adult")), "alternative": str(i18n("Alternative")), "artbook": str(i18n("Artbook")), "other": str(i18n("Other"))} acbfAuthorRolesList = {"Writer": str(i18n("Writer")), "Adapter": str(i18n("Adapter")), "Artist": str(i18n("Artist")), "Penciller": str(i18n("Penciller")), "Inker": str(i18n("Inker")), "Colorist": str(i18n("Colorist")), "Letterer": str(i18n("Letterer")), "Cover Artist": str(i18n("Cover Artist")), "Photographer": str(i18n("Photographer")), "Editor": str(i18n("Editor")), "Assistant Editor": str(i18n("Assistant Editor")), "Designer": str(i18n("Designer")), "Translator": str(i18n("Translator")), "Other": str(i18n("Other"))} def __init__(self): super().__init__() # Get the keys for the autocompletion. self.genreKeysList = [] self.characterKeysList = [] self.ratingKeysList = {} self.formatKeysList = [] self.otherKeysList = [] self.authorRoleList = [] for g in self.acbfGenreList.values(): self.genreKeysList.append(g) for r in self.acbfAuthorRolesList.values(): self.authorRoleList.append(r) mainP = Path(os.path.abspath(__file__)).parent self.get_auto_completion_keys(mainP) extraKeyP = Path(QDir.homePath()) / Application.readSetting(self.configGroup, "extraKeysLocation", str()) self.get_auto_completion_keys(extraKeyP) # Setup the dialog. self.setLayout(QVBoxLayout()) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.setWindowTitle(i18n("Comic Metadata")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.layout().addWidget(buttons) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) # Title, concept, summary, genre, characters, format, rating, language, series, other keywords metadataPage = QWidget() mformLayout = QFormLayout() metadataPage.setLayout(mformLayout) self.lnTitle = QLineEdit() self.lnTitle.setToolTip(i18n("The proper title of the comic.")) self.teSummary = QPlainTextEdit() self.teSummary.setToolTip(i18n("What will you tell others to entice them to read your comic?")) self.lnGenre = QLineEdit() genreCompletion = multi_entry_completer() genreCompletion.setModel(QStringListModel(self.genreKeysList)) self.lnGenre.setCompleter(genreCompletion) genreCompletion.setCaseSensitivity(False) - self.lnGenre.setToolTip(i18n("The genre of the work. Prefilled values are from the ACBF, but you can fill in your own. Separate genres with commas. Try to limit the amount to about two or three")) + self.lnGenre.setToolTip(i18n("The genre of the work. Prefilled values are from the ACBF, but you can fill in your own. Separate genres with commas. Try to limit the amount to about two or three.")) self.lnCharacters = QLineEdit() characterCompletion = multi_entry_completer() characterCompletion.setModel(QStringListModel(self.characterKeysList)) characterCompletion.setCaseSensitivity(False) characterCompletion.setFilterMode(Qt.MatchContains) # So that if there is a list of names with last names, people can type in a last name. self.lnCharacters.setCompleter(characterCompletion) self.lnCharacters.setToolTip(i18n("The names of the characters that this comic revolves around. Comma-separated.")) self.lnFormat = QLineEdit() formatCompletion = multi_entry_completer() formatCompletion.setModel(QStringListModel(self.formatKeysList)) formatCompletion.setCaseSensitivity(False) self.lnFormat.setCompleter(formatCompletion) ratingLayout = QHBoxLayout() self.cmbRatingSystem = QComboBox() self.cmbRatingSystem.addItems(self.ratingKeysList.keys()) self.cmbRatingSystem.setEditable(True) self.cmbRating = QComboBox() self.cmbRating.setEditable(True) self.cmbRatingSystem.currentIndexChanged.connect(self.slot_refill_ratings) ratingLayout.addWidget(self.cmbRatingSystem) ratingLayout.addWidget(self.cmbRating) self.lnSeriesName = QLineEdit() self.lnSeriesName.setToolTip(i18n("If this is part of a series, enter the name of the series and the number.")) self.spnSeriesNumber = QSpinBox() - self.spnSeriesNumber.setPrefix("No. ") + self.spnSeriesNumber.setPrefix(i18n("No. ")) self.spnSeriesVol = QSpinBox() - self.spnSeriesVol.setPrefix("Vol. ") + self.spnSeriesVol.setPrefix(i18n("Vol. ")) seriesLayout = QHBoxLayout() seriesLayout.addWidget(self.lnSeriesName) seriesLayout.addWidget(self.spnSeriesVol) seriesLayout.addWidget(self.spnSeriesNumber) otherCompletion = multi_entry_completer() otherCompletion.setModel(QStringListModel(self.otherKeysList)) otherCompletion.setCaseSensitivity(False) otherCompletion.setFilterMode(Qt.MatchContains) self.lnOtherKeywords = QLineEdit() self.lnOtherKeywords.setCompleter(otherCompletion) - self.lnOtherKeywords.setToolTip(i18n("Other keywords that don't fit in the previously mentioned sets. As always, comma-separated")) + self.lnOtherKeywords.setToolTip(i18n("Other keywords that do not fit in the previously mentioned sets. As always, comma-separated.")) self.cmbLanguage = language_combo_box() self.cmbCountry = country_combo_box() self.cmbLanguage.currentIndexChanged.connect(self.slot_update_countries) self.cmbReadingMode = QComboBox() self.cmbReadingMode.addItem(i18n("Left to Right")) self.cmbReadingMode.addItem(i18n("Right to Left")) self.cmbCoverPage = QComboBox() - self.cmbCoverPage.setToolTip(i18n("Which page is the cover page? This will be empty if there's no pages.")) + self.cmbCoverPage.setToolTip(i18n("Which page is the cover page? This will be empty if there are no pages.")) mformLayout.addRow(i18n("Title:"), self.lnTitle) - mformLayout.addRow(i18n("Cover Page:"), self.cmbCoverPage) + mformLayout.addRow(i18n("Cover page:"), self.cmbCoverPage) mformLayout.addRow(i18n("Summary:"), self.teSummary) mformLayout.addRow(i18n("Language:"), self.cmbLanguage) mformLayout.addRow("", self.cmbCountry) - mformLayout.addRow(i18n("Reading Direction:"), self.cmbReadingMode) + mformLayout.addRow(i18n("Reading direction:"), self.cmbReadingMode) mformLayout.addRow(i18n("Genre:"), self.lnGenre) mformLayout.addRow(i18n("Characters:"), self.lnCharacters) mformLayout.addRow(i18n("Format:"), self.lnFormat) mformLayout.addRow(i18n("Rating:"), ratingLayout) mformLayout.addRow(i18n("Series:"), seriesLayout) mformLayout.addRow(i18n("Other:"), self.lnOtherKeywords) mainWidget.addTab(metadataPage, i18n("Work")) # The page for the authors. authorPage = QWidget() authorPage.setLayout(QVBoxLayout()) - explanation = QLabel(i18n("The following is a table of the authors that contributed to this comic. You can set their nickname, proper names (first, middle, last), Role (Penciller, Inker, etc), email and homepage.")) + explanation = QLabel(i18n("The following is a table of the authors that contributed to this comic. You can set their nickname, proper names (first, middle, last), role (penciller, inker, etc), email and homepage.")) explanation.setWordWrap(True) self.authorModel = QStandardItemModel(0, 8) labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Role"), i18n("Email"), i18n("Homepage"), i18n("Language")] self.authorModel.setHorizontalHeaderLabels(labels) self.authorTable = QTableView() self.authorTable.setModel(self.authorModel) self.authorTable.verticalHeader().setDragEnabled(True) self.authorTable.verticalHeader().setDropIndicatorShown(True) self.authorTable.verticalHeader().setSectionsMovable(True) self.authorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual) delegate = author_delegate() delegate.setCompleterData(self.authorRoleList, 4) delegate.setLanguageData(len(labels) - 1) self.authorTable.setItemDelegate(delegate) author_button_layout = QWidget() author_button_layout.setLayout(QHBoxLayout()) btn_add_author = QPushButton(i18n("Add Author")) btn_add_author.clicked.connect(self.slot_add_author) btn_remove_author = QPushButton(i18n("Remove Author")) btn_remove_author.clicked.connect(self.slot_remove_author) author_button_layout.layout().addWidget(btn_add_author) author_button_layout.layout().addWidget(btn_remove_author) authorPage.layout().addWidget(explanation) authorPage.layout().addWidget(self.authorTable) authorPage.layout().addWidget(author_button_layout) mainWidget.addTab(authorPage, i18n("Authors")) # The page with publisher information. publisherPage = QWidget() publisherLayout = QFormLayout() publisherPage.setLayout(publisherLayout) self.publisherName = QLineEdit() self.publisherName.setToolTip(i18n("The name of the company, group or person who is responsible for the final version the reader gets.")) publishDateLayout = QHBoxLayout() self.publishDate = QDateEdit() self.publishDate.setDisplayFormat(QLocale().system().dateFormat()) currentDate = QPushButton(i18n("Set Today")) currentDate.setToolTip(i18n("Sets the publish date to the current date.")) currentDate.clicked.connect(self.slot_set_date) publishDateLayout.addWidget(self.publishDate) publishDateLayout.addWidget(currentDate) self.publishCity = QLineEdit() self.publishCity.setToolTip(i18n("Traditional publishers are always mentioned in source with the city they are located.")) self.isbn = QLineEdit() self.license = license_combo_box() # Maybe ought to make this a QLineEdit... self.license.setEditable(True) self.license.completer().setCompletionMode(QCompleter.PopupCompletion) dataBaseReference = QVBoxLayout() self.ln_database_name = QLineEdit() - self.ln_database_name.setToolTip(i18n("If there's an entry in a comics data base, that should be added here. It is unlikely to be a factor for comics from scratch, but useful when doing a conversion.")) + self.ln_database_name.setToolTip(i18n("If there is an entry in a comics data base, that should be added here. It is unlikely to be a factor for comics from scratch, but useful when doing a conversion.")) self.cmb_entry_type = QComboBox() self.cmb_entry_type.addItems(["IssueID", "SeriesID", "URL"]) self.cmb_entry_type.setEditable(True) self.ln_database_entry = QLineEdit() dbHorizontal = QHBoxLayout() dbHorizontal.addWidget(self.ln_database_name) dbHorizontal.addWidget(self.cmb_entry_type) dataBaseReference.addLayout(dbHorizontal) dataBaseReference.addWidget(self.ln_database_entry) publisherLayout.addRow(i18n("Name:"), self.publisherName) publisherLayout.addRow(i18n("City:"), self.publishCity) publisherLayout.addRow(i18n("Date:"), publishDateLayout) publisherLayout.addRow(i18n("ISBN:"), self.isbn) publisherLayout.addRow(i18n("License:"), self.license) publisherLayout.addRow(i18n("Database:"), dataBaseReference) mainWidget.addTab(publisherPage, i18n("Publisher")) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] for i in range(self.authorTable.verticalHeader().count()): headerLabelList.append(str(i)) for i in range(self.authorTable.verticalHeader().count()): logicalI = self.authorTable.verticalHeader().logicalIndex(i) headerLabelList[logicalI] = str(i + 1) self.authorModel.setVerticalHeaderLabels(headerLabelList) """ Set the publish date to the current date. """ def slot_set_date(self): self.publishDate.setDate(QDate().currentDate()) def slot_update_countries(self): code = self.cmbLanguage.codeForCurrentEntry() self.cmbCountry.set_country_for_locale(code) """ Append keys to autocompletion lists from the directory mainP. """ def get_auto_completion_keys(self, mainP=Path()): genre = Path(mainP / "key_genre") characters = Path(mainP / "key_characters") rating = Path(mainP / "key_rating") format = Path(mainP / "key_format") keywords = Path(mainP / "key_other") authorRole = Path(mainP / "key_author_roles") if genre.exists(): for t in list(genre.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.genreKeysList: self.genreKeysList.append(str(l).strip("\n")) file.close() if characters.exists(): for t in list(characters.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.characterKeysList: self.characterKeysList.append(str(l).strip("\n")) file.close() if format.exists(): for t in list(format.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.formatKeysList: self.formatKeysList.append(str(l).strip("\n")) file.close() if rating.exists(): for t in list(rating.glob('**/*.csv')): file = open(str(t), "r", newline="", encoding="utf-8") ratings = csv.reader(file) title = os.path.basename(str(t)) r = 0 for row in ratings: listItem = [] if r is 0: title = row[1] else: listItem = self.ratingKeysList[title] item = [] item.append(row[0]) item.append(row[1]) listItem.append(item) self.ratingKeysList[title] = listItem r += 1 file.close() if keywords.exists(): for t in list(keywords.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.otherKeysList: self.otherKeysList.append(str(l).strip("\n")) file.close() if authorRole.exists(): for t in list(authorRole.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.authorRoleList: self.authorRoleList.append(str(l).strip("\n")) file.close() """ Refill the ratings box. This is called whenever the rating system changes. """ def slot_refill_ratings(self): if self.cmbRatingSystem.currentText() in self.ratingKeysList.keys(): self.cmbRating.clear() model = QStandardItemModel() for i in self.ratingKeysList[self.cmbRatingSystem.currentText()]: item = QStandardItem() item.setText(i[0]) item.setToolTip(i[1]) model.appendRow(item) self.cmbRating.setModel(model) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # role listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage language = QLocale.system().name().split("_")[0] if language == "C": language = "en" listItems.append(QStandardItem(language)) # Language self.authorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.authorModel.removeRow(self.authorTable.currentIndex().row()) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "title" in config.keys(): self.lnTitle.setText(config["title"]) self.teSummary.clear() if "pages" in config.keys(): self.cmbCoverPage.clear() for page in config["pages"]: self.cmbCoverPage.addItem(page) if "cover" in config.keys(): if config["cover"] in config["pages"]: self.cmbCoverPage.setCurrentText(config["cover"]) if "summary" in config.keys(): self.teSummary.appendPlainText(config["summary"]) if "genre" in config.keys(): genreList = [] genreListConf = config["genre"] totalMatch = 100 if isinstance(config["genre"], dict): genreListConf = config["genre"].keys() totalMatch = 0 for genre in genreListConf: genreKey = genre if genre in self.acbfGenreList: genreKey = self.acbfGenreList[genre] if isinstance(config["genre"], dict): genreValue = config["genre"][genre] if genreValue > 0: genreKey = str(genreKey + "(" + str(genreValue) + ")") genreList.append(genreKey) self.lnGenre.setText(", ".join(genreList)) if "characters" in config.keys(): self.lnCharacters.setText(", ".join(config["characters"])) if "format" in config.keys(): self.lnFormat.setText(", ".join(config["format"])) if "rating" in config.keys(): self.cmbRating.setCurrentText(config["rating"]) else: self.cmbRating.setCurrentText("") if "ratingSystem" in config.keys(): self.cmbRatingSystem.setCurrentText(config["ratingSystem"]) else: self.cmbRatingSystem.setCurrentText("") if "otherKeywords" in config.keys(): self.lnOtherKeywords.setText(", ".join(config["otherKeywords"])) if "seriesName" in config.keys(): self.lnSeriesName.setText(config["seriesName"]) if "seriesVolume" in config.keys(): self.spnSeriesVol.setValue(config["seriesVolume"]) if "seriesNumber" in config.keys(): self.spnSeriesNumber.setValue(config["seriesNumber"]) if "language" in config.keys(): code = config["language"] if "_" in code: self.cmbLanguage.setEntryToCode(code.split("_")[0]) self.cmbCountry.setEntryToCode(code.split("_")[-1]) else: self.cmbLanguage.setEntryToCode(code) if "readingDirection" in config.keys(): if config["readingDirection"] is "leftToRight": self.cmbReadingMode.setCurrentIndex(int(Qt.LeftToRight)) else: self.cmbReadingMode.setCurrentIndex(int(Qt.RightToLeft)) else: self.cmbReadingMode.setCurrentIndex(QLocale(self.cmbLanguage.codeForCurrentEntry()).textDirection()) if "publisherName" in config.keys(): self.publisherName.setText(config["publisherName"]) if "publisherCity" in config.keys(): self.publishCity.setText(config["publisherCity"]) if "publishingDate" in config.keys(): self.publishDate.setDate(QDate.fromString(config["publishingDate"], Qt.ISODate)) if "isbn-number" in config.keys(): self.isbn.setText(config["isbn-number"]) if "license" in config.keys(): self.license.setCurrentText(config["license"]) else: self.license.setCurrentText("") # I would like to keep it ambiguous whether the artist has thought about the license or not. if "authorList" in config.keys(): authorList = config["authorList"] for i in range(len(authorList)): author = authorList[i] if len(author.keys()) > 0: listItems = [] listItems = [] listItems.append(QStandardItem(author.get("nickname", ""))) listItems.append(QStandardItem(author.get("first-name", ""))) listItems.append(QStandardItem(author.get("initials", ""))) listItems.append(QStandardItem(author.get("last-name", ""))) role = author.get("role", "") if role in self.acbfAuthorRolesList.keys(): role = self.acbfAuthorRolesList[role] listItems.append(QStandardItem(role)) listItems.append(QStandardItem(author.get("email", ""))) listItems.append(QStandardItem(author.get("homepage", ""))) listItems.append(QStandardItem(author.get("language", ""))) self.authorModel.appendRow(listItems) else: self.slot_add_author() dbRef = config.get("databaseReference", {}) self.ln_database_name.setText(dbRef.get("name", "")) self.ln_database_entry.setText(dbRef.get("entry", "")) stringCmbEntryType = self.cmb_entry_type.itemText(0) self.cmb_entry_type.setCurrentText(dbRef.get("type", stringCmbEntryType)) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): text = self.lnTitle.text() if len(text) > 0 and text.isspace() is False: config["title"] = text elif "title" in config.keys(): config.pop("title") config["cover"] = self.cmbCoverPage.currentText() listkeys = self.lnGenre.text() if len(listkeys) > 0 and listkeys.isspace() is False: preSplit = self.lnGenre.text().split(",") genreMatcher = re.compile(r'\((\d+)\)') genreList = {} totalValue = 0 for key in preSplit: m = genreMatcher.search(key) if m: genre = str(genreMatcher.sub("", key)).strip() match = int(m.group()[:-1][1:]) else: genre = key.strip() match = 0 if genre in self.acbfGenreList.values(): i = list(self.acbfGenreList.values()).index(genre) genreList[list(self.acbfGenreList.keys())[i]] = match else: genreList[genre] = match totalValue += match # Normalize the values: for key in genreList.keys(): if genreList[key] > 0: genreList[key] = round(genreList[key] / totalValue * 100) config["genre"] = genreList elif "genre" in config.keys(): config.pop("genre") listkeys = self.lnCharacters.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["characters"] = self.lnCharacters.text().split(", ") elif "characters" in config.keys(): config.pop("characters") listkeys = self.lnFormat.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["format"] = self.lnFormat.text().split(", ") elif "format" in config.keys(): config.pop("format") config["ratingSystem"] = self.cmbRatingSystem.currentText() config["rating"] = self.cmbRating.currentText() listkeys = self.lnOtherKeywords.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["otherKeywords"] = self.lnOtherKeywords.text().split(", ") elif "otherKeywords" in config.keys(): config.pop("otherKeywords") text = self.teSummary.toPlainText() if len(text) > 0 and text.isspace() is False: config["summary"] = text elif "summary" in config.keys(): config.pop("summary") if len(self.lnSeriesName.text()) > 0: config["seriesName"] = self.lnSeriesName.text() config["seriesNumber"] = self.spnSeriesNumber.value() if self.spnSeriesVol.value() > 0: config["seriesVolume"] = self.spnSeriesVol.value() config["language"] = str(self.cmbLanguage.codeForCurrentEntry()+"_"+self.cmbCountry.codeForCurrentEntry()) if self.cmbReadingMode.currentIndex() is int(Qt.LeftToRight): config["readingDirection"] = "leftToRight" else: config["readingDirection"] = "rightToLeft" authorList = [] for row in range(self.authorTable.verticalHeader().count()): logicalIndex = self.authorTable.verticalHeader().logicalIndex(row) listEntries = ["nickname", "first-name", "initials", "last-name", "role", "email", "homepage", "language"] author = {} for i in range(len(listEntries)): entry = self.authorModel.data(self.authorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: if listEntries[i] == "role": if entry in self.acbfAuthorRolesList.values(): entryI = list(self.acbfAuthorRolesList.values()).index(entry) entry = list(self.acbfAuthorRolesList.keys())[entryI] author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["authorList"] = authorList config["publisherName"] = self.publisherName.text() config["publisherCity"] = self.publishCity.text() config["publishingDate"] = self.publishDate.date().toString(Qt.ISODate) config["isbn-number"] = self.isbn.text() config["license"] = self.license.currentText() if self.ln_database_name.text().isalnum() and self.ln_database_entry.text().isalnum(): dbRef = {} dbRef["name"] = self.ln_database_name.text() dbRef["entry"] = self.ln_database_entry.text() dbRef["type"] = self.cmb_entry_type.currentText() config["databaseReference"] = dbRef return config diff --git a/plugins/python/comics_project_management_tools/comics_project_manager_docker.py b/plugins/python/comics_project_management_tools/comics_project_manager_docker.py index 353328dd5a..5eec7e0e92 100644 --- a/plugins/python/comics_project_management_tools/comics_project_manager_docker.py +++ b/plugins/python/comics_project_management_tools/comics_project_manager_docker.py @@ -1,922 +1,922 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ This is a docker that helps you organise your comics project. """ import sys import json import os import zipfile # quick reading of documents import shutil import enum from math import floor import xml.etree.ElementTree as ET from PyQt5.QtCore import QElapsedTimer, QSize, Qt, QRect from PyQt5.QtGui import QStandardItem, QStandardItemModel, QImage, QIcon, QPixmap, QFontMetrics, QPainter, QPalette, QFont from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QListView, QToolButton, QMenu, QAction, QPushButton, QSpacerItem, QSizePolicy, QWidget, QAbstractItemView, QProgressDialog, QDialog, QFileDialog, QDialogButtonBox, qApp, QSplitter, QSlider, QLabel, QStyledItemDelegate, QStyle, QMessageBox import math from krita import * from . import comics_metadata_dialog, comics_exporter, comics_export_dialog, comics_project_setup_wizard, comics_template_dialog, comics_project_settings_dialog, comics_project_page_viewer, comics_project_translation_scraper """ A very simple class so we can have a label that is single line, but doesn't force the widget size to be bigger. This is used by the project name. """ class Elided_Text_Label(QLabel): mainText = str() def __init__(self, parent=None): super(QLabel, self).__init__(parent) self.setMinimumWidth(self.fontMetrics().width("...")) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) def setMainText(self, text=str()): self.mainText = text self.elideText() def elideText(self): self.setText(self.fontMetrics().elidedText(self.mainText, Qt.ElideRight, self.width())) def resizeEvent(self, event): self.elideText() class CPE(enum.IntEnum): TITLE = Qt.DisplayRole URL = Qt.UserRole + 1 KEYWORDS = Qt.UserRole+2 DESCRIPTION = Qt.UserRole+3 LASTEDIT = Qt.UserRole+4 EDITOR = Qt.UserRole+5 IMAGE = Qt.DecorationRole class comic_page_delegate(QStyledItemDelegate): def __init__(self, parent=None): super(QStyledItemDelegate, self).__init__(parent) def paint(self, painter, option, index): if (index.isValid() == False): return painter.save() painter.setOpacity(0.6) if(option.state & QStyle.State_Selected): painter.fillRect(option.rect, option.palette.highlight()) if (option.state & QStyle.State_MouseOver): painter.setOpacity(0.25) painter.fillRect(option.rect, option.palette.highlight()) painter.setOpacity(1.0) painter.setFont(option.font) metrics = QFontMetrics(option.font) regular = QFont(option.font) italics = QFont(option.font) italics.setItalic(True) icon = QIcon(index.data(CPE.IMAGE)) rect = option.rect margin = 4 decoratonSize = QSize(option.decorationSize) imageSize = icon.actualSize(option.decorationSize) leftSideThumbnail = (decoratonSize.width()-imageSize.width())/2 if (rect.width() < decoratonSize.width()): leftSideThumbnail = max(0, (rect.width()-imageSize.width())/2) topSizeThumbnail = ((rect.height()-imageSize.height())/2)+rect.top() painter.drawImage(QRect(leftSideThumbnail, topSizeThumbnail, imageSize.width(), imageSize.height()), icon.pixmap(imageSize).toImage()) labelWidth = rect.width()-decoratonSize.width()-(margin*3) if (decoratonSize.width()+(margin*2)< rect.width()): textRect = QRect(decoratonSize.width()+margin, margin+rect.top(), labelWidth, metrics.height()) textTitle = metrics.elidedText(str(index.row()+1)+". "+index.data(CPE.TITLE), Qt.ElideRight, labelWidth) painter.drawText(textRect, Qt.TextWordWrap, textTitle) if rect.height()/(metrics.lineSpacing()+margin) > 5 or index.data(CPE.KEYWORDS) is not None: painter.setOpacity(0.6) textRect = QRect(textRect.left(), textRect.bottom()+margin, labelWidth, metrics.height()) if textRect.bottom() < rect.bottom(): textKeyWords = index.data(CPE.KEYWORDS) if textKeyWords == None: textKeyWords = i18n("No keywords") painter.setOpacity(0.3) painter.setFont(italics) textKeyWords = metrics.elidedText(textKeyWords, Qt.ElideRight, labelWidth) painter.drawText(textRect, Qt.TextWordWrap, textKeyWords) painter.setFont(regular) if rect.height()/(metrics.lineSpacing()+margin) > 3: painter.setOpacity(0.6) textRect = QRect(textRect.left(), textRect.bottom()+margin, labelWidth, metrics.height()) if textRect.bottom()+metrics.height() < rect.bottom(): textLastEdit = index.data(CPE.LASTEDIT) if textLastEdit is None: textLastEdit = i18n("No last edit timestamp") if index.data(CPE.EDITOR) is not None: textLastEdit += " - " + index.data(CPE.EDITOR) if (index.data(CPE.LASTEDIT) is None) and (index.data(CPE.EDITOR) is None): painter.setOpacity(0.3) painter.setFont(italics) textLastEdit = metrics.elidedText(textLastEdit, Qt.ElideRight, labelWidth) painter.drawText(textRect, Qt.TextWordWrap, textLastEdit) painter.setFont(regular) descRect = QRect(textRect.left(), textRect.bottom()+margin, labelWidth, (rect.bottom()-margin) - (textRect.bottom()+margin)) if textRect.bottom()+metrics.height() < rect.bottom(): textRect.setBottom(textRect.bottom()+(margin/2)) textRect.setLeft(textRect.left()-(margin/2)) painter.setOpacity(0.4) painter.drawLine(textRect.bottomLeft(), textRect.bottomRight()) painter.setOpacity(1.0) textDescription = index.data(CPE.DESCRIPTION) if textDescription is None: textDescription = i18n("No description") painter.setOpacity(0.3) painter.setFont(italics) linesTotal = floor(descRect.height()/metrics.lineSpacing()) if linesTotal == 1: textDescription = metrics.elidedText(textDescription, Qt.ElideRight, labelWidth) painter.drawText(descRect, Qt.TextWordWrap, textDescription) else: descRect.setHeight(linesTotal*metrics.lineSpacing()) totalDescHeight = metrics.boundingRect(descRect, Qt.TextWordWrap, textDescription).height() if totalDescHeight>descRect.height(): if totalDescHeight-metrics.lineSpacing()>descRect.height(): painter.setOpacity(0.5) painter.drawText(descRect, Qt.TextWordWrap, textDescription) descRect.setHeight((linesTotal-1)*metrics.lineSpacing()) painter.drawText(descRect, Qt.TextWordWrap, textDescription) descRect.setHeight((linesTotal-2)*metrics.lineSpacing()) painter.drawText(descRect, Qt.TextWordWrap, textDescription) else: painter.setOpacity(0.75) painter.drawText(descRect, Qt.TextWordWrap, textDescription) descRect.setHeight((linesTotal-1)*metrics.lineSpacing()) painter.drawText(descRect, Qt.TextWordWrap, textDescription) else: painter.drawText(descRect, Qt.TextWordWrap, textDescription) painter.setFont(regular) painter.restore() """ This is a Krita docker called 'Comics Manager'. It allows people to create comics project files, load those files, add pages, remove pages, move pages, manage the metadata, and finally export the result. The logic behind this docker is that it is very easy to get lost in a comics project due to the massive amount of files. By having a docker that gives the user quick access to the pages and also allows them to do all of the meta-stuff, like meta data, but also reordering the pages, the chaos of managing the project should take up less time, and more time can be focussed on actual writing and drawing. """ class comics_project_manager_docker(DockWidget): setupDictionary = {} stringName = i18n("Comics Manager") projecturl = None def __init__(self): super().__init__() self.setWindowTitle(self.stringName) # Setup layout: base = QHBoxLayout() widget = QWidget() widget.setLayout(base) baseLayout = QSplitter() base.addWidget(baseLayout) self.setWidget(widget) buttonLayout = QVBoxLayout() buttonBox = QWidget() buttonBox.setLayout(buttonLayout) baseLayout.addWidget(buttonBox) # Comic page list and pages model self.comicPageList = QListView() self.comicPageList.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.comicPageList.setDragEnabled(True) self.comicPageList.setDragDropMode(QAbstractItemView.InternalMove) self.comicPageList.setDefaultDropAction(Qt.MoveAction) self.comicPageList.setAcceptDrops(True) self.comicPageList.setItemDelegate(comic_page_delegate()) self.pagesModel = QStandardItemModel() self.comicPageList.doubleClicked.connect(self.slot_open_page) self.comicPageList.setIconSize(QSize(128, 128)) # self.comicPageList.itemDelegate().closeEditor.connect(self.slot_write_description) self.pagesModel.layoutChanged.connect(self.slot_write_config) self.pagesModel.rowsInserted.connect(self.slot_write_config) self.pagesModel.rowsRemoved.connect(self.slot_write_config) self.pagesModel.rowsMoved.connect(self.slot_write_config) self.comicPageList.setModel(self.pagesModel) pageBox = QWidget() pageBox.setLayout(QVBoxLayout()) zoomSlider = QSlider(Qt.Horizontal, None) zoomSlider.setRange(1, 8) zoomSlider.setValue(4) zoomSlider.setTickInterval(1) zoomSlider.setMinimumWidth(10) zoomSlider.valueChanged.connect(self.slot_scale_thumbnails) self.projectName = Elided_Text_Label() pageBox.layout().addWidget(self.projectName) pageBox.layout().addWidget(zoomSlider) pageBox.layout().addWidget(self.comicPageList) baseLayout.addWidget(pageBox) self.btn_project = QToolButton() self.btn_project.setPopupMode(QToolButton.MenuButtonPopup) self.btn_project.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) menu_project = QMenu() self.action_new_project = QAction(i18n("New Project"), self) self.action_new_project.triggered.connect(self.slot_new_project) self.action_load_project = QAction(i18n("Open Project"), self) self.action_load_project.triggered.connect(self.slot_open_config) menu_project.addAction(self.action_new_project) menu_project.addAction(self.action_load_project) self.btn_project.setMenu(menu_project) self.btn_project.setDefaultAction(self.action_load_project) buttonLayout.addWidget(self.btn_project) # Settings dropdown with actions for the different settings menus. self.btn_settings = QToolButton() self.btn_settings.setPopupMode(QToolButton.MenuButtonPopup) self.btn_settings.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_edit_project_settings = QAction(i18n("Project Settings"), self) self.action_edit_project_settings.triggered.connect(self.slot_edit_project_settings) self.action_edit_meta_data = QAction(i18n("Meta Data"), self) self.action_edit_meta_data.triggered.connect(self.slot_edit_meta_data) self.action_edit_export_settings = QAction(i18n("Export Settings"), self) self.action_edit_export_settings.triggered.connect(self.slot_edit_export_settings) menu_settings = QMenu() menu_settings.addAction(self.action_edit_project_settings) menu_settings.addAction(self.action_edit_meta_data) menu_settings.addAction(self.action_edit_export_settings) self.btn_settings.setDefaultAction(self.action_edit_project_settings) self.btn_settings.setMenu(menu_settings) buttonLayout.addWidget(self.btn_settings) self.btn_settings.setDisabled(True) # Add page drop down with different page actions. self.btn_add_page = QToolButton() self.btn_add_page.setPopupMode(QToolButton.MenuButtonPopup) self.btn_add_page.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_add_page = QAction(i18n("Add Page"), self) self.action_add_page.triggered.connect(self.slot_add_new_page_single) - self.action_add_template = QAction(i18n("Add Page From Template"), self) + self.action_add_template = QAction(i18n("Add Page from Template"), self) self.action_add_template.triggered.connect(self.slot_add_new_page_from_template) self.action_add_existing = QAction(i18n("Add Existing Pages"), self) self.action_add_existing.triggered.connect(self.slot_add_page_from_url) self.action_remove_selected_page = QAction(i18n("Remove Page"), self) self.action_remove_selected_page.triggered.connect(self.slot_remove_selected_page) self.action_resize_all_pages = QAction(i18n("Batch Resize"), self) self.action_resize_all_pages.triggered.connect(self.slot_batch_resize) self.btn_add_page.setDefaultAction(self.action_add_page) self.action_show_page_viewer = QAction(i18n("View Page In Window"), self) self.action_show_page_viewer.triggered.connect(self.slot_show_page_viewer) self.action_scrape_authors = QAction(i18n("Scrape Author Info"), self) - self.action_scrape_authors.setToolTip(i18n("Search for author information in documents and add it to the author list. This doesn't check for duplicates.")) + self.action_scrape_authors.setToolTip(i18n("Search for author information in documents and add it to the author list. This does not check for duplicates.")) self.action_scrape_authors.triggered.connect(self.slot_scrape_author_list) self.action_scrape_translations = QAction(i18n("Scrape Text for Translation"), self) self.action_scrape_translations.triggered.connect(self.slot_scrape_translations) actionList = [] menu_page = QMenu() actionList.append(self.action_add_page) actionList.append(self.action_add_template) actionList.append(self.action_add_existing) actionList.append(self.action_remove_selected_page) actionList.append(self.action_resize_all_pages) actionList.append(self.action_show_page_viewer) actionList.append(self.action_scrape_authors) actionList.append(self.action_scrape_translations) menu_page.addActions(actionList) self.btn_add_page.setMenu(menu_page) buttonLayout.addWidget(self.btn_add_page) self.btn_add_page.setDisabled(True) self.comicPageList.setContextMenuPolicy(Qt.ActionsContextMenu) self.comicPageList.addActions(actionList) # Export button that... exports. self.btn_export = QPushButton(i18n("Export Comic")) self.btn_export.clicked.connect(self.slot_export) buttonLayout.addWidget(self.btn_export) self.btn_export.setDisabled(True) self.btn_project_url = QPushButton(i18n("Copy Location")) self.btn_project_url.setToolTip(i18n("Copies the path of the project to the clipboard. Useful for quickly copying to a file manager or the like.")) self.btn_project_url.clicked.connect(self.slot_copy_project_url) self.btn_project_url.setDisabled(True) buttonLayout.addWidget(self.btn_project_url) self.page_viewer_dialog = comics_project_page_viewer.comics_project_page_viewer() Application.notifier().imageSaved.connect(self.slot_check_for_page_update) buttonLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)) """ Open the config file and load the json file into a dictionary. """ def slot_open_config(self): - self.path_to_config = QFileDialog.getOpenFileName(caption=i18n("Please select the json comic config file."), filter=str(i18n("json files") + "(*.json)"))[0] + self.path_to_config = QFileDialog.getOpenFileName(caption=i18n("Please select the JSON comic config file."), filter=str(i18n("JSON files") + "(*.json)"))[0] if os.path.exists(self.path_to_config) is True: configFile = open(self.path_to_config, "r", newline="", encoding="utf-16") self.setupDictionary = json.load(configFile) self.projecturl = os.path.dirname(str(self.path_to_config)) configFile.close() self.load_config() """ Further config loading. """ def load_config(self): self.projectName.setMainText(text=str(self.setupDictionary["projectName"])) self.fill_pages() self.btn_settings.setEnabled(True) self.btn_add_page.setEnabled(True) self.btn_export.setEnabled(True) self.btn_project_url.setEnabled(True) """ Fill the pages model with the pages from the pages list. """ def fill_pages(self): self.loadingPages = True self.pagesModel.clear() pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] progress = QProgressDialog() progress.setMinimum(0) progress.setMaximum(len(pagesList)) - progress.setWindowTitle(i18n("Loading pages...")) + progress.setWindowTitle(i18n("Loading Pages...")) for url in pagesList: absurl = os.path.join(self.projecturl, url) if (os.path.exists(absurl)): #page = Application.openDocument(absurl) page = zipfile.ZipFile(absurl, "r") thumbnail = QImage.fromData(page.read("preview.png")) pageItem = QStandardItem() dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) pageItem.setText(dataList[0].replace("_", " ")) pageItem.setDragEnabled(True) pageItem.setDropEnabled(False) pageItem.setEditable(False) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setData(dataList[1], role = CPE.DESCRIPTION) pageItem.setData(url, role = CPE.URL) pageItem.setData(dataList[2], role = CPE.KEYWORDS) pageItem.setData(dataList[3], role = CPE.LASTEDIT) pageItem.setData(dataList[4], role = CPE.EDITOR) pageItem.setToolTip(url) page.close() self.pagesModel.appendRow(pageItem) progress.setValue(progress.value() + 1) progress.setValue(len(pagesList)) self.loadingPages = False """ Function that is triggered by the zoomSlider Resizes the thumbnails. """ def slot_scale_thumbnails(self, multiplier=4): self.comicPageList.setIconSize(QSize(multiplier * 32, multiplier * 32)) """ Function that takes the documentinfo.xml and parses it for the title, subject and abstract tags, to get the title and description. @returns a stringlist with the name on 0 and the description on 1. """ def get_description_and_title(self, string): xmlDoc = ET.fromstring(string) calligra = str("{http://www.calligra.org/DTD/document-info}") name = "" if ET.iselement(xmlDoc[0].find(calligra + 'title')): name = xmlDoc[0].find(calligra + 'title').text if name is None: name = " " desc = "" if ET.iselement(xmlDoc[0].find(calligra + 'subject')): desc = xmlDoc[0].find(calligra + 'subject').text if desc is None or desc.isspace() or len(desc) < 1: if ET.iselement(xmlDoc[0].find(calligra + 'abstract')): desc = xmlDoc[0].find(calligra + 'abstract').text if desc is not None: if desc.startswith(""): desc = desc[:-len("]]>")] keywords = "" if ET.iselement(xmlDoc[0].find(calligra + 'keyword')): keywords = xmlDoc[0].find(calligra + 'keyword').text date = "" if ET.iselement(xmlDoc[0].find(calligra + 'date')): date = xmlDoc[0].find(calligra + 'date').text author = [] if ET.iselement(xmlDoc[1].find(calligra + 'creator-first-name')): string = xmlDoc[1].find(calligra + 'creator-first-name').text if string is not None: author.append(string) if ET.iselement(xmlDoc[1].find(calligra + 'creator-last-name')): string = xmlDoc[1].find(calligra + 'creator-last-name').text if string is not None: author.append(string) if ET.iselement(xmlDoc[1].find(calligra + 'full-name')): string = xmlDoc[1].find(calligra + 'full-name').text if string is not None: author.append(string) return [name, desc, keywords, date, " ".join(author)] """ Scrapes authors from the author data in the document info and puts them into the author list. Doesn't check for duplicates. """ def slot_scrape_author_list(self): listOfAuthors = [] if "authorList" in self.setupDictionary.keys(): listOfAuthors = self.setupDictionary["authorList"] if "pages" in self.setupDictionary.keys(): for relurl in self.setupDictionary["pages"]: absurl = os.path.join(self.projecturl, relurl) page = zipfile.ZipFile(absurl, "r") xmlDoc = ET.fromstring(page.read("documentinfo.xml")) calligra = str("{http://www.calligra.org/DTD/document-info}") authorelem = xmlDoc.find(calligra + 'author') author = {} if ET.iselement(authorelem.find(calligra + 'full-name')): author["nickname"] = str(authorelem.find(calligra + 'full-name').text) if ET.iselement(authorelem.find(calligra + 'creator-first-name')): author["first-name"] = str(authorelem.find(calligra + 'creator-first-name').text) if ET.iselement(authorelem.find(calligra + 'initial')): author["initials"] = str(authorelem.find(calligra + 'initial').text) if ET.iselement(authorelem.find(calligra + 'creator-last-name')): author["last-name"] = str(authorelem.find(calligra + 'creator-last-name').text) if ET.iselement(authorelem.find(calligra + 'email')): author["email"] = str(authorelem.find(calligra + 'email').text) if ET.iselement(authorelem.find(calligra + 'contact')): contact = authorelem.find(calligra + 'contact') contactMode = contact.get("type") if contactMode == "email": author["email"] = str(contact.text) if contactMode == "homepage": author["homepage"] = str(contact.text) if ET.iselement(authorelem.find(calligra + 'position')): author["role"] = str(authorelem.find(calligra + 'position').text) listOfAuthors.append(author) page.close() self.setupDictionary["authorList"] = listOfAuthors """ Edit the general project settings like the project name, concept, pages location, export location, template location, metadata """ def slot_edit_project_settings(self): dialog = comics_project_settings_dialog.comics_project_details_editor(self.projecturl) dialog.setConfig(self.setupDictionary, self.projecturl) if dialog.exec_() == QDialog.Accepted: self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() self.projectName.setMainText(str(self.setupDictionary["projectName"])) """ This allows users to select existing pages and add them to the pages list. The pages are currently not copied to the pages folder. Useful for existing projects. """ def slot_add_page_from_url(self): # get the pages. urlList = QFileDialog.getOpenFileNames(caption=i18n("Which existing pages to add?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0] # get the existing pages list. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] # And add each url in the url list to the pages list and the model. for url in urlList: if self.projecturl not in urlList: newUrl = os.path.join(self.projecturl, self.setupDictionary["pagesLocation"], os.path.basename(url)) shutil.move(url, newUrl) url = newUrl relative = os.path.relpath(url, self.projecturl) if url not in pagesList: page = zipfile.ZipFile(url, "r") thumbnail = QImage.fromData(page.read("preview.png")) dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(dataList[0].replace("_", " ")) newPageItem.setData(dataList[1], role = CPE.DESCRIPTION) newPageItem.setData(relative, role = CPE.URL) newPageItem.setData(dataList[2], role = CPE.KEYWORDS) newPageItem.setData(dataList[3], role = CPE.LASTEDIT) newPageItem.setData(dataList[4], role = CPE.EDITOR) newPageItem.setToolTip(relative) page.close() self.pagesModel.appendRow(newPageItem) """ Remove the selected page from the list of pages. This does not remove it from disk(far too dangerous). """ def slot_remove_selected_page(self): index = self.comicPageList.currentIndex() self.pagesModel.removeRow(index.row()) """ This function adds a new page from the default template. If there's no default template, or the file does not exist, it will show the create/import template dialog. It will remember the selected item as the default template. """ def slot_add_new_page_single(self): templateUrl = "templatepage" templateExists = False if "singlePageTemplate" in self.setupDictionary.keys(): templateUrl = self.setupDictionary["singlePageTemplate"] if os.path.exists(os.path.join(self.projecturl, templateUrl)): templateExists = True if templateExists is False: if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.setupDictionary["singlePageTemplate"] = templateUrl if os.path.exists(os.path.join(self.projecturl, templateUrl)): self.add_new_page(templateUrl) """ This function always asks for a template showing the new template window. This allows users to have multiple different templates created for back covers, spreads, other and have them accesible, while still having the convenience of a singular "add page" that adds a default. """ def slot_add_new_page_from_template(self): if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.add_new_page(templateUrl) """ This is the actual function that adds the template using the template url. It will attempt to name the new page projectName+number. """ def add_new_page(self, templateUrl): # check for page list and or location. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] if not "pageNumber" in self.setupDictionary.keys(): self.setupDictionary['pageNumber'] = 0 if (str(self.setupDictionary["pagesLocation"]).isspace()): self.setupDictionary["pagesLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where should the pages go?"), options=QFileDialog.ShowDirsOnly), self.projecturl) # Search for the possible name. extraUnderscore = str() if str(self.setupDictionary["projectName"])[-1].isdigit(): extraUnderscore = "_" self.setupDictionary['pageNumber'] += 1 pageName = str(self.setupDictionary["projectName"]).replace(" ", "_") + extraUnderscore + str(format(self.setupDictionary['pageNumber'], "03d")) url = os.path.join(str(self.setupDictionary["pagesLocation"]), pageName + ".kra") # open the page by opening the template and resaving it, or just opening it. absoluteUrl = os.path.join(self.projecturl, url) if (os.path.exists(absoluteUrl)): newPage = Application.openDocument(absoluteUrl) else: booltemplateExists = os.path.exists(os.path.join(self.projecturl, templateUrl)) if booltemplateExists is False: templateUrl = os.path.relpath(QFileDialog.getOpenFileName(caption=i18n("Which image should be the basis the new page?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0], self.projecturl) newPage = Application.openDocument(os.path.join(self.projecturl, templateUrl)) newPage.waitForDone() newPage.setFileName(absoluteUrl) newPage.setName(pageName.replace("_", " ")) newPage.save() newPage.waitForDone() # Get out the extra data for the standard item. newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(newPage.thumbnail(256, 256)))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(pageName.replace("_", " ")) newPageItem.setData("", role = CPE.DESCRIPTION) newPageItem.setData(url, role = CPE.URL) newPageItem.setData("", role = CPE.KEYWORDS) newPageItem.setData("", role = CPE.LASTEDIT) newPageItem.setData("", role = CPE.EDITOR) newPageItem.setToolTip(url) # close page document. while os.path.exists(absoluteUrl) is False: qApp.processEvents() newPage.close() # add item to page. self.pagesModel.appendRow(newPageItem) """ Write to the json configuratin file. This also checks the current state of the pages list. """ def slot_write_config(self): # Don't load when the pages are still being loaded, otherwise we'll be overwriting our own pages list. if (self.loadingPages is False): print("CPMT: writing comic configuration...") # Generate a pages list from the pagesmodel. pagesList = [] for i in range(self.pagesModel.rowCount()): index = self.pagesModel.index(i, 0) url = str(self.pagesModel.data(index, role=CPE.URL)) if url not in pagesList: pagesList.append(url) self.setupDictionary["pages"] = pagesList # Save to our json file. configFile = open(self.path_to_config, "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ Open a page in the pagesmodel in Krita. """ def slot_open_page(self, index): if index.column() is 0: # Get the absolute url from the relative one in the pages model. absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(index, role=CPE.URL))) # Make sure the page exists. if os.path.exists(absoluteUrl): page = Application.openDocument(absoluteUrl) # Set the title to the filename if it was empty. It looks a bit neater. if page.name().isspace or len(page.name()) < 1: page.setName(str(self.pagesModel.data(index, role=Qt.DisplayRole)).replace("_", " ")) # Add views for the document so the user can use it. Application.activeWindow().addView(page) Application.setActiveDocument(page) else: print("CPMT: The page cannot be opened because the file doesn't exist:", absoluteUrl) """ Call up the metadata editor dialog. Only when the dialog is "Accepted" will the metadata be saved. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ An attempt at making the description editable from the comic pages list. It is currently not working because ZipFile has no overwrite mechanism, and I don't have the energy to write one yet. """ def slot_write_description(self, index): for row in range(self.pagesModel.rowCount()): index = self.pagesModel.index(row, 1) indexUrl = self.pagesModel.index(row, 0) absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(indexUrl, role=CPE.URL))) page = zipfile.ZipFile(absoluteUrl, "a") xmlDoc = ET.ElementTree() ET.register_namespace("", "http://www.calligra.org/DTD/document-info") location = os.path.join(self.projecturl, "documentinfo.xml") xmlDoc.parse(location) xmlroot = ET.fromstring(page.read("documentinfo.xml")) calligra = "{http://www.calligra.org/DTD/document-info}" aboutelem = xmlroot.find(calligra + 'about') if ET.iselement(aboutelem.find(calligra + 'subject')): desc = aboutelem.find(calligra + 'subject') desc.text = self.pagesModel.data(index, role=Qt.EditRole) xmlstring = ET.tostring(xmlroot, encoding='unicode', method='xml', short_empty_elements=False) page.writestr(zinfo_or_arcname="documentinfo.xml", data=xmlstring) for document in Application.documents(): if str(document.fileName()) == str(absoluteUrl): document.setDocumentInfo(xmlstring) page.close() """ Calls up the export settings dialog. Only when accepted will the configuration be written. """ def slot_edit_export_settings(self): dialog = comics_export_dialog.comic_export_setting_dialog() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ Export the comic. Won't work without export settings set. """ def slot_export(self): exporter = comics_exporter.comicsExporter() exporter.set_config(self.setupDictionary, self.projecturl) exportSuccess = exporter.export() if exportSuccess: print("CPMT: Export success! The files have been written to the export folder!") - QMessageBox.information(self, "Export success!", "The files have been written to the export folder!", QMessageBox.Ok) + QMessageBox.information(self, i18n("Export success"), i18n("The files have been written to the export folder."), QMessageBox.Ok) """ Calls up the comics project setup wizard so users can create a new json file with the basic information. """ def slot_new_project(self): setup = comics_project_setup_wizard.ComicsProjectSetupWizard() setup.showDialog() """ This is triggered by any document save. It checks if the given url in in the pages list, and if so, updates the appropriate page thumbnail. This helps with the management of the pages, because the user will be able to see the thumbnails as a todo for the whole comic, giving a good overview over whether they still need to ink, color or the like for a given page, and it thus also rewards the user whenever they save. """ def slot_check_for_page_update(self, url): if "pages" in self.setupDictionary.keys(): relUrl = os.path.relpath(url, self.projecturl) if relUrl in self.setupDictionary["pages"]: index = self.pagesModel.index(self.setupDictionary["pages"].index(relUrl), 0) if index.isValid(): pageItem = self.pagesModel.itemFromIndex(index) page = zipfile.ZipFile(url, "r") dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) thumbnail = QImage.fromData(page.read("preview.png")) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setText(dataList[0]) pageItem.setData(dataList[1], role = CPE.DESCRIPTION) pageItem.setData(url, role = CPE.URL) pageItem.setData(dataList[2], role = CPE.KEYWORDS) pageItem.setData(dataList[3], role = CPE.LASTEDIT) pageItem.setData(dataList[4], role = CPE.EDITOR) self.pagesModel.setItem(index.row(), index.column(), pageItem) """ Resize all the pages in the pages list. It will show a dialog with the options for resizing. Then, it will try to pop up a progress dialog while resizing. The progress dialog shows the remaining time and pages. """ def slot_batch_resize(self): dialog = QDialog() - dialog.setWindowTitle(i18n("Resize all pages.")) + dialog.setWindowTitle(i18n("Resize all Pages")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) sizesBox = comics_export_dialog.comic_export_resize_widget("Scale", batch=True, fileType=False) exporterSizes = comics_exporter.sizesCalculator() dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(sizesBox) dialog.layout().addWidget(buttons) if dialog.exec_() == QDialog.Accepted: progress = QProgressDialog(i18n("Resizing pages..."), str(), 0, len(self.setupDictionary["pages"])) - progress.setWindowTitle(i18n("Resizing pages.")) + progress.setWindowTitle(i18n("Resizing Pages")) progress.setCancelButton(None) timer = QElapsedTimer() timer.start() config = {} config = sizesBox.get_config(config) for p in range(len(self.setupDictionary["pages"])): absoluteUrl = os.path.join(self.projecturl, self.setupDictionary["pages"][p]) progress.setValue(p) timePassed = timer.elapsed() if (p > 0): timeEstimated = (len(self.setupDictionary["pages"]) - p) * (timePassed / p) passedString = str(int(timePassed / 60000)) + ":" + format(int(timePassed / 1000), "02d") + ":" + format(timePassed % 1000, "03d") estimatedString = str(int(timeEstimated / 60000)) + ":" + format(int(timeEstimated / 1000), "02d") + ":" + format(int(timeEstimated % 1000), "03d") progress.setLabelText(str(i18n("{pages} of {pagesTotal} done. \nTime passed: {passedString}:\n Estimated:{estimated}")).format(pages=p, pagesTotal=len(self.setupDictionary["pages"]), passedString=passedString, estimated=estimatedString)) qApp.processEvents() if os.path.exists(absoluteUrl): doc = Application.openDocument(absoluteUrl) listScales = exporterSizes.get_scale_from_resize_config(config["Scale"], [doc.width(), doc.height(), doc.resolution(), doc.resolution()]) doc.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") doc.waitForDone() doc.save() doc.waitForDone() doc.close() def slot_show_page_viewer(self): index = int(self.comicPageList.currentIndex().row()) self.page_viewer_dialog.load_comic(self.path_to_config) self.page_viewer_dialog.go_to_page_index(index) self.page_viewer_dialog.show() """ Function to copy the current project location into the clipboard. This is useful for users because they'll be able to use that url to quickly move to the project location in outside applications. """ def slot_copy_project_url(self): if self.projecturl is not None: clipboard = qApp.clipboard() clipboard.setText(str(self.projecturl)) """ Scrape text files with the textlayer keys for text, and put those in a POT file. This makes it possible to handle translations. """ def slot_scrape_translations(self): translationFolder = self.setupDictionary.get("translationLocation", "translations") fullTranslationPath = os.path.join(self.projecturl, translationFolder) os.makedirs(fullTranslationPath, exist_ok=True) textLayersToSearch = self.setupDictionary.get("textLayerNames", ["text"]) scraper = comics_project_translation_scraper.translation_scraper(self.projecturl, translationFolder, textLayersToSearch, self.setupDictionary["projectName"]) # Run text scraper. language = self.setupDictionary.get("language", "en") metadata = {} metadata["title"] = self.setupDictionary.get("title", "") metadata["summary"] = self.setupDictionary.get("summary", "") metadata["keywords"] = ", ".join(self.setupDictionary.get("otherKeywords", [""])) metadata["transnotes"] = self.setupDictionary.get("translatorHeader", "Translator's Notes") scraper.start(self.setupDictionary["pages"], language, metadata) - QMessageBox.information(self, i18n("Scraping success!"), i18n("POT file has been written to: ")+fullTranslationPath, QMessageBox.Ok) + QMessageBox.information(self, i18n("Scraping success"), str(i18n("POT file has been written to: {file}")).format(file=fullTranslationPath), QMessageBox.Ok) """ This is required by the dockwidget class, otherwise unused. """ def canvasChanged(self, canvas): pass """ Add docker to program """ Application.addDockWidgetFactory(DockWidgetFactory("comics_project_manager_docker", DockWidgetFactoryBase.DockRight, comics_project_manager_docker)) diff --git a/plugins/python/comics_project_management_tools/comics_project_settings_dialog.py b/plugins/python/comics_project_management_tools/comics_project_settings_dialog.py index 903b8117c6..e80c6dcbf5 100644 --- a/plugins/python/comics_project_management_tools/comics_project_settings_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_project_settings_dialog.py @@ -1,190 +1,190 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ A dialog for editing the general project settings. """ import os from PyQt5.QtWidgets import QWidget, QDialog, QDialogButtonBox, QHBoxLayout, QFormLayout, QPushButton, QLabel, QLineEdit, QToolButton, QFrame, QAction, QFileDialog, QComboBox, QSizePolicy from PyQt5.QtCore import QDir, Qt, pyqtSignal from krita import * """ A Widget that contains both a qlabel and a button for selecting a path. """ class path_select(QWidget): projectUrl = "" question = i18n("Which folder?") """ emits when a new directory has been chosen. """ locationChanged = pyqtSignal() """ Initialise the widget. @param question is the question asked when selecting a directory. @param project url is the url to which the label is relative. """ def __init__(self, parent=None, flags=None, question=str(), projectUrl=None): super(path_select, self).__init__(parent) self.setLayout(QHBoxLayout()) self.location = QLabel() self.button = QToolButton() # Until we have a proper icon self.layout().addWidget(self.location) self.layout().addWidget(self.button) self.layout().setContentsMargins(0, 0, 0, 0) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.location.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.location.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.location.setAlignment(Qt.AlignRight) self.location.setLineWidth(1) if projectUrl is None: self.projectUrl = QDir.homePath() else: self.projectUrl = projectUrl self.question = question self.action_change_folder = QAction(i18n("Change Folder"), self) self.action_change_folder.setIcon(Application.icon("folder")) self.action_change_folder.triggered.connect(self.slot_change_location) self.button.setDefaultAction(self.action_change_folder) """ pops up a directory chooser widget, and when a directory is chosen a locationChanged signal is emitted. """ def slot_change_location(self): location = QFileDialog.getExistingDirectory(caption=self.question, directory=self.projectUrl) if location is not None and location.isspace() is False and len(location) > 0: location = os.path.relpath(location, self.projectUrl) self.location.setText(location) self.locationChanged.emit() """ Set the location. @param path - the location relative to the projectUrl. """ def setLocation(self, path=str()): self.location.setText(path) """ Get the location. @returns a string with the location relative to the projectUrl. """ def getLocation(self): return str(self.location.text()) """ Dialog for editing basic proect details like the project name, default template, template location, etc. """ class comics_project_details_editor(QDialog): configGroup = "ComicsProjectManagementTools" """ Initialise the editor. @param projectUrl - The directory to which all paths are relative. """ def __init__(self, projectUrl=str()): super().__init__() self.projectUrl = projectUrl layout = QFormLayout() self.setLayout(layout) self.setWindowTitle(i18n("Comic Project Settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) self.lnProjectName = QLineEdit() self.lnProjectConcept = QLineEdit() self.cmb_defaultTemplate = QComboBox() self.pagesLocation = path_select(question=i18n("Where should the pages go?"), projectUrl=self.projectUrl) self.exportLocation = path_select(question=i18n("Where should the export go?"), projectUrl=self.projectUrl) self.templateLocation = path_select(question=i18n("Where are the templates?"), projectUrl=self.projectUrl) self.translationLocation = path_select(question=i18n("Where are the translations?"), projectUrl=self.projectUrl) self.keyLocation = path_select(question=i18n("Where are the extra auto-completion keys located?")) self.keyLocation.setToolTip(i18n("The location for extra autocompletion keys in the metadata editor. Point this at a folder containing key_characters/key_format/key_genre/key_rating/key_author_roles/key_other with inside txt files (csv for rating) containing the extra auto-completion keys, each on a new line. This path is stored in the Krita configuration, and not the project configuration.")) self.templateLocation.locationChanged.connect(self.refill_templates) - layout.addRow(i18n("Project Name:"), self.lnProjectName) - layout.addRow(i18n("Project Concept:"), self.lnProjectConcept) - layout.addRow(i18n("Pages Folder:"), self.pagesLocation) - layout.addRow(i18n("Export Folder:"), self.exportLocation) - layout.addRow(i18n("Template Folder:"), self.templateLocation) - layout.addRow(i18n("Translation Folder:"), self.translationLocation) - layout.addRow(i18n("Default Template:"), self.cmb_defaultTemplate) - layout.addRow(i18n("Extra Keys Folder:"), self.keyLocation) + layout.addRow(i18n("Project name:"), self.lnProjectName) + layout.addRow(i18n("Project concept:"), self.lnProjectConcept) + layout.addRow(i18n("Pages folder:"), self.pagesLocation) + layout.addRow(i18n("Export folder:"), self.exportLocation) + layout.addRow(i18n("Template folder:"), self.templateLocation) + layout.addRow(i18n("Translation folder:"), self.translationLocation) + layout.addRow(i18n("Default template:"), self.cmb_defaultTemplate) + layout.addRow(i18n("Extra keys folder:"), self.keyLocation) self.layout().addWidget(buttons) """ Fill the templates doc with the kra files found in the templates directory. Might want to extend this to other files as well, as they basically get resaved anyway... """ def refill_templates(self): self.cmb_defaultTemplate.clear() templateLocation = os.path.join(self.projectUrl, self.templateLocation.getLocation()) for entry in os.scandir(templateLocation): if entry.name.endswith('.kra') and entry.is_file(): name = os.path.relpath(entry.path, templateLocation) self.cmb_defaultTemplate.addItem(name) """ Load the UI values from the config dictionary given. """ def setConfig(self, config, projectUrl): self.projectUrl = projectUrl if "projectName"in config.keys(): self.lnProjectName.setText(config["projectName"]) if "concept"in config.keys(): self.lnProjectConcept.setText(config["concept"]) if "pagesLocation" in config.keys(): self.pagesLocation.setLocation(config.get("pagesLocation", "pages")) self.exportLocation.setLocation(config.get("exportLocation", "export")) self.templateLocation.setLocation(config.get("templateLocation", "templates")) self.translationLocation.setLocation(config.get("translationLocation", "translations")) self.refill_templates() self.keyLocation.setLocation(Application.readSetting(self.configGroup, "extraKeysLocation", str())) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["projectName"] = self.lnProjectName.text() config["concept"] = self.lnProjectConcept.text() config["pagesLocation"] = self.pagesLocation.getLocation() config["exportLocation"] = self.exportLocation.getLocation() config["templateLocation"] = self.templateLocation.getLocation() config["translationLocation"] = self.translationLocation.getLocation() config["singlePageTemplate"] = os.path.join(self.templateLocation.getLocation(), self.cmb_defaultTemplate.currentText()) Application.writeSetting(self.configGroup, "extraKeysLocation", self.keyLocation.getLocation()) return config diff --git a/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py b/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py index a88c815b84..7312b24636 100644 --- a/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py +++ b/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py @@ -1,222 +1,222 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ This is a wizard that helps you set up a comics project in Krita. """ import json # For writing to json. import os # For finding the script location. from pathlib import Path # For reading all the files in a directory. import random # For selecting two random words from a list. from PyQt5.QtWidgets import QWidget, QWizard, QWizardPage, QHBoxLayout, QFormLayout, QFileDialog, QLineEdit, QPushButton, QCheckBox, QLabel, QDialog from PyQt5.QtCore import QDate, QLocale from krita import * from . import comics_metadata_dialog """ The actual wizard. """ class ComicsProjectSetupWizard(): setupDictionary = {} projectDirectory = "" def __init__(self): # super().__init__(parent) # Search the location of the script for the two lists that are used with the projectname generator. mainP = Path(__file__).parent self.generateListA = [] self.generateListB = [] if Path(mainP / "projectGenLists" / "listA.txt").exists(): for l in open(str(mainP / "projectGenLists" / "listA.txt"), "r"): if l.isspace() == False: self.generateListA.append(l.strip("\n")) if Path(mainP / "projectGenLists" / "listB.txt").exists(): for l in open(str(mainP / "projectGenLists" / "listB.txt"), "r"): if l.isspace() == False: self.generateListB.append(l.strip("\n")) def showDialog(self): # Initialise the setup directory empty toavoid exceptions. self.setupDictionary = {} # ask for a project directory. self.projectDirectory = QFileDialog.getExistingDirectory(caption=i18n("Where should the comic project go?"), options=QFileDialog.ShowDirsOnly) if os.path.exists(self.projectDirectory) is False: return self.pagesDirectory = os.path.relpath(self.projectDirectory, self.projectDirectory) self.exportDirectory = os.path.relpath(self.projectDirectory, self.projectDirectory) wizard = QWizard() wizard.setWindowTitle(i18n("Comic Project Setup")) wizard.setOption(QWizard.IndependentPages, True) # Set up the UI for the wizard basicsPage = QWizardPage() basicsPage.setTitle(i18n("Basic Comic Project Settings")) formLayout = QFormLayout() basicsPage.setLayout(formLayout) projectLayout = QHBoxLayout() self.lnProjectName = QLineEdit() basicsPage.registerField("Project Name*", self.lnProjectName) self.lnProjectName.setToolTip(i18n("A Project name. This can be different from the eventual title")) btnRandom = QPushButton() btnRandom.setText(i18n("Generate")) btnRandom.setToolTip(i18n("If you cannot come up with a project name, our highly sophisticated project name generator will serve to give a classy yet down to earth name.")) btnRandom.clicked.connect(self.slot_generate) projectLayout.addWidget(self.lnProjectName) projectLayout.addWidget(btnRandom) lnConcept = QLineEdit() - lnConcept.setToolTip(i18n("What is your comic about? This is mostly for your own convenience so don't worry about what it says too much.")) + lnConcept.setToolTip(i18n("What is your comic about? This is mostly for your own convenience so do not worry about what it says too much.")) self.cmbLanguage = comics_metadata_dialog.language_combo_box() self.cmbLanguage.setToolTip(i18n("The main language the comic is in")) self.cmbLanguage.setEntryToCode(str(QLocale.system().name()).split("_")[0]) self.cmbCountry = comics_metadata_dialog.country_combo_box() if QLocale.system() != QLocale.c(): self.cmbCountry.setEntryToCode(str(QLocale.system().name()).split("_")[-1]) else: self.slot_update_countries() self.cmbLanguage.currentIndexChanged.connect(self.slot_update_countries) self.lnProjectDirectory = QLabel(self.projectDirectory) self.chkMakeProjectDirectory = QCheckBox() labelDirectory = QLabel(i18n("Make a new directory with the project name.")) labelDirectory.setWordWrap(True) stringDirectoryTooltip = i18n("This allows you to select a generic comics project directory, in which a new folder will be made for the project using the given project name.") self.chkMakeProjectDirectory.setToolTip(stringDirectoryTooltip) labelDirectory.setToolTip(stringDirectoryTooltip) self.chkMakeProjectDirectory.setChecked(True) self.lnPagesDirectory = QLineEdit() self.lnPagesDirectory.setText(i18n("pages")) - self.lnPagesDirectory.setToolTip(i18n("The name for the folder where the pages are contained. If it doesn't exist, it will be created.")) + self.lnPagesDirectory.setToolTip(i18n("The name for the folder where the pages are contained. If it does not exist, it will be created.")) self.lnExportDirectory = QLineEdit() self.lnExportDirectory.setText(i18n("export")) - self.lnExportDirectory.setToolTip(i18n("The name for the folder where the export is put. If it doesn't exist, it will be created.")) + self.lnExportDirectory.setToolTip(i18n("The name for the folder where the export is put. If it does not exist, it will be created.")) self.lnTemplateLocation = QLineEdit() self.lnTemplateLocation.setText(i18n("templates")) self.lnTemplateLocation.setToolTip(i18n("The name for the folder where the page templates are sought in.")) self.lnTranslationLocation = QLineEdit() self.lnTranslationLocation.setText(i18n("translations")) self.lnTranslationLocation.setToolTip("This is the location that POT files will be stored to and PO files will be read from.") - formLayout.addRow(i18n("Comic Concept:"), lnConcept) - formLayout.addRow(i18n("Project Name:"), projectLayout) - formLayout.addRow(i18n("Main Language:"), self.cmbLanguage) + formLayout.addRow(i18n("Comic concept:"), lnConcept) + formLayout.addRow(i18n("Project name:"), projectLayout) + formLayout.addRow(i18n("Main language:"), self.cmbLanguage) formLayout.addRow("", self.cmbCountry) buttonMetaData = QPushButton(i18n("Meta Data")) buttonMetaData.clicked.connect(self.slot_edit_meta_data) wizard.addPage(basicsPage) foldersPage = QWizardPage() foldersPage.setTitle(i18n("Folder names and other.")) folderFormLayout = QFormLayout() foldersPage.setLayout(folderFormLayout) - folderFormLayout.addRow(i18n("Project Directory:"), self.lnProjectDirectory) + folderFormLayout.addRow(i18n("Project directory:"), self.lnProjectDirectory) folderFormLayout.addRow(self.chkMakeProjectDirectory, labelDirectory) - folderFormLayout.addRow(i18n("Pages Directory"), self.lnPagesDirectory) - folderFormLayout.addRow(i18n("Export Directory"), self.lnExportDirectory) - folderFormLayout.addRow(i18n("Template Directory"), self.lnTemplateLocation) - folderFormLayout.addRow(i18n("Translation Directory"), self.lnTranslationLocation) + folderFormLayout.addRow(i18n("Pages directory"), self.lnPagesDirectory) + folderFormLayout.addRow(i18n("Export directory"), self.lnExportDirectory) + folderFormLayout.addRow(i18n("Template directory"), self.lnTemplateLocation) + folderFormLayout.addRow(i18n("Translation directory"), self.lnTranslationLocation) folderFormLayout.addRow("", buttonMetaData) wizard.addPage(foldersPage) # Execute the wizard, and after wards... if (wizard.exec_()): # First get the directories, check if the directories exist, and otherwise make them. self.pagesDirectory = self.lnPagesDirectory.text() self.exportDirectory = self.lnExportDirectory.text() self.templateLocation = self.lnTemplateLocation.text() self.translationLocation = self.lnTranslationLocation.text() projectPath = Path(self.projectDirectory) # Only make a project directory if the checkbox for that has been checked. if self.chkMakeProjectDirectory.isChecked(): projectPath = projectPath / self.lnProjectName.text() if projectPath.exists() is False: projectPath.mkdir() self.projectDirectory = str(projectPath) if Path(projectPath / self.pagesDirectory).exists() is False: Path(projectPath / self.pagesDirectory).mkdir() if Path(projectPath / self.exportDirectory).exists() is False: Path(projectPath / self.exportDirectory).mkdir() if Path(projectPath / self.templateLocation).exists() is False: Path(projectPath / self.templateLocation).mkdir() if Path(projectPath / self.translationLocation).exists() is False: Path(projectPath / self.translationLocation).mkdir() # Then store the information into the setup diactionary. self.setupDictionary["projectName"] = self.lnProjectName.text() self.setupDictionary["concept"] = lnConcept.text() self.setupDictionary["language"] = str(self.cmbLanguage.codeForCurrentEntry()) self.setupDictionary["pagesLocation"] = self.pagesDirectory self.setupDictionary["exportLocation"] = self.exportDirectory self.setupDictionary["templateLocation"] = self.templateLocation self.setupDictionary["translationLocation"] = self.translationLocation # Finally, write the dictionary into the json file. self.writeConfig() """ This calls up the metadata dialog, for if people already have information they want to type in at the setup stage. Not super likely, but the organisation and management aspect of the comic manager means we should give the option to organise as smoothly as possible. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() self.setupDictionary["language"] = str(self.cmbLanguage.codeForCurrentEntry()) dialog.setConfig(self.setupDictionary) dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.cmbLanguage.setEntryToCode(self.setupDictionary["language"]) """ Update the country list when the language list changes. """ def slot_update_countries(self): code = self.cmbLanguage.codeForCurrentEntry() self.cmbCountry.set_country_for_locale(code) """ Write the actual config to the chosen project directory. """ def writeConfig(self): print("CPMT: writing comic configuration...") print(self.projectDirectory) configFile = open(os.path.join(self.projectDirectory, "comicConfig.json"), "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ As you may be able to tell, the random projectname generator is hardly sophisticated. It picks a word from a list of names of figures from Greek Mythology, and a name from a list of vegetables and fruits and combines the two camelcased. It makes for good codenames at the least. """ def slot_generate(self): if len(self.generateListA) > 0 and len(self.generateListB) > 0: nameA = self.generateListA[random.randint(0, len(self.generateListA) - 1)] nameB = self.generateListB[random.randint(0, len(self.generateListB) - 1)] self.lnProjectName.setText(str(nameA.title() + nameB.title())) diff --git a/plugins/python/comics_project_management_tools/comics_template_dialog.py b/plugins/python/comics_project_management_tools/comics_template_dialog.py index a9e1dc1603..c71df63886 100644 --- a/plugins/python/comics_project_management_tools/comics_template_dialog.py +++ b/plugins/python/comics_project_management_tools/comics_template_dialog.py @@ -1,440 +1,440 @@ """ Copyright (c) 2017 Wolthera van Hövell tot Westerflier This file is part of the Comics Project Management Tools(CPMT). CPMT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CPMT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the CPMT. If not, see . """ """ Template dialog """ import os import shutil #from PyQt5.QtGui import * from PyQt5.QtWidgets import QDialog, QComboBox, QDialogButtonBox, QVBoxLayout, QFormLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget from PyQt5.QtCore import QLocale, Qt, QByteArray, QRectF from PyQt5.QtGui import QImage, QPainter, QPixmap from krita import * """ Quick and dirty QComboBox subclassing that handles unitconversion for us. """ class simpleUnitBox(QComboBox): pixels = i18n("Pixels") inches = i18n("Inches") centimeter = i18n("Centimeter") millimeter = i18n("millimeter") def __init__(self): super(simpleUnitBox, self).__init__() self.addItem(self.pixels) self.addItem(self.inches) self.addItem(self.centimeter) self.addItem(self.millimeter) if QLocale().system().measurementSystem() is QLocale.MetricSystem: self.setCurrentIndex(2) # set to centimeter if metric system. else: self.setCurrentIndex(1) def pixelsForUnit(self, unit, DPI): if (self.currentText() == self.pixels): return unit elif (self.currentText() == self.inches): return self.inchesToPixels(unit, DPI) elif (self.currentText() == self.centimeter): return self.centimeterToPixels(unit, DPI) elif (self.currentText() == self.millimeter): return self.millimeterToPixels(unit, DPI) def inchesToPixels(self, inches, DPI): return DPI * inches def centimeterToInches(self, cm): return cm / 2.54 def centimeterToPixels(self, cm, DPI): return self.inchesToPixels(self.centimeterToInches(cm), DPI) def millimeterToCentimeter(self, mm): return mm / 10 def millimeterToPixels(self, mm, DPI): return self.inchesToPixels(self.centimeterToInches(self.millimeterToCentimeter(mm)), DPI) class comics_template_dialog(QDialog): templateDirectory = str() templates = QComboBox() buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) def __init__(self, templateDirectory): super().__init__() self.templateDirectory = templateDirectory - self.setWindowTitle(i18n("Add new template")) + self.setWindowTitle(i18n("Add new Template")) self.setLayout(QVBoxLayout()) self.templates = QComboBox() self.templates.setEnabled(False) self.fill_templates() self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.buttons.button(QDialogButtonBox.Ok).setEnabled(False) mainWidget = QWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(self.buttons) mainWidget.setLayout(QVBoxLayout()) - btn_create = QPushButton(i18n("Create a template")) + btn_create = QPushButton(i18n("Create Template")) btn_create.clicked.connect(self.slot_create_template) - btn_import = QPushButton(i18n("Import templates")) + btn_import = QPushButton(i18n("Import Templates")) btn_import.clicked.connect(self.slot_import_template) mainWidget.layout().addWidget(self.templates) mainWidget.layout().addWidget(btn_create) mainWidget.layout().addWidget(btn_import) def fill_templates(self): self.templates.clear() for entry in os.scandir(self.templateDirectory): if entry.name.endswith('.kra') and entry.is_file(): name = os.path.relpath(entry.path, self.templateDirectory) self.templates.addItem(name) if self.templates.model().rowCount() > 0: self.templates.setEnabled(True) self.buttons.button(QDialogButtonBox.Ok).setEnabled(True) def slot_create_template(self): create = comics_template_create(self.templateDirectory) if create.exec_() == QDialog.Accepted: if (create.prepare_krita_file()): self.fill_templates() def slot_import_template(self): filenames = QFileDialog.getOpenFileNames(caption=i18n("Which files should be added to the template folder?"), directory=self.templateDirectory, filter=str(i18n("Krita files") + "(*.kra)"))[0] for file in filenames: shutil.copy2(file, self.templateDirectory) self.fill_templates() def url(self): return os.path.join(self.templateDirectory, self.templates.currentText()) class comics_template_create(QDialog): urlSavedTemplate = str() templateDirectory = str() def __init__(self, templateDirectory): super().__init__() self.templateDirectory = templateDirectory - self.setWindowTitle(i18n("Create new template")) + self.setWindowTitle(i18n("Create new Template")) self.setLayout(QVBoxLayout()) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QWidget() 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.")) explanation.setWordWrap(True) self.layout().addWidget(explanation) self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) mainWidget.setLayout(QHBoxLayout()) elements = QWidget() elements.setLayout(QVBoxLayout()) mainWidget.layout().addWidget(elements) self.imagePreview = QLabel() self.imagePreview.setMinimumSize(256, 256) mainWidget.layout().addWidget(self.imagePreview) self.templateName = QLineEdit() self.templateName.setPlaceholderText("...") elements.layout().addWidget(self.templateName) self.DPI = QSpinBox() self.DPI.setMaximum(1200) self.DPI.setValue(300) self.spn_width = QDoubleSpinBox() self.spn_width.setMaximum(10000) self.spn_height = QDoubleSpinBox() self.spn_height.setMaximum(10000) self.widthUnit = simpleUnitBox() self.heightUnit = simpleUnitBox() widgetSize = QWidget() sizeForm = QFormLayout() sizeForm.addRow(i18n("DPI:"), self.DPI) widthLayout = QHBoxLayout() widthLayout.addWidget(self.spn_width) widthLayout.addWidget(self.widthUnit) sizeForm.addRow(i18n("Width:"), widthLayout) heightLayout = QHBoxLayout() heightLayout.addWidget(self.spn_height) heightLayout.addWidget(self.heightUnit) sizeForm.addRow(i18n("Height:"), heightLayout) widgetSize.setLayout(sizeForm) elements.layout().addWidget(widgetSize) marginAndBleed = QTabWidget() elements.layout().addWidget(marginAndBleed) margins = QWidget() marginForm = QGridLayout() margins.setLayout(marginForm) self.marginLeft = QDoubleSpinBox() self.marginLeft.setMaximum(1000) self.marginLeftUnit = simpleUnitBox() self.marginRight = QDoubleSpinBox() self.marginRight.setMaximum(1000) self.marginRightUnit = simpleUnitBox() self.marginTop = QDoubleSpinBox() self.marginTop.setMaximum(1000) self.marginTopUnit = simpleUnitBox() self.marginBottom = QDoubleSpinBox() self.marginBottom.setMaximum(1000) self.marginBottomUnit = simpleUnitBox() marginForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignRight) marginForm.addWidget(self.marginLeft, 0, 1) marginForm.addWidget(self.marginLeftUnit, 0, 2) marginForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignRight) marginForm.addWidget(self.marginTop, 1, 1) marginForm.addWidget(self.marginTopUnit, 1, 2) marginForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignRight) marginForm.addWidget(self.marginRight, 2, 1) marginForm.addWidget(self.marginRightUnit, 2, 2) marginForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignRight) marginForm.addWidget(self.marginBottom, 3, 1) marginForm.addWidget(self.marginBottomUnit, 3, 2) marginAndBleed.addTab(margins, i18n("Margins")) bleeds = QWidget() bleedsForm = QGridLayout() bleeds.setLayout(bleedsForm) self.bleedLeft = QDoubleSpinBox() self.bleedLeft.setMaximum(1000) self.bleedLeftUnit = simpleUnitBox() self.bleedRight = QDoubleSpinBox() self.bleedRight.setMaximum(1000) self.bleedRightUnit = simpleUnitBox() self.bleedTop = QDoubleSpinBox() self.bleedTop.setMaximum(1000) self.bleedTopUnit = simpleUnitBox() self.bleedBottom = QDoubleSpinBox() self.bleedBottom.setMaximum(1000) self.bleedBottomUnit = simpleUnitBox() bleedsForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedLeft, 0, 1) bleedsForm.addWidget(self.bleedLeftUnit, 0, 2) bleedsForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedTop, 1, 1) bleedsForm.addWidget(self.bleedTopUnit, 1, 2) bleedsForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedRight, 2, 1) bleedsForm.addWidget(self.bleedRightUnit, 2, 2) bleedsForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedBottom, 3, 1) bleedsForm.addWidget(self.bleedBottomUnit, 3, 2) marginAndBleed.addTab(bleeds, i18n("Bleeds")) if QLocale().system().measurementSystem() is QLocale.MetricSystem: self.setDefaults("European") else: self.setDefaults("American") self.spn_width.valueChanged.connect(self.updateImagePreview) self.widthUnit.currentIndexChanged.connect(self.updateImagePreview) self.spn_height.valueChanged.connect(self.updateImagePreview) self.heightUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginLeft.valueChanged.connect(self.updateImagePreview) self.marginLeftUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginRight.valueChanged.connect(self.updateImagePreview) self.marginRightUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginTop.valueChanged.connect(self.updateImagePreview) self.marginTopUnit.currentIndexChanged.connect(self.updateImagePreview) self.marginBottom.valueChanged.connect(self.updateImagePreview) self.marginBottomUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedLeft.valueChanged.connect(self.updateImagePreview) self.bleedLeftUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedRight.valueChanged.connect(self.updateImagePreview) self.bleedRightUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedTop.valueChanged.connect(self.updateImagePreview) self.bleedTopUnit.currentIndexChanged.connect(self.updateImagePreview) self.bleedBottom.valueChanged.connect(self.updateImagePreview) self.bleedBottomUnit.currentIndexChanged.connect(self.updateImagePreview) self.updateImagePreview() def prepare_krita_file(self): wBase = max(self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()), 1) bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value()) bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value()) mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value()) mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value()) hBase = max(self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()), 1) bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value()) bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value()) mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value()) mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value()) template = Application.createDocument((wBase + bL + bR), (hBase + bT + bB), self.templateName.text(), "RGBA", "U8", "sRGB built-in", self.DPI.value()) backgroundNode = template.createNode(i18n("Background"), "paintlayer") template.rootNode().addChildNode(backgroundNode, None) pixelByteArray = QByteArray() pixelByteArray = backgroundNode.pixelData(0, 0, (wBase + bL + bR), (hBase + bT + bB)) white = int(255) pixelByteArray.fill(white.to_bytes(1, byteorder='little')) backgroundNode.setPixelData(pixelByteArray, 0, 0, (wBase + bL + bR), (hBase + bT + bB)) backgroundNode.setLocked(True) sketchNode = template.createNode(i18n("Sketch"), "paintlayer") template.rootNode().addChildNode(sketchNode, backgroundNode) verticalGuides = [] verticalGuides.append(bL) verticalGuides.append(bL + mL) verticalGuides.append((bL + wBase) - mR) verticalGuides.append(bL + wBase) horizontalGuides = [] horizontalGuides.append(bT) horizontalGuides.append(bT + mT) horizontalGuides.append((bT + hBase) - mB) horizontalGuides.append(bT + hBase) template.setHorizontalGuides(horizontalGuides) template.setVerticalGuides(verticalGuides) template.setGuidesVisible(True) template.setGuidesLocked(True) self.urlSavedTemplate = os.path.join(self.templateDirectory, self.templateName.text() + ".kra") success = template.exportImage(self.urlSavedTemplate, InfoObject()) print("CPMT: Template", self.templateName.text(), "made and saved.") template.waitForDone() template.close() return success def updateImagePreview(self): maxSize = 256 wBase = max(self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()), 1) bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value()) bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value()) mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value()) mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value()) hBase = max(self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()), 1) bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value()) bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value()) mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value()) mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value()) scaleRatio = maxSize / (hBase + bT + bB) if wBase > hBase: scaleRatio = maxSize / (wBase + bR + bL) width = (wBase + bL + bR) * scaleRatio height = (hBase + bT + bB) * scaleRatio topLeft = [max((maxSize - width) / 2, 0), max((maxSize - height) / 2, 0)] image = QImage(maxSize, maxSize, QImage.Format_ARGB32) image.fill(Qt.transparent) p = QPainter(image) p.setBrush(Qt.white) CanvasSize = QRectF(topLeft[0], topLeft[1], width, height) p.drawRect(CanvasSize.toRect()) # Draw bleeds. PageSize = CanvasSize PageSize.setWidth(width - (bR * scaleRatio)) PageSize.setHeight(height - (bB * scaleRatio)) PageSize.setX(PageSize.x() + (bL * scaleRatio)) PageSize.setY(PageSize.y() + (bT * scaleRatio)) p.setPen(Qt.blue) p.setBrush(Qt.transparent) p.drawRect(PageSize.toRect()) # Draw liveArea LiveArea = PageSize LiveArea.setWidth(LiveArea.width() - (mR * scaleRatio)) LiveArea.setHeight(LiveArea.height() - (mB * scaleRatio)) LiveArea.setX(LiveArea.x() + (mL * scaleRatio)) LiveArea.setY(LiveArea.y() + (mT * scaleRatio)) p.setPen(Qt.blue) p.drawRect(LiveArea.toRect()) p.end() self.imagePreview.setPixmap(QPixmap.fromImage(image)) def setDefaults(self, type): if type == "American": # American 11x17 inch self.spn_width.setValue(11) self.widthUnit.setCurrentIndex(1) self.spn_height.setValue(17) self.heightUnit.setCurrentIndex(1) self.bleedBottom.setValue(1) self.bleedBottomUnit.setCurrentIndex(1) self.bleedTop.setValue(1) self.bleedTopUnit.setCurrentIndex(1) self.bleedLeft.setValue(0.5) self.bleedLeftUnit.setCurrentIndex(1) self.bleedRight.setValue(0.5) self.bleedRightUnit.setCurrentIndex(1) self.marginBottom.setValue(0.745) self.marginBottomUnit.setCurrentIndex(1) self.marginTop.setValue(0.745) self.marginTopUnit.setCurrentIndex(1) self.marginRight.setValue(0.5) self.marginRightUnit.setCurrentIndex(1) self.marginLeft.setValue(0.5) self.marginLeftUnit.setCurrentIndex(1) if type == "European": # European A4 self.spn_width.setValue(21) self.widthUnit.setCurrentIndex(2) self.spn_height.setValue(29.7) self.heightUnit.setCurrentIndex(2) self.bleedBottom.setValue(5) self.bleedBottomUnit.setCurrentIndex(3) self.bleedTop.setValue(5) self.bleedTopUnit.setCurrentIndex(3) self.bleedLeft.setValue(5) self.bleedLeftUnit.setCurrentIndex(3) self.bleedRight.setValue(5) self.bleedRightUnit.setCurrentIndex(3) self.marginBottom.setValue(1.5) self.marginBottomUnit.setCurrentIndex(2) self.marginTop.setValue(1.5) self.marginTopUnit.setCurrentIndex(2) self.marginRight.setValue(1) self.marginRightUnit.setCurrentIndex(2) self.marginLeft.setValue(1) self.marginLeftUnit.setCurrentIndex(2) def url(self): return self.urlSavedTemplate diff --git a/plugins/python/documenttools/documenttools.py b/plugins/python/documenttools/documenttools.py index cf498ef349..2fbcdd6c77 100644 --- a/plugins/python/documenttools/documenttools.py +++ b/plugins/python/documenttools/documenttools.py @@ -1,34 +1,34 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import krita from . import uidocumenttools class DocumentToolsExtension(krita.Extension): def __init__(self, parent): super(DocumentToolsExtension, self).__init__(parent) def setup(self): pass def createActions(self, window): - action = window.createAction("document_tools", "Document Tools") - action.setToolTip("Plugin to manipulate properties of selected documents") + action = window.createAction("document_tools", i18n("Document Tools")) + action.setToolTip(i18n("Plugin to manipulate properties of selected documents.")) action.triggered.connect(self.initialize) def initialize(self): self.uidocumenttools = uidocumenttools.UIDocumentTools() self.uidocumenttools.initialize() Scripter.addExtension(DocumentToolsExtension(krita.Krita.instance())) diff --git a/plugins/python/documenttools/uidocumenttools.py b/plugins/python/documenttools/uidocumenttools.py index eec5e51279..ae85d202a6 100644 --- a/plugins/python/documenttools/uidocumenttools.py +++ b/plugins/python/documenttools/uidocumenttools.py @@ -1,107 +1,107 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from . import documenttoolsdialog from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QFormLayout, QListWidget, QAbstractItemView, QDialogButtonBox, QVBoxLayout, QFrame, QTabWidget, QPushButton, QAbstractScrollArea, QMessageBox) import krita import importlib class UIDocumentTools(object): def __init__(self): self.mainDialog = documenttoolsdialog.DocumentToolsDialog() self.mainLayout = QVBoxLayout(self.mainDialog) self.formLayout = QFormLayout() self.documentLayout = QVBoxLayout() - self.refreshButton = QPushButton("Refresh") + self.refreshButton = QPushButton(i18n("Refresh")) self.widgetDocuments = QListWidget() self.tabTools = QTabWidget() self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.kritaInstance = krita.Krita.instance() self.documentsList = [] self.refreshButton.clicked.connect(self.refreshButtonClicked) self.buttonBox.accepted.connect(self.confirmButton) self.buttonBox.rejected.connect(self.mainDialog.close) self.mainDialog.setWindowModality(Qt.NonModal) self.widgetDocuments.setSelectionMode(QAbstractItemView.MultiSelection) self.widgetDocuments.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) def initialize(self): self.loadDocuments() self.loadTools() self.documentLayout.addWidget(self.widgetDocuments) self.documentLayout.addWidget(self.refreshButton) - self.formLayout.addRow('Documents', self.documentLayout) + self.formLayout.addRow(i18n("Documents:"), self.documentLayout) self.formLayout.addRow(self.tabTools) self.line = QFrame() self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.mainLayout.addLayout(self.formLayout) self.mainLayout.addWidget(self.line) self.mainLayout.addWidget(self.buttonBox) self.mainDialog.resize(500, 300) - self.mainDialog.setWindowTitle("Document Tools") + self.mainDialog.setWindowTitle(i18n("Document Tools")) self.mainDialog.setSizeGripEnabled(True) self.mainDialog.show() self.mainDialog.activateWindow() def loadTools(self): modulePath = 'documenttools.tools' toolsModule = importlib.import_module(modulePath) modules = [] for classPath in toolsModule.ToolClasses: _module = classPath[:classPath.rfind(".")] _klass = classPath[classPath.rfind(".") + 1:] modules.append(dict(module='{0}.{1}'.format(modulePath, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) toolClass = getattr(m, module['klass']) obj = toolClass(self.mainDialog) self.tabTools.addTab(obj, obj.objectName()) def loadDocuments(self): self.widgetDocuments.clear() self.documentsList = [document for document in self.kritaInstance.documents() if document.fileName()] for document in self.documentsList: self.widgetDocuments.addItem(document.fileName()) def refreshButtonClicked(self): self.loadDocuments() def confirmButton(self): selectedPaths = [item.text() for item in self.widgetDocuments.selectedItems()] selectedDocuments = [document for document in self.documentsList for path in selectedPaths if path == document.fileName()] self.msgBox = QMessageBox(self.mainDialog) if selectedDocuments: widget = self.tabTools.currentWidget() widget.adjust(selectedDocuments) - self.msgBox.setText("The selected documents has been modified.") + self.msgBox.setText(i18n("The selected documents has been modified.")) else: - self.msgBox.setText("Select at least one document.") + self.msgBox.setText(i18n("Select at least one document.")) self.msgBox.exec_() diff --git a/plugins/python/exportlayers/exportlayers.py b/plugins/python/exportlayers/exportlayers.py index c6336f62a5..17cb698d71 100644 --- a/plugins/python/exportlayers/exportlayers.py +++ b/plugins/python/exportlayers/exportlayers.py @@ -1,34 +1,34 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import krita from . import uiexportlayers class ExportLayersExtension(krita.Extension): def __init__(self, parent): super(ExportLayersExtension, self).__init__(parent) def setup(self): pass def createActions(self, window): - action = window.createAction("export_layers", "Export Layers") - action.setToolTip("Plugin to export layers from a document") + action = window.createAction("export_layers", i18n("Export Layers")) + action.setToolTip(i18n("Plugin to export layers from a document.")) action.triggered.connect(self.initialize) def initialize(self): self.uiexportlayers = uiexportlayers.UIExportLayers() self.uiexportlayers.initialize() Scripter.addExtension(ExportLayersExtension(krita.Krita.instance())) diff --git a/plugins/python/exportlayers/uiexportlayers.py b/plugins/python/exportlayers/uiexportlayers.py index 9b8c3239d9..8d0f66966a 100644 --- a/plugins/python/exportlayers/uiexportlayers.py +++ b/plugins/python/exportlayers/uiexportlayers.py @@ -1,184 +1,184 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from . import exportlayersdialog from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QFormLayout, QListWidget, QHBoxLayout, QDialogButtonBox, QVBoxLayout, QFrame, QPushButton, QAbstractScrollArea, QLineEdit, QMessageBox, QFileDialog, QCheckBox, QSpinBox, QComboBox) import os import errno import krita class UIExportLayers(object): def __init__(self): self.mainDialog = exportlayersdialog.ExportLayersDialog() self.mainLayout = QVBoxLayout(self.mainDialog) self.formLayout = QFormLayout() self.documentLayout = QVBoxLayout() self.directorySelectorLayout = QHBoxLayout() self.optionsLayout = QVBoxLayout() self.resolutionLayout = QHBoxLayout() - self.refreshButton = QPushButton("Refresh") + self.refreshButton = QPushButton(i18n("Refresh")) self.widgetDocuments = QListWidget() self.directoryTextField = QLineEdit() - self.directoryDialogButton = QPushButton("...") - self.exportFilterLayersCheckBox = QCheckBox("Export filter layers") - self.batchmodeCheckBox = QCheckBox("Export in batchmode") - self.ignoreInvisibleLayersCheckBox = QCheckBox("Ignore invisible layers") + self.directoryDialogButton = QPushButton(i18n("...")) + self.exportFilterLayersCheckBox = QCheckBox(i18n("Export filter layers")) + self.batchmodeCheckBox = QCheckBox(i18n("Export in batchmode")) + self.ignoreInvisibleLayersCheckBox = QCheckBox(i18n("Ignore invisible layers")) self.xResSpinBox = QSpinBox() self.yResSpinBox = QSpinBox() self.formatsComboBox = QComboBox() self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.kritaInstance = krita.Krita.instance() self.documentsList = [] self.directoryTextField.setReadOnly(True) self.batchmodeCheckBox.setChecked(True) self.directoryDialogButton.clicked.connect(self._selectDir) self.widgetDocuments.currentRowChanged.connect(self._setResolution) self.refreshButton.clicked.connect(self.refreshButtonClicked) self.buttonBox.accepted.connect(self.confirmButton) self.buttonBox.rejected.connect(self.mainDialog.close) self.mainDialog.setWindowModality(Qt.NonModal) self.widgetDocuments.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) def initialize(self): self.loadDocuments() self.xResSpinBox.setRange(1, 10000) self.yResSpinBox.setRange(1, 10000) - self.formatsComboBox.addItem("jpeg") - self.formatsComboBox.addItem("png") + self.formatsComboBox.addItem(i18n("JPEG")) + self.formatsComboBox.addItem(i18n("PNG")) self.documentLayout.addWidget(self.widgetDocuments) self.documentLayout.addWidget(self.refreshButton) self.directorySelectorLayout.addWidget(self.directoryTextField) self.directorySelectorLayout.addWidget(self.directoryDialogButton) self.optionsLayout.addWidget(self.exportFilterLayersCheckBox) self.optionsLayout.addWidget(self.batchmodeCheckBox) self.optionsLayout.addWidget(self.ignoreInvisibleLayersCheckBox) self.resolutionLayout.addWidget(self.xResSpinBox) self.resolutionLayout.addWidget(self.yResSpinBox) - self.formLayout.addRow('Documents', self.documentLayout) - self.formLayout.addRow('Initial directory', self.directorySelectorLayout) - self.formLayout.addRow('Export options', self.optionsLayout) - self.formLayout.addRow('Resolution', self.resolutionLayout) - self.formLayout.addRow('Images Extensions', self.formatsComboBox) + self.formLayout.addRow(i18n("Documents:"), self.documentLayout) + self.formLayout.addRow(i18n("Initial directory:"), self.directorySelectorLayout) + self.formLayout.addRow(i18n("Export options:"), self.optionsLayout) + self.formLayout.addRow(i18n("Resolution:"), self.resolutionLayout) + self.formLayout.addRow(i18n("Images extensions:"), self.formatsComboBox) self.line = QFrame() self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.mainLayout.addLayout(self.formLayout) self.mainLayout.addWidget(self.line) self.mainLayout.addWidget(self.buttonBox) self.mainDialog.resize(500, 300) - self.mainDialog.setWindowTitle("Export Layers") + self.mainDialog.setWindowTitle(i18n("Export Layers")) self.mainDialog.setSizeGripEnabled(True) self.mainDialog.show() self.mainDialog.activateWindow() def loadDocuments(self): self.widgetDocuments.clear() self.documentsList = [document for document in self.kritaInstance.documents() if document.fileName()] for document in self.documentsList: self.widgetDocuments.addItem(document.fileName()) def refreshButtonClicked(self): self.loadDocuments() def confirmButton(self): selectedPaths = [item.text() for item in self.widgetDocuments.selectedItems()] selectedDocuments = [document for document in self.documentsList for path in selectedPaths if path == document.fileName()] self.msgBox = QMessageBox(self.mainDialog) if not selectedDocuments: - self.msgBox.setText("Select one document.") + self.msgBox.setText(i18n("Select one document.")) elif not self.directoryTextField.text(): - self.msgBox.setText("Select the initial directory.") + self.msgBox.setText(i18n("Select the initial directory.")) else: self.export(selectedDocuments[0]) - self.msgBox.setText("All layers has been exported.") + self.msgBox.setText(i18n("All layers has been exported.")) self.msgBox.exec_() def mkdir(self, directory): target_directory = self.directoryTextField.text() + directory if os.path.exists(target_directory) and os.path.isdir(target_directory): return try: os.makedirs(target_directory) except OSError as e: raise e def export(self, document): Application.setBatchmode(self.batchmodeCheckBox.isChecked()) documentName = document.fileName() if document.fileName() else 'Untitled' fileName, extension = os.path.splitext(os.path.basename(documentName)) self.mkdir('/' + fileName) self._exportLayers(document.rootNode(), self.formatsComboBox.currentText(), '/' + fileName) Application.setBatchmode(True) def _exportLayers(self, parentNode, fileFormat, parentDir): """ This method get all sub-nodes from the current node and export then in the defined format.""" for node in parentNode.childNodes(): newDir = '' if node.type() == 'grouplayer': newDir = os.path.join(parentDir, node.name()) self.mkdir(newDir) elif not self.exportFilterLayersCheckBox.isChecked() and 'filter' in node.type(): continue elif self.ignoreInvisibleLayersCheckBox.isChecked() and not node.visible(): continue else: nodeName = node.name() _fileFormat = self.formatsComboBox.currentText() if '[jpeg]' in nodeName: _fileFormat = 'jpeg' elif '[png]' in nodeName: _fileFormat = 'png' layerFileName = '{0}{1}/{2}.{3}'.format(self.directoryTextField.text(), parentDir, node.name(), _fileFormat) node.save(layerFileName, self.xResSpinBox.value(), self.yResSpinBox.value()) if node.childNodes(): self._exportLayers(node, fileFormat, newDir) def _selectDir(self): - directory = QFileDialog.getExistingDirectory(self.mainDialog, "Select a folder", os.path.expanduser("~"), QFileDialog.ShowDirsOnly) + directory = QFileDialog.getExistingDirectory(self.mainDialog, i18n("Select a Folder"), os.path.expanduser("~"), QFileDialog.ShowDirsOnly) self.directoryTextField.setText(directory) def _setResolution(self, index): document = self.documentsList[index] self.xResSpinBox.setValue(document.width()) self.yResSpinBox.setValue(document.height()) diff --git a/plugins/python/filtermanager/filtermanager.py b/plugins/python/filtermanager/filtermanager.py index 63ea46f32b..806ce710c9 100644 --- a/plugins/python/filtermanager/filtermanager.py +++ b/plugins/python/filtermanager/filtermanager.py @@ -1,34 +1,34 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import krita from . import uifiltermanager class FilterManagerExtension(krita.Extension): def __init__(self, parent): super(FilterManagerExtension, self).__init__(parent) def setup(self): pass def createActions(self, window): - action = window.createAction("filter_manager", "Filter Manager") - action.setToolTip("Plugin to filters management") + action = window.createAction("filter_manager", i18n("Filter Manager")) + action.setToolTip(i18n("Plugin to filters management.")) action.triggered.connect(self.initialize) def initialize(self): self.uifiltermanager = uifiltermanager.UIFilterManager() self.uifiltermanager.initialize() Scripter.addExtension(FilterManagerExtension(krita.Krita.instance())) diff --git a/plugins/python/filtermanager/uifiltermanager.py b/plugins/python/filtermanager/uifiltermanager.py index fa6b794686..a270bbdf85 100644 --- a/plugins/python/filtermanager/uifiltermanager.py +++ b/plugins/python/filtermanager/uifiltermanager.py @@ -1,107 +1,107 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from . import filtermanagerdialog from .components import (filtercombobox, filtermanagertreemodel) from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QFormLayout, QAbstractItemView, QDialogButtonBox, QVBoxLayout, QFrame, QAbstractScrollArea, QWidget, QTreeView) import krita class UIFilterManager(object): def __init__(self): self.mainDialog = filtermanagerdialog.FilterManagerDialog() self.mainLayout = QVBoxLayout(self.mainDialog) self.formLayout = QFormLayout() self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.kritaInstance = krita.Krita.instance() self._filters = sorted(self.kritaInstance.filters()) self._documents = self.kritaInstance.documents() self.treeModel = filtermanagertreemodel.FilterManagerTreeModel(self) self.documentsTreeView = QTreeView() self.filterComboBox = filtercombobox.FilterComboBox(self) self.buttonBox.accepted.connect(self.confirmButton) self.buttonBox.rejected.connect(self.mainDialog.close) self.documentsTreeView.setSelectionMode(QAbstractItemView.SingleSelection) self.mainDialog.setWindowModality(Qt.NonModal) def initialize(self): self.documentsTreeView.setModel(self.treeModel) - self.documentsTreeView.setWindowTitle("Document Tree Model") + self.documentsTreeView.setWindowTitle(i18n("Document Tree Model")) self.documentsTreeView.resizeColumnToContents(0) self.documentsTreeView.resizeColumnToContents(1) self.documentsTreeView.resizeColumnToContents(2) - self.formLayout.addRow("Filters", self.filterComboBox) + self.formLayout.addRow(i18n("Filters:"), self.filterComboBox) self.line = QFrame() self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.mainLayout.addWidget(self.documentsTreeView) self.mainLayout.addLayout(self.formLayout) self.mainLayout.addWidget(self.line) self.mainLayout.addWidget(self.buttonBox) self.mainDialog.resize(500, 300) - self.mainDialog.setWindowTitle("Filter Manager") + self.mainDialog.setWindowTitle(i18n("Filter Manager")) self.mainDialog.setSizeGripEnabled(True) self.mainDialog.show() self.mainDialog.activateWindow() def confirmButton(self): documentsIndexes = [] selectionModel = self.documentsTreeView.selectionModel() for index in selectionModel.selectedRows(): node = self.treeModel.data(index, Qt.UserRole + 1) documentIndex = self.treeModel.data(index, Qt.UserRole + 2) _type = self.treeModel.data(index, Qt.UserRole + 3) if _type == 'Document': self.applyFilterOverDocument(self.documents[documentIndex]) else: self.applyFilterOverNode(node, self.documents[documentIndex]) documentsIndexes.append(documentIndex) self.refreshDocumentsProjections(set(documentsIndexes)) def refreshDocumentsProjections(self, indexes): for index in indexes: document = self.documents[index] document.refreshProjection() def applyFilterOverNode(self, node, document): _filter = self.kritaInstance.filter(self.filterComboBox.currentText()) _filter.apply(node, 0, 0, document.width(), document.height()) def applyFilterOverDocument(self, document): """ This method applies the selected filter just to topLevelNodes, then if topLevelNodes are GroupLayers, that filter will not be applied. """ for node in document.topLevelNodes(): self.applyFilterOverNode(node, document) @property def filters(self): return self._filters @property def documents(self): return self._documents diff --git a/plugins/python/highpass/highpass.py b/plugins/python/highpass/highpass.py index a5a92a24da..3ce1616b3a 100644 --- a/plugins/python/highpass/highpass.py +++ b/plugins/python/highpass/highpass.py @@ -1,130 +1,130 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * class HighpassExtension(Extension): def __init__(self, parent): super().__init__(parent) def setup(self): pass def createActions(self, window): - action = window.createAction("high_pass_filter", "High Pass") + action = window.createAction("high_pass_filter", i18n("High Pass")) action.triggered.connect(self.showDialog) def showDialog(self): doc = Application.activeDocument() if doc == None: - QMessageBox.information(Application.activeWindow().qwindow(), "Highpass Filter", "There is no active image.") + QMessageBox.information(Application.activeWindow().qwindow(), i18n("High Pass Filter"), i18n("There is no active image.")) return self.dialog = QDialog(Application.activeWindow().qwindow()) self.intRadius = QSpinBox() self.intRadius.setValue(10) self.intRadius.setRange(2, 200) self.cmbMode = QComboBox() self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"]) - self.keepOriginal = QCheckBox("Keep Original Layer") + self.keepOriginal = QCheckBox(i18n("Keep original layer")) self.keepOriginal.setChecked(True) form = QFormLayout() - form.addRow("Filter Radius", self.intRadius) - form.addRow("Mode", self.cmbMode) + form.addRow(i18n("Filter radius:"), self.intRadius) + form.addRow(i18n("Mode:"), self.cmbMode) form.addRow("", self.keepOriginal) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.highpass) self.buttonBox.rejected.connect(self.dialog.reject) vbox = QVBoxLayout(self.dialog) vbox.addLayout(form) vbox.addWidget(self.buttonBox) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() def highpass(self): # XXX: Start undo macro image = Application.activeDocument() original = image.activeNode() working_layer = original # We can only highpass on paint layers if self.keepOriginal.isChecked() or original.type() != "paintlayer": working_layer = image.createNode("working", "paintlayer") working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile()) working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()), 0, 0, image.width(), image.height()) original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented image.setActiveNode(working_layer) colors_layer = None # if keeping colors if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3: colors_layer = working_layer.duplicate() # XXX: Unimplemented colors_layer.setName("colors") original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented # if greyscale, desature if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3): filter = Application.filter("desaturate") filter.apply(working_layer, 0, 0, image.width(), image.height()) # Duplicate on top and blur blur_layer = working_layer.duplicate() blur_layer.setName("blur") original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented # blur filter = Application.filter("gaussian blur") filter_configuration = filter.configuration() filter_configuration.setProperty("horizRadius", self.intRadius.value()) filter_configuration.setProperty("vertRadius", self.intRadius.value()) filter_configuration.setProperty("lockAspect", true) filter.setConfiguration(filter_configuration) filter.apply(blur_layer, 0, 0, image.width(), image.height()) if self.cmbMode.currentIndex() <= 3: blur_layer.setBlendingMode("grain_extract") working_layer = image.mergeDown(blur_layer) # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier. if self.cmbMode.currentIndex() == 3: working_layer.setBlendingMode("value") working_layer = image.mergeDown(working_layer) # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier. if self.cmbMode.currentIndex() == 1: # get the average color of the entire image # clear the colors layer to the given color working_layer = image.mergeDown(working_layer) else: # Mode == 4, RedRobes image.setActiveNode(blur_layer) # Get the average color of the input layer # copy the solid colour layer # copy the blurred layer # XXX: End undo macro Scripter.addExtension(HighpassExtension(Krita.instance())) diff --git a/plugins/python/scripter/scripter.py b/plugins/python/scripter/scripter.py index 35b63d1bd9..30192234e2 100644 --- a/plugins/python/scripter/scripter.py +++ b/plugins/python/scripter/scripter.py @@ -1,44 +1,44 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtCore import QSettings, QStandardPaths from krita import Krita, Extension from . import uicontroller, documentcontroller, debugcontroller class ScripterExtension(Extension): def __init__(self, parent): super(ScripterExtension, self).__init__(parent) def setup(self): pass def createActions(self, window): - action = window.createAction("python_scripter", "Scripter") + action = window.createAction("python_scripter", i18n("Scripter")) action.triggered.connect(self.initialize) def initialize(self): configPath = QStandardPaths.writableLocation(QStandardPaths.GenericConfigLocation) self.settings = QSettings(configPath + '/krita-scripterrc', QSettings.IniFormat) self.uicontroller = uicontroller.UIController() self.documentcontroller = documentcontroller.DocumentController() self.debugcontroller = debugcontroller.DebugController(self) self.uicontroller.initialize(self) Krita.instance().addExtension(ScripterExtension(Krita.instance())) diff --git a/plugins/python/tenbrushes/tenbrushes.py b/plugins/python/tenbrushes/tenbrushes.py index ee0387ac1a..d640a998f6 100644 --- a/plugins/python/tenbrushes/tenbrushes.py +++ b/plugins/python/tenbrushes/tenbrushes.py @@ -1,75 +1,75 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import krita from . import uitenbrushes class TenBrushesExtension(krita.Extension): def __init__(self, parent): super(TenBrushesExtension, self).__init__(parent) self.actions = [] self.buttons = [] self.selectedPresets = [] self.oldPreset = None def setup(self): self.readSettings() def createActions(self, window): - action = window.createAction("ten_brushes", "Ten Brushes") - action.setToolTip("Assign ten brush presets to ten shortcuts.") + action = window.createAction("ten_brushes", i18n("Ten Brushes")) + action.setToolTip(i18n("Assign ten brush presets to ten shortcuts.")) action.triggered.connect(self.initialize) self.loadActions(window) def initialize(self): self.uitenbrushes = uitenbrushes.UITenBrushes() self.uitenbrushes.initialize(self) def readSettings(self): self.selectedPresets = Application.readSetting("", "tenbrushes", "").split(',') def writeSettings(self): presets = [] for index, button in enumerate(self.buttons): self.actions[index].preset = button.preset presets.append(button.preset) Application.writeSetting("", "tenbrushes", ','.join(map(str, presets))) def loadActions(self, window): allPresets = Application.resources("preset") for index, item in enumerate(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']): - action = window.createAction("activate_preset_" + item, "Activate Brush Preset " + item, "") + action = window.createAction("activate_preset_" + item, str(i18n("Activate Brush Preset {num}")).format(num=item), "") action.triggered.connect(self.activatePreset) if index < len(self.selectedPresets) and self.selectedPresets[index] in allPresets: action.preset = self.selectedPresets[index] else: action.preset = None self.actions.append(action) def activatePreset(self): allPresets = Application.resources("preset") if Application.activeWindow() and len(Application.activeWindow().views()) > 0 and self.sender().preset in allPresets: currentPreset = Application.activeWindow().views()[0].currentBrushPreset() if self.sender().preset == currentPreset.name(): Application.activeWindow().views()[0].activateResource(self.oldPreset) else: self.oldPreset = Application.activeWindow().views()[0].currentBrushPreset() Application.activeWindow().views()[0].activateResource(allPresets[self.sender().preset]) Scripter.addExtension(TenBrushesExtension(Application)) diff --git a/plugins/python/tenbrushes/uitenbrushes.py b/plugins/python/tenbrushes/uitenbrushes.py index 72826374cc..fbbe040d40 100644 --- a/plugins/python/tenbrushes/uitenbrushes.py +++ b/plugins/python/tenbrushes/uitenbrushes.py @@ -1,75 +1,75 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QPixmap, QIcon from PyQt5.QtWidgets import (QDialogButtonBox, QLabel, QVBoxLayout, QHBoxLayout) from . import tenbrushesdialog, dropbutton import krita class UITenBrushes(object): def __init__(self): self.kritaInstance = krita.Krita.instance() self.mainDialog = tenbrushesdialog.TenBrushesDialog(self, self.kritaInstance.activeWindow().qwindow()) self.buttonBox = QDialogButtonBox(self.mainDialog) self.vbox = QVBoxLayout(self.mainDialog) self.hbox = QHBoxLayout(self.mainDialog) self.buttonBox.accepted.connect(self.mainDialog.accept) self.buttonBox.rejected.connect(self.mainDialog.reject) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.presetChooser = krita.PresetChooser(self.mainDialog) def initialize(self, tenbrushes): self.tenbrushes = tenbrushes self.loadButtons() self.vbox.addLayout(self.hbox) - self.vbox.addWidget(QLabel("Select the brush preset, then click on the button you want to use to select the preset")) + self.vbox.addWidget(QLabel(i18n("Select the brush preset, then click on the button you want to use to select the preset"))) self.vbox.addWidget(self.presetChooser) self.vbox.addWidget(self.buttonBox) self.mainDialog.show() self.mainDialog.activateWindow() self.mainDialog.exec_() def loadButtons(self): self.tenbrushes.buttons = [] allPresets = Application.resources("preset") for index, item in enumerate(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']): buttonLayout = QVBoxLayout() button = dropbutton.DropButton(self.mainDialog) button.setObjectName(item) button.clicked.connect(button.selectPreset) button.presetChooser = self.presetChooser if self.tenbrushes.actions[index] and self.tenbrushes.actions[index].preset and self.tenbrushes.actions[index].preset in allPresets: p = allPresets[self.tenbrushes.actions[index].preset] button.preset = p.name() button.setIcon(QIcon(QPixmap.fromImage(p.image()))) buttonLayout.addWidget(button) label = QLabel(self.tenbrushes.actions[index].shortcut().toString()) label.setAlignment(Qt.AlignHCenter) buttonLayout.addWidget(label) self.hbox.addLayout(buttonLayout) self.tenbrushes.buttons.append(button) diff --git a/plugins/python/tenscripts/tenscripts.py b/plugins/python/tenscripts/tenscripts.py index 983b977721..32a375b361 100644 --- a/plugins/python/tenscripts/tenscripts.py +++ b/plugins/python/tenscripts/tenscripts.py @@ -1,93 +1,93 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' import sys from PyQt5.QtWidgets import QMessageBox import krita from . import uitenscripts if sys.version_info[0] > 2: import importlib else: import imp class TenScriptsExtension(krita.Extension): def __init__(self, parent): super(TenScriptsExtension, self).__init__(parent) self.actions = [] self.scripts = [] def setup(self): self.readSettings() def createActions(self, window): - action = window.createAction("ten_scripts", "Ten Scripts") - action.setToolTip("Assign ten scripts to ten shortcuts.") + action = window.createAction("ten_scripts", i18n("Ten Scripts")) + action.setToolTip(i18n("Assign ten scripts to ten shortcuts.")) action.triggered.connect(self.initialize) self.loadActions(window) def initialize(self): self.uitenscripts = uitenscripts.UITenScripts() self.uitenscripts.initialize(self) def readSettings(self): self.scripts = Application.readSetting("tenscripts", "scripts", "").split(',') def writeSettings(self): saved_scripts = self.uitenscripts.saved_scripts() for index, script in enumerate(saved_scripts): self.actions[index].script = script Application.writeSetting("tenscripts", "scripts", ','.join(map(str, saved_scripts))) def loadActions(self, window): for index, item in enumerate(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']): - action = window.createAction("execute_script_" + item, "Execute Script " + item, "") + action = window.createAction("execute_script_" + item, str(i18n("Execute Script {num}")).format(num=item), "") action.script = None action.triggered.connect(self._executeScript) if index < len(self.scripts): action.script = self.scripts[index] self.actions.append(action) def _executeScript(self): script = self.sender().script if script: try: if sys.version_info[0] > 2: spec = importlib.util.spec_from_file_location("users_script", script) users_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(users_module) else: users_module = imp.load_source("users_script", script) if hasattr(users_module, 'main') and callable(users_module.main): users_module.main() - self.showMessage('script {0} executed'.format(self.sender().script)) + self.showMessage(str(i18n("Script {0} executed")).format(self.sender().script)) except Exception as e: self.showMessage(str(e)) else: - self.showMessage("You didn't assign a script to that action") + self.showMessage(i18n("You did not assign a script to that action")) def showMessage(self, message): self.msgBox = QMessageBox(Application.activeWindow().qwindow()) self.msgBox.setText(message) self.msgBox.exec_() Scripter.addExtension(TenScriptsExtension(Application)) diff --git a/plugins/python/tenscripts/uitenscripts.py b/plugins/python/tenscripts/uitenscripts.py index 2eec3c15b6..6ac83c9d48 100644 --- a/plugins/python/tenscripts/uitenscripts.py +++ b/plugins/python/tenscripts/uitenscripts.py @@ -1,109 +1,109 @@ ''' This script is licensed CC 0 1.0, so that you can learn from it. ------ CC 0 1.0 --------------- The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. https://creativecommons.org/publicdomain/zero/1.0/legalcode ''' from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QScrollArea, QGridLayout, QFileDialog, QLabel, QDialogButtonBox) from . import tenscriptsdialog import krita class UITenScripts(object): def __init__(self): self.kritaInstance = krita.Krita.instance() self.mainDialog = tenscriptsdialog.TenScriptsDialog(self, self.kritaInstance.activeWindow().qwindow()) self.buttonBox = QDialogButtonBox(self.mainDialog) self.layout = QVBoxLayout(self.mainDialog) self.baseWidget = QWidget() self.baseArea = QWidget() self.scrollArea = QScrollArea() self.scriptsLayout = QGridLayout() self.buttonBox.accepted.connect(self.mainDialog.accept) self.buttonBox.rejected.connect(self.mainDialog.reject) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.scrollArea.setWidgetResizable(True) def initialize(self, tenscripts): self.tenscripts = tenscripts self._loadGridLayout() self._fillScripts() self.baseArea.setLayout(self.scriptsLayout) self.scrollArea.setWidget(self.baseArea) self.layout.addWidget(self.scrollArea) self.layout.addWidget(self.buttonBox) self.mainDialog.show() self.mainDialog.activateWindow() self.mainDialog.exec_() def addNewRow(self, key): rowPosition = self.scriptsLayout.rowCount() rowLayout = QHBoxLayout() label = QLabel() directoryTextField = QLineEdit() - directoryDialogButton = QPushButton("...") + directoryDialogButton = QPushButton(i18n("...")) directoryTextField.setReadOnly(True) label.setText(self.tenscripts.actions[key].shortcut().toString()) - directoryTextField.setToolTip("Selected Path") - directoryDialogButton.setToolTip("Select the script") + directoryTextField.setToolTip(i18n("Selected path")) + directoryDialogButton.setToolTip(i18n("Select the script")) directoryDialogButton.clicked.connect(self._selectScript) self.scriptsLayout.addWidget(label, rowPosition, 0, Qt.AlignLeft|Qt.AlignTop) self.scriptsLayout.addWidget(directoryTextField, rowPosition, 1, Qt.AlignLeft|Qt.AlignTop) self.scriptsLayout.addWidget(directoryDialogButton, rowPosition, 2, Qt.AlignLeft|Qt.AlignTop) def saved_scripts(self): _saved_scripts = [] index = 0 for row in range(self.scriptsLayout.rowCount()-1): textField = self.scriptsLayout.itemAt(index + 1).widget() if textField.text(): _saved_scripts.append(textField.text()) index += 3 return _saved_scripts def _selectScript(self): dialog = QFileDialog(self.mainDialog) - dialog.setNameFilter('Python files (*.py)') + dialog.setNameFilter(i18n("Python files (*.py)")) if dialog.exec_(): selectedFile = dialog.selectedFiles()[0] obj = self.mainDialog.sender() textField = self.scriptsLayout.itemAt(self.scriptsLayout.indexOf(obj)-1).widget() textField.setText(selectedFile) def _loadGridLayout(self): for item in range(0, 10): self.addNewRow(item) def _fillScripts(self): scripts = self.tenscripts.scripts index = 0 for row in range(self.scriptsLayout.rowCount()-1): if row >= len(scripts): return textField = self.scriptsLayout.itemAt(index + 1).widget() textField.setText(scripts[row]) index += 3