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