1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a class representing the shortcuts JSON file.
8"""
9
10import json
11import time
12import typing
13
14from PyQt5.QtCore import QObject
15
16from E5Gui import E5MessageBox
17from E5Gui.E5OverrideCursor import E5OverridenCursor
18from E5Gui.E5Application import e5App
19
20import Preferences
21
22HelpViewer = typing.TypeVar("WebBrowserWindow")
23
24
25class ShortcutsFile(QObject):
26    """
27    Class representing the shortcuts JSON file.
28    """
29    def __init__(self: "ShortcutsFile", parent: QObject = None) -> None:
30        """
31        Constructor
32
33        @param parent reference to the parent object (defaults to None)
34        @type QObject (optional)
35        """
36        super().__init__(parent)
37
38    def __addActionsToDict(self: "ShortcutsFile", category: str, actions: list,
39                           actionsDict: dict) -> None:
40        """
41        Private method to add a list of actions to the actions dictionary.
42
43        @param category category of the actions
44        @type str
45        @param actions list of actions
46        @type list of QAction
47        @param actionsDict reference to the actions dictionary to be modified
48        @type dict
49        """
50        if actions:
51            if category not in actionsDict:
52                actionsDict[category] = {}
53            for act in actions:
54                if act.objectName():
55                    # shortcuts are only exported, if their objectName is set
56                    actionsDict[category][act.objectName()] = (
57                        act.shortcut().toString(),
58                        act.alternateShortcut().toString()
59                    )
60
61    def writeFile(self: "ShortcutsFile", filename: str,
62                  helpViewer: HelpViewer = None) -> bool:
63        """
64        Public method to write the shortcuts data to a shortcuts JSON file.
65
66        @param filename name of the shortcuts file
67        @type str
68        @param helpViewer reference to the help window object
69        @type WebBrowserWindow
70        @return flag indicating a successful write
71        @rtype bool
72        """
73        actionsDict = {}
74
75        # step 1: collect all the shortcuts
76        if helpViewer is None:
77            self.__addActionsToDict(
78                "Project",
79                e5App().getObject("Project").getActions(),
80                actionsDict
81            )
82            self.__addActionsToDict(
83                "General",
84                e5App().getObject("UserInterface").getActions('ui'),
85                actionsDict
86            )
87            self.__addActionsToDict(
88                "Wizards",
89                e5App().getObject("UserInterface").getActions('wizards'),
90                actionsDict
91            )
92            self.__addActionsToDict(
93                "Debug",
94                e5App().getObject("DebugUI").getActions(),
95                actionsDict
96            )
97            self.__addActionsToDict(
98                "Edit",
99                e5App().getObject("ViewManager").getActions('edit'),
100                actionsDict
101            )
102            self.__addActionsToDict(
103                "File",
104                e5App().getObject("ViewManager").getActions('file'),
105                actionsDict
106            )
107            self.__addActionsToDict(
108                "Search",
109                e5App().getObject("ViewManager").getActions('search'),
110                actionsDict
111            )
112            self.__addActionsToDict(
113                "View",
114                e5App().getObject("ViewManager").getActions('view'),
115                actionsDict
116            )
117            self.__addActionsToDict(
118                "Macro",
119                e5App().getObject("ViewManager").getActions('macro'),
120                actionsDict
121            )
122            self.__addActionsToDict(
123                "Bookmarks",
124                e5App().getObject("ViewManager").getActions('bookmark'),
125                actionsDict
126            )
127            self.__addActionsToDict(
128                "Spelling",
129                e5App().getObject("ViewManager").getActions('spelling'),
130                actionsDict
131            )
132            self.__addActionsToDict(
133                "Window",
134                e5App().getObject("ViewManager").getActions('window'),
135                actionsDict
136            )
137
138            for category, ref in e5App().getPluginObjects():
139                if hasattr(ref, "getActions"):
140                    self.__addActionsToDict(
141                        category, ref.getActions(), actionsDict
142                    )
143
144        else:
145            self.__addActionsToDict(
146                helpViewer.getActionsCategory(),
147                helpViewer.getActions(),
148                actionsDict
149            )
150
151        # step 2: assemble the data structure to be written
152        shortcutsDict = {}
153        # step 2.0: header
154        shortcutsDict["header"] = {
155            "comment": "eric keyboard shortcuts file",
156            "saved": time.strftime('%Y-%m-%d, %H:%M:%S'),
157            "author": Preferences.getUser("Email"),
158        }
159        # step 2.1: keyboard shortcuts
160        shortcutsDict["shortcuts"] = actionsDict
161
162        try:
163            jsonString = json.dumps(shortcutsDict, indent=2)
164            with open(filename, "w") as f:
165                f.write(jsonString)
166        except (TypeError, OSError) as err:
167            with E5OverridenCursor():
168                E5MessageBox.critical(
169                    None,
170                    self.tr("Export Keyboard Shortcuts"),
171                    self.tr(
172                        "<p>The keyboard shortcuts file <b>{0}</b> could not"
173                        " be written.</p><p>Reason: {1}</p>"
174                    ).format(filename, str(err))
175                )
176                return False
177
178        return True
179
180    def readFile(self: "ShortcutsFile", filename: str) -> bool:
181        """
182        Public method to read the shortcuts data from a shortcuts JSON file.
183
184        @param filename name of the shortcuts file
185        @type str
186        @return Dictionary of dictionaries of shortcuts. The keys of the
187            dictionary are the shortcuts categories, the values are
188            dictionaries. These dictionaries have the shortcut name as their
189            key and a tuple of accelerators as their value.
190        @rtype dict
191        """
192        try:
193            with open(filename, "r") as f:
194                jsonString = f.read()
195            shortcutsDict = json.loads(jsonString)
196        except (OSError, json.JSONDecodeError) as err:
197            E5MessageBox.critical(
198                None,
199                self.tr("Import Keyboard Shortcuts"),
200                self.tr(
201                    "<p>The keyboard shortcuts file <b>{0}</b> could not be"
202                    " read.</p><p>Reason: {1}</p>"
203                ).format(filename, str(err))
204            )
205            return {}
206
207        return shortcutsDict["shortcuts"]
208