|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a personal information manager used to complete form |
|
8 fields. |
|
9 """ |
|
10 |
|
11 from __future__ import unicode_literals |
|
12 |
|
13 from PyQt5.QtCore import Qt, QObject, QPoint |
|
14 from PyQt5.QtWidgets import QDialog, QMenu |
|
15 |
|
16 import Preferences |
|
17 import UI.PixmapCache |
|
18 |
|
19 |
|
20 class PersonalInformationManager(QObject): |
|
21 """ |
|
22 Class implementing the personal information manager used to complete form |
|
23 fields. |
|
24 """ |
|
25 FullName = 0 |
|
26 LastName = 1 |
|
27 FirstName = 2 |
|
28 Email = 3 |
|
29 Mobile = 4 |
|
30 Phone = 5 |
|
31 Address = 6 |
|
32 City = 7 |
|
33 Zip = 8 |
|
34 State = 9 |
|
35 Country = 10 |
|
36 HomePage = 11 |
|
37 Special1 = 12 |
|
38 Special2 = 13 |
|
39 Special3 = 14 |
|
40 Special4 = 15 |
|
41 Max = 16 |
|
42 Invalid = 256 |
|
43 |
|
44 def __init__(self, parent=None): |
|
45 """ |
|
46 Constructor |
|
47 |
|
48 @param parent reference to the parent object (QObject) |
|
49 """ |
|
50 super(PersonalInformationManager, self).__init__(parent) |
|
51 |
|
52 self.__loaded = False |
|
53 self.__allInfo = {} |
|
54 self.__infoMatches = {} |
|
55 self.__translations = {} |
|
56 |
|
57 self.__view = None |
|
58 self.__clickedPos = QPoint() |
|
59 |
|
60 def __loadSettings(self): |
|
61 """ |
|
62 Private method to load the settings. |
|
63 """ |
|
64 self.__allInfo[self.FullName] = \ |
|
65 Preferences.getWebBrowser("PimFullName") |
|
66 self.__allInfo[self.LastName] = \ |
|
67 Preferences.getWebBrowser("PimLastName") |
|
68 self.__allInfo[self.FirstName] = \ |
|
69 Preferences.getWebBrowser("PimFirstName") |
|
70 self.__allInfo[self.Email] = Preferences.getWebBrowser("PimEmail") |
|
71 self.__allInfo[self.Mobile] = Preferences.getWebBrowser("PimMobile") |
|
72 self.__allInfo[self.Phone] = Preferences.getWebBrowser("PimPhone") |
|
73 self.__allInfo[self.Address] = Preferences.getWebBrowser("PimAddress") |
|
74 self.__allInfo[self.City] = Preferences.getWebBrowser("PimCity") |
|
75 self.__allInfo[self.Zip] = Preferences.getWebBrowser("PimZip") |
|
76 self.__allInfo[self.State] = Preferences.getWebBrowser("PimState") |
|
77 self.__allInfo[self.Country] = Preferences.getWebBrowser("PimCountry") |
|
78 self.__allInfo[self.HomePage] = \ |
|
79 Preferences.getWebBrowser("PimHomePage") |
|
80 self.__allInfo[self.Special1] = \ |
|
81 Preferences.getWebBrowser("PimSpecial1") |
|
82 self.__allInfo[self.Special2] = \ |
|
83 Preferences.getWebBrowser("PimSpecial2") |
|
84 self.__allInfo[self.Special3] = \ |
|
85 Preferences.getWebBrowser("PimSpecial3") |
|
86 self.__allInfo[self.Special4] = \ |
|
87 Preferences.getWebBrowser("PimSpecial4") |
|
88 |
|
89 self.__translations[self.FullName] = self.tr("Full Name") |
|
90 self.__translations[self.LastName] = self.tr("Last Name") |
|
91 self.__translations[self.FirstName] = self.tr("First Name") |
|
92 self.__translations[self.Email] = self.tr("E-mail") |
|
93 self.__translations[self.Mobile] = self.tr("Mobile") |
|
94 self.__translations[self.Phone] = self.tr("Phone") |
|
95 self.__translations[self.Address] = self.tr("Address") |
|
96 self.__translations[self.City] = self.tr("City") |
|
97 self.__translations[self.Zip] = self.tr("ZIP Code") |
|
98 self.__translations[self.State] = self.tr("State/Region") |
|
99 self.__translations[self.Country] = self.tr("Country") |
|
100 self.__translations[self.HomePage] = self.tr("Home Page") |
|
101 self.__translations[self.Special1] = self.tr("Custom 1") |
|
102 self.__translations[self.Special2] = self.tr("Custom 2") |
|
103 self.__translations[self.Special3] = self.tr("Custom 3") |
|
104 self.__translations[self.Special4] = self.tr("Custom 4") |
|
105 |
|
106 self.__infoMatches[self.FullName] = ["fullname", "realname"] |
|
107 self.__infoMatches[self.LastName] = ["lastname", "surname"] |
|
108 self.__infoMatches[self.FirstName] = ["firstname", "name"] |
|
109 self.__infoMatches[self.Email] = ["email", "e-mail", "mail"] |
|
110 self.__infoMatches[self.Mobile] = ["mobile", "mobilephone"] |
|
111 self.__infoMatches[self.Phone] = ["phone", "telephone"] |
|
112 self.__infoMatches[self.Address] = ["address"] |
|
113 self.__infoMatches[self.City] = ["city"] |
|
114 self.__infoMatches[self.Zip] = ["zip"] |
|
115 self.__infoMatches[self.State] = ["state", "region"] |
|
116 self.__infoMatches[self.Country] = ["country"] |
|
117 self.__infoMatches[self.HomePage] = ["homepage", "www"] |
|
118 |
|
119 self.__loaded = True |
|
120 |
|
121 def showConfigurationDialog(self): |
|
122 """ |
|
123 Public method to show the configuration dialog. |
|
124 """ |
|
125 from .PersonalDataDialog import PersonalDataDialog |
|
126 dlg = PersonalDataDialog() |
|
127 if dlg.exec_() == QDialog.Accepted: |
|
128 dlg.storeData() |
|
129 self.__loadSettings() |
|
130 |
|
131 def createSubMenu(self, menu, view, hitTestResult): |
|
132 """ |
|
133 Public method to create the personal information sub-menu. |
|
134 |
|
135 @param menu reference to the main menu (QMenu) |
|
136 @param view reference to the view (HelpBrowser) |
|
137 @param hitTestResult reference to the hit test result |
|
138 (WebHitTestResult) |
|
139 """ |
|
140 self.__view = view |
|
141 self.__clickedPos = hitTestResult.pos() |
|
142 |
|
143 if not hitTestResult.isContentEditable(): |
|
144 return |
|
145 |
|
146 if not self.__loaded: |
|
147 self.__loadSettings() |
|
148 |
|
149 submenu = QMenu(self.tr("Insert Personal Information"), menu) |
|
150 submenu.setIcon(UI.PixmapCache.getIcon("pim.png")) |
|
151 |
|
152 for key, info in sorted(self.__allInfo.items()): |
|
153 if info: |
|
154 act = submenu.addAction( |
|
155 self.__translations[key], self.__insertData) |
|
156 act.setData(info) |
|
157 |
|
158 submenu.addSeparator() |
|
159 submenu.addAction(self.tr("Edit Personal Information"), |
|
160 self.showConfigurationDialog) |
|
161 |
|
162 menu.addMenu(submenu) |
|
163 menu.addSeparator() |
|
164 |
|
165 def __insertData(self): |
|
166 """ |
|
167 Private slot to insert the selected personal information. |
|
168 """ |
|
169 if self.__view is None or self.__clickedPos.isNull(): |
|
170 return |
|
171 |
|
172 act = self.sender() |
|
173 if act is None: |
|
174 return |
|
175 |
|
176 info = act.data() |
|
177 info = info.replace('"', '\\"') |
|
178 |
|
179 source = """ |
|
180 var e = document.elementFromPoint({0}, {1}); |
|
181 if (e) {{ |
|
182 var v = e.value.substring(0, e.selectionStart); |
|
183 v += "{2}" + e.value.substring(e.selectionEnd); |
|
184 e.value = v; |
|
185 }}""".format(self.__clickedPos.x(), self.__clickedPos.y(), info) |
|
186 self.__view.page().runJavaScript(source) |
|
187 |
|
188 def viewKeyPressEvent(self, view, evt): |
|
189 """ |
|
190 Protected method to handle key press events we are interested in. |
|
191 |
|
192 @param view reference to the view (HelpBrowser) |
|
193 @param evt reference to the key event (QKeyEvent) |
|
194 @return flag indicating handling of the event (boolean) |
|
195 """ |
|
196 if view is None: |
|
197 return False |
|
198 |
|
199 isEnter = evt.key() in [Qt.Key_Return, Qt.Key_Enter] |
|
200 isControlModifier = evt.modifiers() & Qt.ControlModifier |
|
201 if not isEnter or not isControlModifier: |
|
202 return False |
|
203 |
|
204 if not self.__loaded: |
|
205 self.__loadSettings() |
|
206 |
|
207 source = """ |
|
208 var inputs = document.getElementsByTagName('input'); |
|
209 var table = {0}; |
|
210 for (var i = 0; i < inputs.length; ++i) {{ |
|
211 var input = inputs[i]; |
|
212 if (input.type != 'text' || input.name == '') |
|
213 continue; |
|
214 for (var key in table) {{ |
|
215 if (!table.hasOwnProperty(key)) |
|
216 continue; |
|
217 if (key == input.name || input.name.indexOf(key) != -1) {{ |
|
218 input.value = table[key]; |
|
219 break; |
|
220 }} |
|
221 }} |
|
222 }}""".format(self.__matchingJsTable()) |
|
223 view.page().runJavaScript(source) |
|
224 |
|
225 return True |
|
226 |
|
227 def connectPage(self, page): |
|
228 """ |
|
229 Public method to allow the personal information manager to connect to |
|
230 the page. |
|
231 |
|
232 @param page reference to the web page (HelpWebPage) |
|
233 """ |
|
234 page.loadFinished.connect(self.__pageLoadFinished) |
|
235 |
|
236 def __pageLoadFinished(self, ok): |
|
237 """ |
|
238 Private slot to handle the completion of a page load. |
|
239 |
|
240 @param ok flag indicating a successful load (boolean) |
|
241 """ |
|
242 page = self.sender() |
|
243 if page is None or not ok: |
|
244 return |
|
245 |
|
246 if not self.__loaded: |
|
247 self.__loadSettings() |
|
248 |
|
249 source = """ |
|
250 var inputs = document.getElementsByTagName('input'); |
|
251 var table = {0}; |
|
252 for (var i = 0; i < inputs.length; ++i) {{ |
|
253 var input = inputs[i]; |
|
254 if (input.type != 'text' || input.name == '') |
|
255 continue; |
|
256 for (var key in table) {{ |
|
257 if (!table.hasOwnProperty(key) || table[key] == '') |
|
258 continue; |
|
259 if (key == input.name || input.name.indexOf(key) != -1) {{ |
|
260 input.style['-webkit-appearance'] = 'none'; |
|
261 input.style['-webkit-box-shadow'] = |
|
262 'inset 0 0 2px 1px #000EEE'; |
|
263 break; |
|
264 }} |
|
265 }} |
|
266 }}""".format(self.__matchingJsTable()) |
|
267 page.runJavaScript(source) |
|
268 |
|
269 def __matchingJsTable(self): |
|
270 """ |
|
271 Private method to create the common part of the JavaScript sources. |
|
272 |
|
273 @return JavaScript source |
|
274 @rtype str |
|
275 """ |
|
276 values = [] |
|
277 for key, names in self.__infoMatches.items(): |
|
278 for name in names: |
|
279 value = self.__allInfo[key].replace('"', '\\"') |
|
280 values.append('"{0}":"{1}"'.format(name, value)) |
|
281 return "{{ {0} }}".format(",".join(values)) |