1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the icon editor main window.
8"""
9
10import os
11import contextlib
12
13from PyQt5.QtCore import (
14    pyqtSignal, Qt, QSize, QSignalMapper, QFileInfo, QFile, QEvent
15)
16from PyQt5.QtGui import (
17    QPalette, QImage, QImageReader, QImageWriter, QKeySequence
18)
19from PyQt5.QtWidgets import QScrollArea, QLabel, QDockWidget, QWhatsThis
20
21from E5Gui.E5Action import E5Action, createActionGroup
22from E5Gui import E5FileDialog, E5MessageBox
23from E5Gui.E5MainWindow import E5MainWindow
24from E5Gui.E5ZoomWidget import E5ZoomWidget
25
26from .IconEditorGrid import IconEditorGrid
27
28import UI.PixmapCache
29import UI.Config
30
31import Preferences
32
33
34class IconEditorWindow(E5MainWindow):
35    """
36    Class implementing the web browser main window.
37
38    @signal editorClosed() emitted after the window was requested to close down
39    """
40    editorClosed = pyqtSignal()
41
42    windows = []
43
44    def __init__(self, fileName="", parent=None, fromEric=False,
45                 initShortcutsOnly=False, project=None):
46        """
47        Constructor
48
49        @param fileName name of a file to load on startup (string)
50        @param parent parent widget of this window (QWidget)
51        @param fromEric flag indicating whether it was called from within
52            eric (boolean)
53        @param initShortcutsOnly flag indicating to just initialize the
54            keyboard shortcuts (boolean)
55        @param project reference to the project object (Project)
56        """
57        super().__init__(parent)
58        self.setObjectName("eric6_icon_editor")
59
60        self.fromEric = fromEric
61        self.initShortcutsOnly = initShortcutsOnly
62        self.setWindowIcon(UI.PixmapCache.getIcon("iconEditor"))
63
64        if self.initShortcutsOnly:
65            self.__initActions()
66        else:
67            if not self.fromEric:
68                self.setStyle(Preferences.getUI("Style"),
69                              Preferences.getUI("StyleSheet"))
70            self.__editor = IconEditorGrid()
71            self.__scrollArea = QScrollArea()
72            self.__scrollArea.setWidget(self.__editor)
73            self.__scrollArea.viewport().setBackgroundRole(
74                QPalette.ColorRole.Dark)
75            self.__scrollArea.viewport().setAutoFillBackground(True)
76            self.setCentralWidget(self.__scrollArea)
77
78            g = Preferences.getGeometry("IconEditorGeometry")
79            if g.isEmpty():
80                s = QSize(600, 500)
81                self.resize(s)
82            else:
83                self.restoreGeometry(g)
84
85            self.__initActions()
86            self.__initMenus()
87            self.__initToolbars()
88            self.__createStatusBar()
89            self.__initFileFilters()
90            self.__createPaletteDock()
91
92            self.__palette.previewChanged(self.__editor.previewPixmap())
93            self.__palette.colorChanged(self.__editor.penColor())
94            self.__palette.setCompositingMode(self.__editor.compositingMode())
95
96            self.__class__.windows.append(self)
97
98            state = Preferences.getIconEditor("IconEditorState")
99            self.restoreState(state)
100
101            self.__editor.imageChanged.connect(self.__modificationChanged)
102            self.__editor.positionChanged.connect(self.__updatePosition)
103            self.__editor.sizeChanged.connect(self.__updateSize)
104            self.__editor.previewChanged.connect(self.__palette.previewChanged)
105            self.__editor.colorChanged.connect(self.__palette.colorChanged)
106            self.__palette.colorSelected.connect(self.__editor.setPenColor)
107            self.__palette.compositingChanged.connect(
108                self.__editor.setCompositingMode)
109
110            self.__setCurrentFile("")
111            if fileName:
112                self.__loadIconFile(fileName)
113
114            self.__checkActions()
115
116            self.__project = project
117            self.__lastOpenPath = ""
118            self.__lastSavePath = ""
119
120            self.grabGesture(Qt.GestureType.PinchGesture)
121
122    def __initFileFilters(self):
123        """
124        Private method to define the supported image file filters.
125        """
126        filters = {
127            'bmp': self.tr("Windows Bitmap File (*.bmp)"),
128            'cur': self.tr("Windows Cursor File (*.cur)"),
129            'dds': self.tr("DirectDraw-Surface File (*.dds)"),
130            'gif': self.tr("Graphic Interchange Format File (*.gif)"),
131            'icns': self.tr("Apple Icon File (*.icns)"),
132            'ico': self.tr("Windows Icon File (*.ico)"),
133            'jp2': self.tr("JPEG2000 File (*.jp2)"),
134            'jpg': self.tr("JPEG File (*.jpg)"),
135            'jpeg': self.tr("JPEG File (*.jpeg)"),
136            'mng': self.tr("Multiple-Image Network Graphics File (*.mng)"),
137            'pbm': self.tr("Portable Bitmap File (*.pbm)"),
138            'pcx': self.tr("Paintbrush Bitmap File (*.pcx)"),
139            'pgm': self.tr("Portable Graymap File (*.pgm)"),
140            'png': self.tr("Portable Network Graphics File (*.png)"),
141            'ppm': self.tr("Portable Pixmap File (*.ppm)"),
142            'sgi': self.tr("Silicon Graphics Image File (*.sgi)"),
143            'svg': self.tr("Scalable Vector Graphics File (*.svg)"),
144            'svgz': self.tr("Compressed Scalable Vector Graphics File"
145                            " (*.svgz)"),
146            'tga': self.tr("Targa Graphic File (*.tga)"),
147            'tif': self.tr("TIFF File (*.tif)"),
148            'tiff': self.tr("TIFF File (*.tiff)"),
149            'wbmp': self.tr("WAP Bitmap File (*.wbmp)"),
150            'webp': self.tr("WebP Image File (*.webp)"),
151            'xbm': self.tr("X11 Bitmap File (*.xbm)"),
152            'xpm': self.tr("X11 Pixmap File (*.xpm)"),
153        }
154
155        inputFormats = []
156        readFormats = QImageReader.supportedImageFormats()
157        for readFormat in readFormats:
158            with contextlib.suppress(KeyError):
159                inputFormats.append(filters[bytes(readFormat).decode()])
160        inputFormats.sort()
161        inputFormats.append(self.tr("All Files (*)"))
162        self.__inputFilter = ';;'.join(inputFormats)
163
164        outputFormats = []
165        writeFormats = QImageWriter.supportedImageFormats()
166        for writeFormat in writeFormats:
167            with contextlib.suppress(KeyError):
168                outputFormats.append(filters[bytes(writeFormat).decode()])
169        outputFormats.sort()
170        self.__outputFilter = ';;'.join(outputFormats)
171
172        self.__defaultFilter = filters['png']
173
174    def __initActions(self):
175        """
176        Private method to define the user interface actions.
177        """
178        # list of all actions
179        self.__actions = []
180
181        self.__initFileActions()
182        self.__initEditActions()
183        self.__initViewActions()
184        self.__initToolsActions()
185        self.__initHelpActions()
186
187    def __initFileActions(self):
188        """
189        Private method to define the file related user interface actions.
190        """
191        self.newAct = E5Action(
192            self.tr('New'),
193            UI.PixmapCache.getIcon("new"),
194            self.tr('&New'),
195            QKeySequence(self.tr("Ctrl+N", "File|New")),
196            0, self, 'iconEditor_file_new')
197        self.newAct.setStatusTip(self.tr('Create a new icon'))
198        self.newAct.setWhatsThis(self.tr(
199            """<b>New</b>"""
200            """<p>This creates a new icon.</p>"""
201        ))
202        self.newAct.triggered.connect(self.__newIcon)
203        self.__actions.append(self.newAct)
204
205        self.newWindowAct = E5Action(
206            self.tr('New Window'),
207            UI.PixmapCache.getIcon("newWindow"),
208            self.tr('New &Window'),
209            0, 0, self, 'iconEditor_file_new_window')
210        self.newWindowAct.setStatusTip(self.tr(
211            'Open a new icon editor window'))
212        self.newWindowAct.setWhatsThis(self.tr(
213            """<b>New Window</b>"""
214            """<p>This opens a new icon editor window.</p>"""
215        ))
216        self.newWindowAct.triggered.connect(self.__newWindow)
217        self.__actions.append(self.newWindowAct)
218
219        self.openAct = E5Action(
220            self.tr('Open'),
221            UI.PixmapCache.getIcon("open"),
222            self.tr('&Open...'),
223            QKeySequence(self.tr("Ctrl+O", "File|Open")),
224            0, self, 'iconEditor_file_open')
225        self.openAct.setStatusTip(self.tr('Open an icon file for editing'))
226        self.openAct.setWhatsThis(self.tr(
227            """<b>Open File</b>"""
228            """<p>This opens a new icon file for editing."""
229            """ It pops up a file selection dialog.</p>"""
230        ))
231        self.openAct.triggered.connect(self.__openIcon)
232        self.__actions.append(self.openAct)
233
234        self.saveAct = E5Action(
235            self.tr('Save'),
236            UI.PixmapCache.getIcon("fileSave"),
237            self.tr('&Save'),
238            QKeySequence(self.tr("Ctrl+S", "File|Save")),
239            0, self, 'iconEditor_file_save')
240        self.saveAct.setStatusTip(self.tr('Save the current icon'))
241        self.saveAct.setWhatsThis(self.tr(
242            """<b>Save File</b>"""
243            """<p>Save the contents of the icon editor window.</p>"""
244        ))
245        self.saveAct.triggered.connect(self.__saveIcon)
246        self.__actions.append(self.saveAct)
247
248        self.saveAsAct = E5Action(
249            self.tr('Save As'),
250            UI.PixmapCache.getIcon("fileSaveAs"),
251            self.tr('Save &As...'),
252            QKeySequence(self.tr("Shift+Ctrl+S", "File|Save As")),
253            0, self, 'iconEditor_file_save_as')
254        self.saveAsAct.setStatusTip(
255            self.tr('Save the current icon to a new file'))
256        self.saveAsAct.setWhatsThis(self.tr(
257            """<b>Save As...</b>"""
258            """<p>Saves the current icon to a new file.</p>"""
259        ))
260        self.saveAsAct.triggered.connect(self.__saveIconAs)
261        self.__actions.append(self.saveAsAct)
262
263        self.closeAct = E5Action(
264            self.tr('Close'),
265            UI.PixmapCache.getIcon("close"),
266            self.tr('&Close'),
267            QKeySequence(self.tr("Ctrl+W", "File|Close")),
268            0, self, 'iconEditor_file_close')
269        self.closeAct.setStatusTip(self.tr(
270            'Close the current icon editor window'))
271        self.closeAct.setWhatsThis(self.tr(
272            """<b>Close</b>"""
273            """<p>Closes the current icon editor window.</p>"""
274        ))
275        self.closeAct.triggered.connect(self.close)
276        self.__actions.append(self.closeAct)
277
278        self.closeAllAct = E5Action(
279            self.tr('Close All'),
280            self.tr('Close &All'),
281            0, 0, self, 'iconEditor_file_close_all')
282        self.closeAllAct.setStatusTip(self.tr(
283            'Close all icon editor windows'))
284        self.closeAllAct.setWhatsThis(self.tr(
285            """<b>Close All</b>"""
286            """<p>Closes all icon editor windows except the first one.</p>"""
287        ))
288        self.closeAllAct.triggered.connect(self.__closeAll)
289        self.__actions.append(self.closeAllAct)
290
291        self.closeOthersAct = E5Action(
292            self.tr('Close Others'),
293            self.tr('Close Others'),
294            0, 0, self, 'iconEditor_file_close_others')
295        self.closeOthersAct.setStatusTip(self.tr(
296            'Close all other icon editor windows'))
297        self.closeOthersAct.setWhatsThis(self.tr(
298            """<b>Close Others</b>"""
299            """<p>Closes all other icon editor windows.</p>"""
300        ))
301        self.closeOthersAct.triggered.connect(self.__closeOthers)
302        self.__actions.append(self.closeOthersAct)
303
304        self.exitAct = E5Action(
305            self.tr('Quit'),
306            UI.PixmapCache.getIcon("exit"),
307            self.tr('&Quit'),
308            QKeySequence(self.tr("Ctrl+Q", "File|Quit")),
309            0, self, 'iconEditor_file_quit')
310        self.exitAct.setStatusTip(self.tr('Quit the icon editor'))
311        self.exitAct.setWhatsThis(self.tr(
312            """<b>Quit</b>"""
313            """<p>Quit the icon editor.</p>"""
314        ))
315        if not self.fromEric:
316            self.exitAct.triggered.connect(self.__closeAll)
317        self.__actions.append(self.exitAct)
318
319    def __initEditActions(self):
320        """
321        Private method to create the Edit actions.
322        """
323        self.undoAct = E5Action(
324            self.tr('Undo'),
325            UI.PixmapCache.getIcon("editUndo"),
326            self.tr('&Undo'),
327            QKeySequence(self.tr("Ctrl+Z", "Edit|Undo")),
328            QKeySequence(self.tr("Alt+Backspace", "Edit|Undo")),
329            self, 'iconEditor_edit_undo')
330        self.undoAct.setStatusTip(self.tr('Undo the last change'))
331        self.undoAct.setWhatsThis(self.tr(
332            """<b>Undo</b>"""
333            """<p>Undo the last change done.</p>"""
334        ))
335        self.undoAct.triggered.connect(self.__editor.editUndo)
336        self.__actions.append(self.undoAct)
337
338        self.redoAct = E5Action(
339            self.tr('Redo'),
340            UI.PixmapCache.getIcon("editRedo"),
341            self.tr('&Redo'),
342            QKeySequence(self.tr("Ctrl+Shift+Z", "Edit|Redo")),
343            0, self, 'iconEditor_edit_redo')
344        self.redoAct.setStatusTip(self.tr('Redo the last change'))
345        self.redoAct.setWhatsThis(self.tr(
346            """<b>Redo</b>"""
347            """<p>Redo the last change done.</p>"""
348        ))
349        self.redoAct.triggered.connect(self.__editor.editRedo)
350        self.__actions.append(self.redoAct)
351
352        self.cutAct = E5Action(
353            self.tr('Cut'),
354            UI.PixmapCache.getIcon("editCut"),
355            self.tr('Cu&t'),
356            QKeySequence(self.tr("Ctrl+X", "Edit|Cut")),
357            QKeySequence(self.tr("Shift+Del", "Edit|Cut")),
358            self, 'iconEditor_edit_cut')
359        self.cutAct.setStatusTip(self.tr('Cut the selection'))
360        self.cutAct.setWhatsThis(self.tr(
361            """<b>Cut</b>"""
362            """<p>Cut the selected image area to the clipboard.</p>"""
363        ))
364        self.cutAct.triggered.connect(self.__editor.editCut)
365        self.__actions.append(self.cutAct)
366
367        self.copyAct = E5Action(
368            self.tr('Copy'),
369            UI.PixmapCache.getIcon("editCopy"),
370            self.tr('&Copy'),
371            QKeySequence(self.tr("Ctrl+C", "Edit|Copy")),
372            QKeySequence(self.tr("Ctrl+Ins", "Edit|Copy")),
373            self, 'iconEditor_edit_copy')
374        self.copyAct.setStatusTip(self.tr('Copy the selection'))
375        self.copyAct.setWhatsThis(self.tr(
376            """<b>Copy</b>"""
377            """<p>Copy the selected image area to the clipboard.</p>"""
378        ))
379        self.copyAct.triggered.connect(self.__editor.editCopy)
380        self.__actions.append(self.copyAct)
381
382        self.pasteAct = E5Action(
383            self.tr('Paste'),
384            UI.PixmapCache.getIcon("editPaste"),
385            self.tr('&Paste'),
386            QKeySequence(self.tr("Ctrl+V", "Edit|Paste")),
387            QKeySequence(self.tr("Shift+Ins", "Edit|Paste")),
388            self, 'iconEditor_edit_paste')
389        self.pasteAct.setStatusTip(self.tr('Paste the clipboard image'))
390        self.pasteAct.setWhatsThis(self.tr(
391            """<b>Paste</b>"""
392            """<p>Paste the clipboard image.</p>"""
393        ))
394        self.pasteAct.triggered.connect(self.__editor.editPaste)
395        self.__actions.append(self.pasteAct)
396
397        self.pasteNewAct = E5Action(
398            self.tr('Paste as New'),
399            self.tr('Paste as &New'),
400            0, 0, self, 'iconEditor_edit_paste_as_new')
401        self.pasteNewAct.setStatusTip(self.tr(
402            'Paste the clipboard image replacing the current one'))
403        self.pasteNewAct.setWhatsThis(self.tr(
404            """<b>Paste as New</b>"""
405            """<p>Paste the clipboard image replacing the current one.</p>"""
406        ))
407        self.pasteNewAct.triggered.connect(self.__editor.editPasteAsNew)
408        self.__actions.append(self.pasteNewAct)
409
410        self.deleteAct = E5Action(
411            self.tr('Clear'),
412            UI.PixmapCache.getIcon("editDelete"),
413            self.tr('Cl&ear'),
414            QKeySequence(self.tr("Alt+Shift+C", "Edit|Clear")),
415            0,
416            self, 'iconEditor_edit_clear')
417        self.deleteAct.setStatusTip(self.tr('Clear the icon image'))
418        self.deleteAct.setWhatsThis(self.tr(
419            """<b>Clear</b>"""
420            """<p>Clear the icon image and set it to be completely"""
421            """ transparent.</p>"""
422        ))
423        self.deleteAct.triggered.connect(self.__editor.editClear)
424        self.__actions.append(self.deleteAct)
425
426        self.selectAllAct = E5Action(
427            self.tr('Select All'),
428            self.tr('&Select All'),
429            QKeySequence(self.tr("Ctrl+A", "Edit|Select All")),
430            0,
431            self, 'iconEditor_edit_select_all')
432        self.selectAllAct.setStatusTip(self.tr(
433            'Select the complete icon image'))
434        self.selectAllAct.setWhatsThis(self.tr(
435            """<b>Select All</b>"""
436            """<p>Selects the complete icon image.</p>"""
437        ))
438        self.selectAllAct.triggered.connect(self.__editor.editSelectAll)
439        self.__actions.append(self.selectAllAct)
440
441        self.resizeAct = E5Action(
442            self.tr('Change Size'),
443            UI.PixmapCache.getIcon("transformResize"),
444            self.tr('Change Si&ze...'),
445            0, 0,
446            self, 'iconEditor_edit_change_size')
447        self.resizeAct.setStatusTip(self.tr('Change the icon size'))
448        self.resizeAct.setWhatsThis(self.tr(
449            """<b>Change Size...</b>"""
450            """<p>Changes the icon size.</p>"""
451        ))
452        self.resizeAct.triggered.connect(self.__editor.editResize)
453        self.__actions.append(self.resizeAct)
454
455        self.grayscaleAct = E5Action(
456            self.tr('Grayscale'),
457            UI.PixmapCache.getIcon("grayscale"),
458            self.tr('&Grayscale'),
459            0, 0,
460            self, 'iconEditor_edit_grayscale')
461        self.grayscaleAct.setStatusTip(self.tr(
462            'Change the icon to grayscale'))
463        self.grayscaleAct.setWhatsThis(self.tr(
464            """<b>Grayscale</b>"""
465            """<p>Changes the icon to grayscale.</p>"""
466        ))
467        self.grayscaleAct.triggered.connect(self.__editor.grayScale)
468        self.__actions.append(self.grayscaleAct)
469
470        self.redoAct.setEnabled(False)
471        self.__editor.canRedoChanged.connect(self.redoAct.setEnabled)
472
473        self.undoAct.setEnabled(False)
474        self.__editor.canUndoChanged.connect(self.undoAct.setEnabled)
475
476        self.cutAct.setEnabled(False)
477        self.copyAct.setEnabled(False)
478        self.__editor.selectionAvailable.connect(self.cutAct.setEnabled)
479        self.__editor.selectionAvailable.connect(self.copyAct.setEnabled)
480
481        self.pasteAct.setEnabled(self.__editor.canPaste())
482        self.pasteNewAct.setEnabled(self.__editor.canPaste())
483        self.__editor.clipboardImageAvailable.connect(
484            self.pasteAct.setEnabled)
485        self.__editor.clipboardImageAvailable.connect(
486            self.pasteNewAct.setEnabled)
487
488    def __initViewActions(self):
489        """
490        Private method to create the View actions.
491        """
492        self.zoomInAct = E5Action(
493            self.tr('Zoom in'),
494            UI.PixmapCache.getIcon("zoomIn"),
495            self.tr('Zoom &in'),
496            QKeySequence(self.tr("Ctrl++", "View|Zoom in")),
497            0, self, 'iconEditor_view_zoom_in')
498        self.zoomInAct.setStatusTip(self.tr('Zoom in on the icon'))
499        self.zoomInAct.setWhatsThis(self.tr(
500            """<b>Zoom in</b>"""
501            """<p>Zoom in on the icon. This makes the grid bigger.</p>"""
502        ))
503        self.zoomInAct.triggered.connect(self.__zoomIn)
504        self.__actions.append(self.zoomInAct)
505
506        self.zoomOutAct = E5Action(
507            self.tr('Zoom out'),
508            UI.PixmapCache.getIcon("zoomOut"),
509            self.tr('Zoom &out'),
510            QKeySequence(self.tr("Ctrl+-", "View|Zoom out")),
511            0, self, 'iconEditor_view_zoom_out')
512        self.zoomOutAct.setStatusTip(self.tr('Zoom out on the icon'))
513        self.zoomOutAct.setWhatsThis(self.tr(
514            """<b>Zoom out</b>"""
515            """<p>Zoom out on the icon. This makes the grid smaller.</p>"""
516        ))
517        self.zoomOutAct.triggered.connect(self.__zoomOut)
518        self.__actions.append(self.zoomOutAct)
519
520        self.zoomResetAct = E5Action(
521            self.tr('Zoom reset'),
522            UI.PixmapCache.getIcon("zoomReset"),
523            self.tr('Zoom &reset'),
524            QKeySequence(self.tr("Ctrl+0", "View|Zoom reset")),
525            0, self, 'iconEditor_view_zoom_reset')
526        self.zoomResetAct.setStatusTip(self.tr(
527            'Reset the zoom of the icon'))
528        self.zoomResetAct.setWhatsThis(self.tr(
529            """<b>Zoom reset</b>"""
530            """<p>Reset the zoom of the icon. """
531            """This sets the zoom factor to 100%.</p>"""
532        ))
533        self.zoomResetAct.triggered.connect(self.__zoomReset)
534        self.__actions.append(self.zoomResetAct)
535
536        self.showGridAct = E5Action(
537            self.tr('Show Grid'),
538            UI.PixmapCache.getIcon("grid"),
539            self.tr('Show &Grid'),
540            0, 0,
541            self, 'iconEditor_view_show_grid')
542        self.showGridAct.setStatusTip(self.tr(
543            'Toggle the display of the grid'))
544        self.showGridAct.setWhatsThis(self.tr(
545            """<b>Show Grid</b>"""
546            """<p>Toggle the display of the grid.</p>"""
547        ))
548        self.showGridAct.triggered[bool].connect(self.__editor.setGridEnabled)
549        self.__actions.append(self.showGridAct)
550        self.showGridAct.setCheckable(True)
551        self.showGridAct.setChecked(self.__editor.isGridEnabled())
552
553    def __initToolsActions(self):
554        """
555        Private method to create the View actions.
556        """
557        self.esm = QSignalMapper(self)
558        try:
559            self.esm.mappedInt.connect(self.__editor.setTool)
560        except AttributeError:
561            # pre Qt 5.15
562            self.esm.mapped[int].connect(self.__editor.setTool)
563
564        self.drawingActGrp = createActionGroup(self)
565        self.drawingActGrp.setExclusive(True)
566
567        self.drawPencilAct = E5Action(
568            self.tr('Freehand'),
569            UI.PixmapCache.getIcon("drawBrush"),
570            self.tr('&Freehand'),
571            0, 0,
572            self.drawingActGrp, 'iconEditor_tools_pencil')
573        self.drawPencilAct.setWhatsThis(self.tr(
574            """<b>Free hand</b>"""
575            """<p>Draws non linear lines.</p>"""
576        ))
577        self.drawPencilAct.setCheckable(True)
578        self.esm.setMapping(self.drawPencilAct, IconEditorGrid.Pencil)
579        self.drawPencilAct.triggered.connect(self.esm.map)
580        self.__actions.append(self.drawPencilAct)
581
582        self.drawColorPickerAct = E5Action(
583            self.tr('Color Picker'),
584            UI.PixmapCache.getIcon("colorPicker"),
585            self.tr('&Color Picker'),
586            0, 0,
587            self.drawingActGrp, 'iconEditor_tools_color_picker')
588        self.drawColorPickerAct.setWhatsThis(self.tr(
589            """<b>Color Picker</b>"""
590            """<p>The color of the pixel clicked on will become """
591            """the current draw color.</p>"""
592        ))
593        self.drawColorPickerAct.setCheckable(True)
594        self.esm.setMapping(self.drawColorPickerAct,
595                            IconEditorGrid.ColorPicker)
596        self.drawColorPickerAct.triggered.connect(self.esm.map)
597        self.__actions.append(self.drawColorPickerAct)
598
599        self.drawRectangleAct = E5Action(
600            self.tr('Rectangle'),
601            UI.PixmapCache.getIcon("drawRectangle"),
602            self.tr('&Rectangle'),
603            0, 0,
604            self.drawingActGrp, 'iconEditor_tools_rectangle')
605        self.drawRectangleAct.setWhatsThis(self.tr(
606            """<b>Rectangle</b>"""
607            """<p>Draw a rectangle.</p>"""
608        ))
609        self.drawRectangleAct.setCheckable(True)
610        self.esm.setMapping(self.drawRectangleAct, IconEditorGrid.Rectangle)
611        self.drawRectangleAct.triggered.connect(self.esm.map)
612        self.__actions.append(self.drawRectangleAct)
613
614        self.drawFilledRectangleAct = E5Action(
615            self.tr('Filled Rectangle'),
616            UI.PixmapCache.getIcon("drawRectangleFilled"),
617            self.tr('F&illed Rectangle'),
618            0, 0,
619            self.drawingActGrp, 'iconEditor_tools_filled_rectangle')
620        self.drawFilledRectangleAct.setWhatsThis(self.tr(
621            """<b>Filled Rectangle</b>"""
622            """<p>Draw a filled rectangle.</p>"""
623        ))
624        self.drawFilledRectangleAct.setCheckable(True)
625        self.esm.setMapping(self.drawFilledRectangleAct,
626                            IconEditorGrid.FilledRectangle)
627        self.drawFilledRectangleAct.triggered.connect(self.esm.map)
628        self.__actions.append(self.drawFilledRectangleAct)
629
630        self.drawCircleAct = E5Action(
631            self.tr('Circle'),
632            UI.PixmapCache.getIcon("drawCircle"),
633            self.tr('Circle'),
634            0, 0,
635            self.drawingActGrp, 'iconEditor_tools_circle')
636        self.drawCircleAct.setWhatsThis(self.tr(
637            """<b>Circle</b>"""
638            """<p>Draw a circle.</p>"""
639        ))
640        self.drawCircleAct.setCheckable(True)
641        self.esm.setMapping(self.drawCircleAct, IconEditorGrid.Circle)
642        self.drawCircleAct.triggered.connect(self.esm.map)
643        self.__actions.append(self.drawCircleAct)
644
645        self.drawFilledCircleAct = E5Action(
646            self.tr('Filled Circle'),
647            UI.PixmapCache.getIcon("drawCircleFilled"),
648            self.tr('Fille&d Circle'),
649            0, 0,
650            self.drawingActGrp, 'iconEditor_tools_filled_circle')
651        self.drawFilledCircleAct.setWhatsThis(self.tr(
652            """<b>Filled Circle</b>"""
653            """<p>Draw a filled circle.</p>"""
654        ))
655        self.drawFilledCircleAct.setCheckable(True)
656        self.esm.setMapping(self.drawFilledCircleAct,
657                            IconEditorGrid.FilledCircle)
658        self.drawFilledCircleAct.triggered.connect(self.esm.map)
659        self.__actions.append(self.drawFilledCircleAct)
660
661        self.drawEllipseAct = E5Action(
662            self.tr('Ellipse'),
663            UI.PixmapCache.getIcon("drawEllipse"),
664            self.tr('&Ellipse'),
665            0, 0,
666            self.drawingActGrp, 'iconEditor_tools_ellipse')
667        self.drawEllipseAct.setWhatsThis(self.tr(
668            """<b>Ellipse</b>"""
669            """<p>Draw an ellipse.</p>"""
670        ))
671        self.drawEllipseAct.setCheckable(True)
672        self.esm.setMapping(self.drawEllipseAct, IconEditorGrid.Ellipse)
673        self.drawEllipseAct.triggered.connect(self.esm.map)
674        self.__actions.append(self.drawEllipseAct)
675
676        self.drawFilledEllipseAct = E5Action(
677            self.tr('Filled Ellipse'),
678            UI.PixmapCache.getIcon("drawEllipseFilled"),
679            self.tr('Fille&d Elli&pse'),
680            0, 0,
681            self.drawingActGrp, 'iconEditor_tools_filled_ellipse')
682        self.drawFilledEllipseAct.setWhatsThis(self.tr(
683            """<b>Filled Ellipse</b>"""
684            """<p>Draw a filled ellipse.</p>"""
685        ))
686        self.drawFilledEllipseAct.setCheckable(True)
687        self.esm.setMapping(self.drawFilledEllipseAct,
688                            IconEditorGrid.FilledEllipse)
689        self.drawFilledEllipseAct.triggered.connect(self.esm.map)
690        self.__actions.append(self.drawFilledEllipseAct)
691
692        self.drawFloodFillAct = E5Action(
693            self.tr('Flood Fill'),
694            UI.PixmapCache.getIcon("drawFill"),
695            self.tr('Fl&ood Fill'),
696            0, 0,
697            self.drawingActGrp, 'iconEditor_tools_flood_fill')
698        self.drawFloodFillAct.setWhatsThis(self.tr(
699            """<b>Flood Fill</b>"""
700            """<p>Fill adjoining pixels with the same color with """
701            """the current color.</p>"""
702        ))
703        self.drawFloodFillAct.setCheckable(True)
704        self.esm.setMapping(self.drawFloodFillAct, IconEditorGrid.Fill)
705        self.drawFloodFillAct.triggered.connect(self.esm.map)
706        self.__actions.append(self.drawFloodFillAct)
707
708        self.drawLineAct = E5Action(
709            self.tr('Line'),
710            UI.PixmapCache.getIcon("drawLine"),
711            self.tr('&Line'),
712            0, 0,
713            self.drawingActGrp, 'iconEditor_tools_line')
714        self.drawLineAct.setWhatsThis(self.tr(
715            """<b>Line</b>"""
716            """<p>Draw a line.</p>"""
717        ))
718        self.drawLineAct.setCheckable(True)
719        self.esm.setMapping(self.drawLineAct, IconEditorGrid.Line)
720        self.drawLineAct.triggered.connect(self.esm.map)
721        self.__actions.append(self.drawLineAct)
722
723        self.drawEraserAct = E5Action(
724            self.tr('Eraser (Transparent)'),
725            UI.PixmapCache.getIcon("drawEraser"),
726            self.tr('Eraser (&Transparent)'),
727            0, 0,
728            self.drawingActGrp, 'iconEditor_tools_eraser')
729        self.drawEraserAct.setWhatsThis(self.tr(
730            """<b>Eraser (Transparent)</b>"""
731            """<p>Erase pixels by setting them to transparent.</p>"""
732        ))
733        self.drawEraserAct.setCheckable(True)
734        self.esm.setMapping(self.drawEraserAct, IconEditorGrid.Rubber)
735        self.drawEraserAct.triggered.connect(self.esm.map)
736        self.__actions.append(self.drawEraserAct)
737
738        self.drawRectangleSelectionAct = E5Action(
739            self.tr('Rectangular Selection'),
740            UI.PixmapCache.getIcon("selectRectangle"),
741            self.tr('Rect&angular Selection'),
742            0, 0,
743            self.drawingActGrp, 'iconEditor_tools_selection_rectangle')
744        self.drawRectangleSelectionAct.setWhatsThis(self.tr(
745            """<b>Rectangular Selection</b>"""
746            """<p>Select a rectangular section of the icon using"""
747            """ the mouse.</p>"""
748        ))
749        self.drawRectangleSelectionAct.setCheckable(True)
750        self.esm.setMapping(self.drawRectangleSelectionAct,
751                            IconEditorGrid.RectangleSelection)
752        self.drawRectangleSelectionAct.triggered.connect(self.esm.map)
753        self.__actions.append(self.drawRectangleSelectionAct)
754
755        self.drawCircleSelectionAct = E5Action(
756            self.tr('Circular Selection'),
757            UI.PixmapCache.getIcon("selectCircle"),
758            self.tr('Rect&angular Selection'),
759            0, 0,
760            self.drawingActGrp, 'iconEditor_tools_selection_circle')
761        self.drawCircleSelectionAct.setWhatsThis(self.tr(
762            """<b>Circular Selection</b>"""
763            """<p>Select a circular section of the icon using"""
764            """ the mouse.</p>"""
765        ))
766        self.drawCircleSelectionAct.setCheckable(True)
767        self.esm.setMapping(self.drawCircleSelectionAct,
768                            IconEditorGrid.CircleSelection)
769        self.drawCircleSelectionAct.triggered.connect(self.esm.map)
770        self.__actions.append(self.drawCircleSelectionAct)
771
772        self.drawPencilAct.setChecked(True)
773
774    def __initHelpActions(self):
775        """
776        Private method to create the Help actions.
777        """
778        self.aboutAct = E5Action(
779            self.tr('About'),
780            self.tr('&About'),
781            0, 0, self, 'iconEditor_help_about')
782        self.aboutAct.setStatusTip(self.tr(
783            'Display information about this software'))
784        self.aboutAct.setWhatsThis(self.tr(
785            """<b>About</b>"""
786            """<p>Display some information about this software.</p>"""))
787        self.aboutAct.triggered.connect(self.__about)
788        self.__actions.append(self.aboutAct)
789
790        self.aboutQtAct = E5Action(
791            self.tr('About Qt'),
792            self.tr('About &Qt'),
793            0, 0, self, 'iconEditor_help_about_qt')
794        self.aboutQtAct.setStatusTip(
795            self.tr('Display information about the Qt toolkit'))
796        self.aboutQtAct.setWhatsThis(self.tr(
797            """<b>About Qt</b>"""
798            """<p>Display some information about the Qt toolkit.</p>"""
799        ))
800        self.aboutQtAct.triggered.connect(self.__aboutQt)
801        self.__actions.append(self.aboutQtAct)
802
803        self.whatsThisAct = E5Action(
804            self.tr('What\'s This?'),
805            UI.PixmapCache.getIcon("whatsThis"),
806            self.tr('&What\'s This?'),
807            QKeySequence(self.tr("Shift+F1", "Help|What's This?'")),
808            0, self, 'iconEditor_help_whats_this')
809        self.whatsThisAct.setStatusTip(self.tr('Context sensitive help'))
810        self.whatsThisAct.setWhatsThis(self.tr(
811            """<b>Display context sensitive help</b>"""
812            """<p>In What's This? mode, the mouse cursor shows an arrow"""
813            """ with a question mark, and you can click on the interface"""
814            """ elements to get a short description of what they do and"""
815            """ how to use them. In dialogs, this feature can be accessed"""
816            """ using the context help button in the titlebar.</p>"""
817        ))
818        self.whatsThisAct.triggered.connect(self.__whatsThis)
819        self.__actions.append(self.whatsThisAct)
820
821    def __initMenus(self):
822        """
823        Private method to create the menus.
824        """
825        mb = self.menuBar()
826
827        menu = mb.addMenu(self.tr('&File'))
828        menu.setTearOffEnabled(True)
829        menu.addAction(self.newAct)
830        menu.addAction(self.newWindowAct)
831        menu.addAction(self.openAct)
832        menu.addSeparator()
833        menu.addAction(self.saveAct)
834        menu.addAction(self.saveAsAct)
835        menu.addSeparator()
836        menu.addAction(self.closeAct)
837        menu.addAction(self.closeOthersAct)
838        if self.fromEric:
839            menu.addAction(self.closeAllAct)
840        else:
841            menu.addSeparator()
842            menu.addAction(self.exitAct)
843
844        menu = mb.addMenu(self.tr("&Edit"))
845        menu.setTearOffEnabled(True)
846        menu.addAction(self.undoAct)
847        menu.addAction(self.redoAct)
848        menu.addSeparator()
849        menu.addAction(self.cutAct)
850        menu.addAction(self.copyAct)
851        menu.addAction(self.pasteAct)
852        menu.addAction(self.pasteNewAct)
853        menu.addAction(self.deleteAct)
854        menu.addSeparator()
855        menu.addAction(self.selectAllAct)
856        menu.addSeparator()
857        menu.addAction(self.resizeAct)
858        menu.addAction(self.grayscaleAct)
859
860        menu = mb.addMenu(self.tr('&View'))
861        menu.setTearOffEnabled(True)
862        menu.addAction(self.zoomInAct)
863        menu.addAction(self.zoomResetAct)
864        menu.addAction(self.zoomOutAct)
865        menu.addSeparator()
866        menu.addAction(self.showGridAct)
867
868        menu = mb.addMenu(self.tr('&Tools'))
869        menu.setTearOffEnabled(True)
870        menu.addAction(self.drawPencilAct)
871        menu.addAction(self.drawColorPickerAct)
872        menu.addAction(self.drawRectangleAct)
873        menu.addAction(self.drawFilledRectangleAct)
874        menu.addAction(self.drawCircleAct)
875        menu.addAction(self.drawFilledCircleAct)
876        menu.addAction(self.drawEllipseAct)
877        menu.addAction(self.drawFilledEllipseAct)
878        menu.addAction(self.drawFloodFillAct)
879        menu.addAction(self.drawLineAct)
880        menu.addAction(self.drawEraserAct)
881        menu.addSeparator()
882        menu.addAction(self.drawRectangleSelectionAct)
883        menu.addAction(self.drawCircleSelectionAct)
884
885        mb.addSeparator()
886
887        menu = mb.addMenu(self.tr("&Help"))
888        menu.addAction(self.aboutAct)
889        menu.addAction(self.aboutQtAct)
890        menu.addSeparator()
891        menu.addAction(self.whatsThisAct)
892
893    def __initToolbars(self):
894        """
895        Private method to create the toolbars.
896        """
897        filetb = self.addToolBar(self.tr("File"))
898        filetb.setObjectName("FileToolBar")
899        filetb.setIconSize(UI.Config.ToolBarIconSize)
900        filetb.addAction(self.newAct)
901        filetb.addAction(self.newWindowAct)
902        filetb.addAction(self.openAct)
903        filetb.addSeparator()
904        filetb.addAction(self.saveAct)
905        filetb.addAction(self.saveAsAct)
906        filetb.addSeparator()
907        filetb.addAction(self.closeAct)
908        if not self.fromEric:
909            filetb.addAction(self.exitAct)
910
911        edittb = self.addToolBar(self.tr("Edit"))
912        edittb.setObjectName("EditToolBar")
913        edittb.setIconSize(UI.Config.ToolBarIconSize)
914        edittb.addAction(self.undoAct)
915        edittb.addAction(self.redoAct)
916        edittb.addSeparator()
917        edittb.addAction(self.cutAct)
918        edittb.addAction(self.copyAct)
919        edittb.addAction(self.pasteAct)
920        edittb.addSeparator()
921        edittb.addAction(self.resizeAct)
922        edittb.addAction(self.grayscaleAct)
923
924        viewtb = self.addToolBar(self.tr("View"))
925        viewtb.setObjectName("ViewToolBar")
926        viewtb.setIconSize(UI.Config.ToolBarIconSize)
927        viewtb.addAction(self.showGridAct)
928
929        toolstb = self.addToolBar(self.tr("Tools"))
930        toolstb.setObjectName("ToolsToolBar")
931        toolstb.setIconSize(UI.Config.ToolBarIconSize)
932        toolstb.addAction(self.drawPencilAct)
933        toolstb.addAction(self.drawColorPickerAct)
934        toolstb.addAction(self.drawRectangleAct)
935        toolstb.addAction(self.drawFilledRectangleAct)
936        toolstb.addAction(self.drawCircleAct)
937        toolstb.addAction(self.drawFilledCircleAct)
938        toolstb.addAction(self.drawEllipseAct)
939        toolstb.addAction(self.drawFilledEllipseAct)
940        toolstb.addAction(self.drawFloodFillAct)
941        toolstb.addAction(self.drawLineAct)
942        toolstb.addAction(self.drawEraserAct)
943        toolstb.addSeparator()
944        toolstb.addAction(self.drawRectangleSelectionAct)
945        toolstb.addAction(self.drawCircleSelectionAct)
946
947        helptb = self.addToolBar(self.tr("Help"))
948        helptb.setObjectName("HelpToolBar")
949        helptb.setIconSize(UI.Config.ToolBarIconSize)
950        helptb.addAction(self.whatsThisAct)
951
952    def __createStatusBar(self):
953        """
954        Private method to initialize the status bar.
955        """
956        self.__statusBar = self.statusBar()
957        self.__statusBar.setSizeGripEnabled(True)
958
959        self.__sbSize = QLabel(self.__statusBar)
960        self.__statusBar.addPermanentWidget(self.__sbSize)
961        self.__sbSize.setWhatsThis(self.tr(
962            """<p>This part of the status bar displays the icon size.</p>"""
963        ))
964        self.__updateSize(*self.__editor.iconSize())
965
966        self.__sbPos = QLabel(self.__statusBar)
967        self.__statusBar.addPermanentWidget(self.__sbPos)
968        self.__sbPos.setWhatsThis(self.tr(
969            """<p>This part of the status bar displays the cursor"""
970            """ position.</p>"""
971        ))
972        self.__updatePosition(0, 0)
973
974        self.__zoomWidget = E5ZoomWidget(
975            UI.PixmapCache.getPixmap("zoomOut"),
976            UI.PixmapCache.getPixmap("zoomIn"),
977            UI.PixmapCache.getPixmap("zoomReset"), self)
978        self.__zoomWidget.setMinimum(IconEditorGrid.ZoomMinimum)
979        self.__zoomWidget.setMaximum(IconEditorGrid.ZoomMaximum)
980        self.__zoomWidget.setDefault(IconEditorGrid.ZoomDefault)
981        self.__zoomWidget.setSingleStep(IconEditorGrid.ZoomStep)
982        self.__zoomWidget.setPercent(IconEditorGrid.ZoomPercent)
983        self.__statusBar.addPermanentWidget(self.__zoomWidget)
984        self.__zoomWidget.setValue(self.__editor.zoomFactor())
985        self.__zoomWidget.valueChanged.connect(self.__editor.setZoomFactor)
986        self.__editor.zoomChanged.connect(self.__zoomWidget.setValue)
987
988        self.__updateZoom()
989
990    def __createPaletteDock(self):
991        """
992        Private method to initialize the palette dock widget.
993        """
994        from .IconEditorPalette import IconEditorPalette
995
996        self.__paletteDock = QDockWidget(self)
997        self.__paletteDock.setObjectName("paletteDock")
998        self.__paletteDock.setFeatures(
999            QDockWidget.DockWidgetFeatures(
1000                QDockWidget.DockWidgetFeature.DockWidgetClosable |
1001                QDockWidget.DockWidgetFeature.DockWidgetMovable |
1002                QDockWidget.DockWidgetFeature.DockWidgetFloatable
1003            )
1004        )
1005        self.__paletteDock.setWindowTitle("Palette")
1006        self.__palette = IconEditorPalette()
1007        self.__paletteDock.setWidget(self.__palette)
1008        self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea,
1009                           self.__paletteDock)
1010
1011    def closeEvent(self, evt):
1012        """
1013        Protected event handler for the close event.
1014
1015        @param evt the close event (QCloseEvent)
1016                <br />This event is simply accepted after the history has been
1017                saved and all window references have been deleted.
1018        """
1019        if self.__maybeSave():
1020            self.__editor.shutdown()
1021
1022            state = self.saveState()
1023            Preferences.setIconEditor("IconEditorState", state)
1024
1025            Preferences.setGeometry("IconEditorGeometry", self.saveGeometry())
1026
1027            with contextlib.suppress(ValueError):
1028                if self.fromEric or len(self.__class__.windows) > 1:
1029                    del self.__class__.windows[
1030                        self.__class__.windows.index(self)]
1031
1032            if not self.fromEric:
1033                Preferences.syncPreferences()
1034
1035            evt.accept()
1036            self.editorClosed.emit()
1037        else:
1038            evt.ignore()
1039
1040    def __newIcon(self):
1041        """
1042        Private slot to create a new icon.
1043        """
1044        if self.__maybeSave():
1045            self.__editor.editNew()
1046            self.__setCurrentFile("")
1047
1048        self.__checkActions()
1049
1050    def __newWindow(self):
1051        """
1052        Private slot called to open a new icon editor window.
1053        """
1054        ie = IconEditorWindow(parent=self.parent(), fromEric=self.fromEric,
1055                              project=self.__project)
1056        ie.setRecentPaths(self.__lastOpenPath, self.__lastSavePath)
1057        ie.show()
1058
1059    def __openIcon(self):
1060        """
1061        Private slot to open an icon file.
1062        """
1063        if self.__maybeSave():
1064            if (
1065                not self.__lastOpenPath and
1066                self.__project is not None and
1067                self.__project.isOpen()
1068            ):
1069                self.__lastOpenPath = self.__project.getProjectPath()
1070
1071            fileName = E5FileDialog.getOpenFileNameAndFilter(
1072                self,
1073                self.tr("Open icon file"),
1074                self.__lastOpenPath,
1075                self.__inputFilter,
1076                self.__defaultFilter)[0]
1077            if fileName:
1078                self.__loadIconFile(fileName)
1079                self.__lastOpenPath = os.path.dirname(fileName)
1080        self.__checkActions()
1081
1082    def __saveIcon(self):
1083        """
1084        Private slot to save the icon.
1085
1086        @return flag indicating success (boolean)
1087        """
1088        if not self.__fileName:
1089            return self.__saveIconAs()
1090        else:
1091            return self.__saveIconFile(self.__fileName)
1092
1093    def __saveIconAs(self):
1094        """
1095        Private slot to save the icon with a new name.
1096
1097        @return flag indicating success (boolean)
1098        """
1099        if (
1100            not self.__lastSavePath and
1101            self.__project is not None and
1102            self.__project.isOpen()
1103        ):
1104            self.__lastSavePath = self.__project.getProjectPath()
1105        if not self.__lastSavePath and self.__lastOpenPath:
1106            self.__lastSavePath = self.__lastOpenPath
1107
1108        fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
1109            self,
1110            self.tr("Save icon file"),
1111            self.__lastSavePath,
1112            self.__outputFilter,
1113            self.__defaultFilter,
1114            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
1115        if not fileName:
1116            return False
1117
1118        ext = QFileInfo(fileName).suffix()
1119        if not ext:
1120            ex = selectedFilter.split("(*")[1].split(")")[0]
1121            if ex:
1122                fileName += ex
1123        if QFileInfo(fileName).exists():
1124            res = E5MessageBox.yesNo(
1125                self,
1126                self.tr("Save icon file"),
1127                self.tr("<p>The file <b>{0}</b> already exists."
1128                        " Overwrite it?</p>").format(fileName),
1129                icon=E5MessageBox.Warning)
1130            if not res:
1131                return False
1132
1133        self.__lastSavePath = os.path.dirname(fileName)
1134
1135        return self.__saveIconFile(fileName)
1136
1137    def __closeAll(self):
1138        """
1139        Private slot to close all windows.
1140        """
1141        self.__closeOthers()
1142        self.close()
1143
1144    def __closeOthers(self):
1145        """
1146        Private slot to close all other windows.
1147        """
1148        for win in self.__class__.windows[:]:
1149            if win != self:
1150                win.close()
1151
1152    def __loadIconFile(self, fileName):
1153        """
1154        Private method to load an icon file.
1155
1156        @param fileName name of the icon file to load (string).
1157        """
1158        file = QFile(fileName)
1159        if not file.exists():
1160            E5MessageBox.warning(
1161                self, self.tr("eric Icon Editor"),
1162                self.tr("The file '{0}' does not exist.")
1163                .format(fileName))
1164            return
1165
1166        if not file.open(QFile.ReadOnly):
1167            E5MessageBox.warning(
1168                self, self.tr("eric Icon Editor"),
1169                self.tr("Cannot read file '{0}:\n{1}.")
1170                .format(fileName, file.errorString()))
1171            return
1172        file.close()
1173
1174        img = QImage(fileName)
1175        self.__editor.setIconImage(img, clearUndo=True)
1176        self.__setCurrentFile(fileName)
1177
1178    def __saveIconFile(self, fileName):
1179        """
1180        Private method to save to the given file.
1181
1182        @param fileName name of the file to save to (string)
1183        @return flag indicating success (boolean)
1184        """
1185        file = QFile(fileName)
1186        if not file.open(QFile.WriteOnly):
1187            E5MessageBox.warning(
1188                self, self.tr("eric Icon Editor"),
1189                self.tr("Cannot write file '{0}:\n{1}.")
1190                .format(fileName, file.errorString()))
1191
1192            self.__checkActions()
1193
1194            return False
1195
1196        img = self.__editor.iconImage()
1197        res = img.save(file)
1198        file.close()
1199
1200        if not res:
1201            E5MessageBox.warning(
1202                self, self.tr("eric Icon Editor"),
1203                self.tr("Cannot write file '{0}:\n{1}.")
1204                .format(fileName, file.errorString()))
1205
1206            self.__checkActions()
1207
1208            return False
1209
1210        self.__editor.setDirty(False, setCleanState=True)
1211
1212        self.__setCurrentFile(fileName)
1213        self.__statusBar.showMessage(self.tr("Icon saved"), 2000)
1214
1215        self.__checkActions()
1216
1217        return True
1218
1219    def __setCurrentFile(self, fileName):
1220        """
1221        Private method to register the file name of the current file.
1222
1223        @param fileName name of the file to register (string)
1224        """
1225        self.__fileName = fileName
1226
1227        shownName = (
1228            self.__strippedName(self.__fileName)
1229            if self.__fileName else
1230            self.tr("Untitled")
1231        )
1232
1233        self.setWindowTitle(self.tr("{0}[*] - {1}")
1234                            .format(shownName, self.tr("Icon Editor")))
1235
1236        self.setWindowModified(self.__editor.isDirty())
1237
1238    def __strippedName(self, fullFileName):
1239        """
1240        Private method to return the filename part of the given path.
1241
1242        @param fullFileName full pathname of the given file (string)
1243        @return filename part (string)
1244        """
1245        return QFileInfo(fullFileName).fileName()
1246
1247    def __maybeSave(self):
1248        """
1249        Private method to ask the user to save the file, if it was modified.
1250
1251        @return flag indicating, if it is ok to continue (boolean)
1252        """
1253        if self.__editor.isDirty():
1254            ret = E5MessageBox.okToClearData(
1255                self,
1256                self.tr("eric Icon Editor"),
1257                self.tr("""The icon image has unsaved changes."""),
1258                self.__saveIcon)
1259            if not ret:
1260                return False
1261        return True
1262
1263    def setRecentPaths(self, openPath, savePath):
1264        """
1265        Public method to set the last open and save paths.
1266
1267        @param openPath least recently used open path (string)
1268        @param savePath least recently used save path (string)
1269        """
1270        if openPath:
1271            self.__lastOpenPath = openPath
1272        if savePath:
1273            self.__lastSavePath = savePath
1274
1275    def __checkActions(self):
1276        """
1277        Private slot to check some actions for their enable/disable status.
1278        """
1279        self.saveAct.setEnabled(self.__editor.isDirty())
1280
1281    def __modificationChanged(self, m):
1282        """
1283        Private slot to handle the modificationChanged signal.
1284
1285        @param m modification status
1286        """
1287        self.setWindowModified(m)
1288        self.__checkActions()
1289
1290    def __updatePosition(self, x, y):
1291        """
1292        Private slot to show the current cursor position.
1293
1294        @param x x-coordinate (integer)
1295        @param y y-coordinate (integer)
1296        """
1297        self.__sbPos.setText("X: {0:d} Y: {1:d}".format(x + 1, y + 1))
1298
1299    def __updateSize(self, w, h):
1300        """
1301        Private slot to show the current icon size.
1302
1303        @param w width of the icon (integer)
1304        @param h height of the icon (integer)
1305        """
1306        self.__sbSize.setText("Size: {0:d} x {1:d}".format(w, h))
1307
1308    def __updateZoom(self):
1309        """
1310        Private slot to show the current zoom factor.
1311        """
1312        self.zoomOutAct.setEnabled(
1313            self.__editor.zoomFactor() > IconEditorGrid.ZoomMinimum)
1314        self.zoomInAct.setEnabled(
1315            self.__editor.zoomFactor() < IconEditorGrid.ZoomMaximum)
1316
1317    def __zoomIn(self):
1318        """
1319        Private slot called to handle the zoom in action.
1320        """
1321        self.__editor.setZoomFactor(
1322            self.__editor.zoomFactor() + IconEditorGrid.ZoomStep)
1323        self.__updateZoom()
1324
1325    def __zoomOut(self):
1326        """
1327        Private slot called to handle the zoom out action.
1328        """
1329        self.__editor.setZoomFactor(
1330            self.__editor.zoomFactor() - IconEditorGrid.ZoomStep)
1331        self.__updateZoom()
1332
1333    def __zoomReset(self):
1334        """
1335        Private slot called to handle the zoom reset action.
1336        """
1337        self.__editor.setZoomFactor(IconEditorGrid.ZoomDefault)
1338        self.__updateZoom()
1339
1340    def __about(self):
1341        """
1342        Private slot to show a little About message.
1343        """
1344        E5MessageBox.about(
1345            self, self.tr("About eric Icon Editor"),
1346            self.tr("The eric Icon Editor is a simple editor component"
1347                    " to perform icon drawing tasks."))
1348
1349    def __aboutQt(self):
1350        """
1351        Private slot to handle the About Qt dialog.
1352        """
1353        E5MessageBox.aboutQt(self, "eric Icon Editor")
1354
1355    def __whatsThis(self):
1356        """
1357        Private slot called in to enter Whats This mode.
1358        """
1359        QWhatsThis.enterWhatsThisMode()
1360
1361    def wheelEvent(self, evt):
1362        """
1363        Protected method to handle wheel events.
1364
1365        @param evt reference to the wheel event (QWheelEvent)
1366        """
1367        if evt.modifiers() & Qt.KeyboardModifier.ControlModifier:
1368            delta = evt.angleDelta().y()
1369            if delta < 0:
1370                self.__zoomOut()
1371            elif delta > 0:
1372                self.__zoomIn()
1373            evt.accept()
1374            return
1375
1376        super().wheelEvent(evt)
1377
1378    def event(self, evt):
1379        """
1380        Public method handling events.
1381
1382        @param evt reference to the event (QEvent)
1383        @return flag indicating, if the event was handled (boolean)
1384        """
1385        if evt.type() == QEvent.Type.Gesture:
1386            self.gestureEvent(evt)
1387            return True
1388
1389        return super().event(evt)
1390
1391    def gestureEvent(self, evt):
1392        """
1393        Protected method handling gesture events.
1394
1395        @param evt reference to the gesture event (QGestureEvent
1396        """
1397        pinch = evt.gesture(Qt.GestureType.PinchGesture)
1398        if pinch:
1399            if pinch.state() == Qt.GestureState.GestureStarted:
1400                pinch.setTotalScaleFactor(self.__editor.zoomFactor() / 100.0)
1401            elif pinch.state() == Qt.GestureState.GestureUpdated:
1402                self.__editor.setZoomFactor(
1403                    int(pinch.totalScaleFactor() * 100))
1404                self.__updateZoom()
1405            evt.accept()
1406