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