1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a dialog to enter data for an image markup.
8"""
9
10import contextlib
11
12from PyQt5.QtCore import pyqtSlot, QSize
13from PyQt5.QtGui import QImage, QImageReader
14from PyQt5.QtWidgets import QDialog, QDialogButtonBox
15
16from E5Gui.E5PathPicker import E5PathPickerModes
17
18from .Ui_ImageMarkupDialog import Ui_ImageMarkupDialog
19
20
21class ImageMarkupDialog(QDialog, Ui_ImageMarkupDialog):
22    """
23    Class implementing a dialog to enter data for an image markup.
24    """
25    HtmlMode = 0
26    MarkDownMode = 1
27    RestMode = 2
28
29    def __init__(self, mode, parent=None):
30        """
31        Constructor
32
33        @param mode mode of the dialog
34        @type int
35        @param parent reference to the parent widget
36        @type QWidget
37        """
38        super().__init__(parent)
39        self.setupUi(self)
40
41        if mode == ImageMarkupDialog.MarkDownMode:
42            self.sizeCheckBox.setEnabled(False)
43            self.aspectRatioCheckBox.setEnabled(False)
44            self.widthSpinBox.setEnabled(False)
45            self.heightSpinBox.setEnabled(False)
46        elif mode == ImageMarkupDialog.RestMode:
47            self.titleEdit.setEnabled(False)
48
49        self.__mode = mode
50        self.__originalImageSize = QSize()
51
52        filters = {
53            'bmp': self.tr("Windows Bitmap File (*.bmp)"),
54            'cur': self.tr("Windows Cursor File (*.cur)"),
55            'dds': self.tr("DirectDraw-Surface File (*.dds)"),
56            'gif': self.tr("Graphic Interchange Format File (*.gif)"),
57            'icns': self.tr("Apple Icon File (*.icns)"),
58            'ico': self.tr("Windows Icon File (*.ico)"),
59            'jp2': self.tr("JPEG2000 File (*.jp2)"),
60            'jpg': self.tr("JPEG File (*.jpg)"),
61            'jpeg': self.tr("JPEG File (*.jpeg)"),
62            'mng': self.tr("Multiple-Image Network Graphics File (*.mng)"),
63            'pbm': self.tr("Portable Bitmap File (*.pbm)"),
64            'pcx': self.tr("Paintbrush Bitmap File (*.pcx)"),
65            'pgm': self.tr("Portable Graymap File (*.pgm)"),
66            'png': self.tr("Portable Network Graphics File (*.png)"),
67            'ppm': self.tr("Portable Pixmap File (*.ppm)"),
68            'sgi': self.tr("Silicon Graphics Image File (*.sgi)"),
69            'svg': self.tr("Scalable Vector Graphics File (*.svg)"),
70            'svgz': self.tr("Compressed Scalable Vector Graphics File"
71                            " (*.svgz)"),
72            'tga': self.tr("Targa Graphic File (*.tga)"),
73            'tif': self.tr("TIFF File (*.tif)"),
74            'tiff': self.tr("TIFF File (*.tiff)"),
75            'wbmp': self.tr("WAP Bitmap File (*.wbmp)"),
76            'webp': self.tr("WebP Image File (*.webp)"),
77            'xbm': self.tr("X11 Bitmap File (*.xbm)"),
78            'xpm': self.tr("X11 Pixmap File (*.xpm)"),
79        }
80
81        inputFormats = []
82        readFormats = QImageReader.supportedImageFormats()
83        for readFormat in readFormats:
84            with contextlib.suppress(KeyError):
85                inputFormats.append(filters[bytes(readFormat).decode()])
86        inputFormats.sort()
87        inputFormats.append(self.tr("All Files (*)"))
88        if filters["png"] in inputFormats:
89            inputFormats.remove(filters["png"])
90            inputFormats.insert(0, filters["png"])
91        self.imagePicker.setFilters(';;'.join(inputFormats))
92        self.imagePicker.setMode(E5PathPickerModes.OpenFileMode)
93
94        self.sizeCheckBox.setChecked(True)
95        self.aspectRatioCheckBox.setChecked(True)
96
97        msh = self.minimumSizeHint()
98        self.resize(max(self.width(), msh.width()), msh.height())
99
100        self.__updateOkButton()
101
102    def __updateOkButton(self):
103        """
104        Private slot to set the state of the OK button.
105        """
106        enable = bool(self.imagePicker.text())
107        if self.__mode == ImageMarkupDialog.MarkDownMode:
108            enable = enable and bool(self.altTextEdit.text())
109
110        self.buttonBox.button(
111            QDialogButtonBox.StandardButton.Ok).setEnabled(enable)
112
113    @pyqtSlot(str)
114    def on_imagePicker_textChanged(self, address):
115        """
116        Private slot handling changes of the image path.
117
118        @param address image address (URL or local path)
119        @type str
120        """
121        if address and "://" not in address:
122            image = QImage(address)
123            # load the file to set the size spin boxes
124            if image.isNull():
125                self.widthSpinBox.setValue(0)
126                self.heightSpinBox.setValue(0)
127                self.__originalImageSize = QSize()
128                self.__aspectRatio = 1
129            else:
130                self.widthSpinBox.setValue(image.width())
131                self.heightSpinBox.setValue(image.height())
132                self.__originalImageSize = image.size()
133                self.__aspectRatio = (
134                    float(self.__originalImageSize.height()) /
135                    self.__originalImageSize.width()
136                )
137        else:
138            self.widthSpinBox.setValue(0)
139            self.heightSpinBox.setValue(0)
140            self.__originalImageSize = QSize()
141            self.__aspectRatio = 1
142
143        self.__updateOkButton()
144
145    @pyqtSlot(str)
146    def on_altTextEdit_textChanged(self, txt):
147        """
148        Private slot handling changes of the alternative text.
149
150        @param txt alternative text
151        @type str
152        """
153        self.__updateOkButton()
154
155    @pyqtSlot(bool)
156    def on_sizeCheckBox_toggled(self, checked):
157        """
158        Private slot to reset the width and height spin boxes.
159
160        @param checked flag indicating the state of the check box
161        @type bool
162        """
163        if checked:
164            self.widthSpinBox.setValue(self.__originalImageSize.width())
165            self.heightSpinBox.setValue(self.__originalImageSize.height())
166
167    @pyqtSlot(bool)
168    def on_aspectRatioCheckBox_toggled(self, checked):
169        """
170        Private slot to adjust the height to match the original aspect ratio.
171
172        @param checked flag indicating the state of the check box
173        @type bool
174        """
175        if checked and self.__originalImageSize.isValid():
176            height = self.widthSpinBox.value() * self.__aspectRatio
177            self.heightSpinBox.setValue(height)
178
179    @pyqtSlot(int)
180    def on_widthSpinBox_valueChanged(self, width):
181        """
182        Private slot to adjust the height spin box.
183
184        @param width width for the image
185        @type int
186        """
187        if (
188            self.aspectRatioCheckBox.isChecked() and
189            self.widthSpinBox.hasFocus()
190        ):
191            height = width * self.__aspectRatio
192            self.heightSpinBox.setValue(height)
193
194    @pyqtSlot(int)
195    def on_heightSpinBox_valueChanged(self, height):
196        """
197        Private slot to adjust the width spin box.
198
199        @param height height for the image
200        @type int
201        """
202        if (
203            self.aspectRatioCheckBox.isChecked() and
204            self.heightSpinBox.hasFocus()
205        ):
206            width = height / self.__aspectRatio
207            self.widthSpinBox.setValue(width)
208
209    def getData(self):
210        """
211        Public method to get the entered data.
212
213        @return tuple containing the image address, alternative text,
214            title text, flag to keep the original size, width and height
215        @rtype tuple of (str, str, str, bool, int, int)
216        """
217        return (
218            self.imagePicker.text(),
219            self.altTextEdit.text(),
220            self.titleEdit.text(),
221            self.sizeCheckBox.isChecked(),
222            self.widthSpinBox.value(),
223            self.heightSpinBox.value(),
224        )
225