Sun, 14 Feb 2016 16:47:40 +0100
Continued porting the web browser.
- added support for open search engines
--- a/Helpviewer/HelpBrowserWV.py Sat Feb 13 14:17:39 2016 +0100 +++ b/Helpviewer/HelpBrowserWV.py Sun Feb 14 16:47:40 2016 +0100 @@ -10,7 +10,7 @@ from __future__ import unicode_literals try: - str = unicode + str = unicode # __IGNORE_EXCEPTION__ except NameError: pass @@ -1617,8 +1617,8 @@ searchUrl = QUrl(self.page().mainFrame().baseUrl().resolved( QUrl(formElement.attribute("action")))) - if searchUrl.scheme() != "http": - return +## if searchUrl.scheme() != "http": +## return if qVersion() >= "5.0.0": from PyQt5.QtCore import QUrlQuery
--- a/Preferences/__init__.py Sat Feb 13 14:17:39 2016 +0100 +++ b/Preferences/__init__.py Sun Feb 14 16:47:40 2016 +0100 @@ -1017,6 +1017,11 @@ "UserStyleSheet": "", "ZoomValuesDB": "{}", # empty JSON dictionary "HistoryLimit": 30, + "WebSearchSuggestions": True, + "WebSearchEngine": "DuckDuckGo", + "WebSearchKeywords": [], # array of two tuples (keyword, + # search engine name) + "SearchLanguage": QLocale().language(), } @classmethod @@ -2653,17 +2658,17 @@ return QColor(col) else: return prefClass.webBrowserDefaults[key] -## elif key in ["WebSearchKeywords"]: -## # return a list of tuples of (keyword, engine name) -## keywords = [] -## size = prefClass.settings.beginReadArray("Help/" + key) -## for index in range(size): -## prefClass.settings.setArrayIndex(index) -## keyword = prefClass.settings.value("Keyword") -## engineName = prefClass.settings.value("Engine") -## keywords.append((keyword, engineName)) -## prefClass.settings.endArray() -## return keywords + elif key in ["WebSearchKeywords"]: + # return a list of tuples of (keyword, engine name) + keywords = [] + size = prefClass.settings.beginReadArray("WebBrowser/" + key) + for index in range(size): + prefClass.settings.setArrayIndex(index) + keyword = prefClass.settings.value("Keyword") + engineName = prefClass.settings.value("Engine") + keywords.append((keyword, engineName)) + prefClass.settings.endArray() + return keywords ## elif key in ["DownloadManagerDownloads"]: ## # return a list of tuples of (URL, save location, done flag, page url) ## downloads = [] @@ -2742,7 +2747,7 @@ "LocalContentCanAccessRemoteUrls", "LocalContentCanAccessFileUrls", "XSSAuditingEnabled", "ScrollAnimatorEnabled", "ErrorPageEnabled", - "WarnOnMultipleClose", + "WarnOnMultipleClose", "WebSearchSuggestions", ]: return toBool(prefClass.settings.value( "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) @@ -2768,19 +2773,19 @@ """ if key in ["StandardFont", "FixedFont"]: prefClass.settings.setValue("WebBrowser/" + key, value.toString()) -## elif key == "SaveUrlColor": -## prefClass.settings.setValue("Help/" + key, value.name()) -## elif key == "WebSearchKeywords": -## # value is list of tuples of (keyword, engine name) -## prefClass.settings.remove("Help/" + key) -## prefClass.settings.beginWriteArray("Help/" + key, len(value)) -## index = 0 -## for v in value: -## prefClass.settings.setArrayIndex(index) -## prefClass.settings.setValue("Keyword", v[0]) -## prefClass.settings.setValue("Engine", v[1]) -## index += 1 -## prefClass.settings.endArray() + elif key == "SaveUrlColor": + prefClass.settings.setValue("WebBrowser/" + key, value.name()) + elif key == "WebSearchKeywords": + # value is list of tuples of (keyword, engine name) + prefClass.settings.remove("WebBrowser/" + key) + prefClass.settings.beginWriteArray("WebBrowser/" + key, len(value)) + index = 0 + for v in value: + prefClass.settings.setArrayIndex(index) + prefClass.settings.setValue("Keyword", v[0]) + prefClass.settings.setValue("Engine", v[1]) + index += 1 + prefClass.settings.endArray() ## elif key == "DownloadManagerDownloads": ## # value is list of tuples of (URL, save location, done flag, page url) ## prefClass.settings.remove("Help/" + key)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Network/LoadRequest.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a page load request object. +""" + +# +# This code was ported from QupZilla. +# Copyright (C) David Rosca <nowrep@gmail.com> +# + +from __future__ import unicode_literals + +try: + from enum import Enum +except ImportError: + from ThirdParty.enum import Enum + +from PyQt5.QtCore import QByteArray, QUrl + + +class LoadRequestOperations(Enum): + """ + Class implementing the load request operations. + """ + GetOperation = 0 + PostOperation = 1 + + +class LoadRequest(object): + """ + Class implementing a page load request object. + """ + def __init__(self, urlOrRequest=None, + op=LoadRequestOperations.GetOperation, data=QByteArray()): + """ + Constructor + + @param urlOrRequest URL or request object + @type QUrl or LoadRequest + @param op request operation + @type LoadRequestOperations + @param data request data + @type QByteArray + """ + self.__url = QUrl() + self.__operation = op + self.__data = QByteArray(data) + + if isinstance(urlOrRequest, QUrl): + self.__url = QUrl(urlOrRequest) + elif isinstance(urlOrRequest, LoadRequest): + self.__url = urlOrRequest.url() + self.__operation = urlOrRequest.operation() + self.__data = urlOrRequest.data() + + def isEmpty(self): + """ + Public method to test for an empty request. + + @return flag indicating an empty request + @rtype bool + """ + return self.__url.isEmpty() + + def url(self): + """ + Public method to get the request URL. + + @return request URL + @rtype QUrl + """ + return QUrl(self.__url) + + def setUrl(self, url): + """ + Public method to set the request URL. + + @param url request URL + @type QUrl + """ + self.__url = QUrl(url) + + def urlString(self): + """ + Public method to get the request URL as a string. + + @return request URL as a string + @rtype str + """ + return QUrl.fromPercentEncoding(self.__url.toEncoded()) + + def operation(self): + """ + Public method to get the request operation. + + @return request operation + @rtype one of LoadRequest.GetOperation, LoadRequest.PostOperation + """ + return self.__operation + + def setOperation(self, op): + """ + Public method to set the request operation. + + @param op request operation + @type one of LoadRequest.GetOperation, LoadRequest.PostOperation + """ + assert op in [LoadRequestOperations.GetOperation, + LoadRequestOperations.PostOperation] + + self.__operation = op + + def data(self): + """ + Public method to get the request data. + + @return request data + @rtype QByteArray + """ + return QByteArray(self.__data) + + def setData(self, data): + """ + Public method to set the request data + + @param data request data + @type QByteArray + """ + self.__data = QByteArray(data)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Amazoncom.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Amazon.com</ShortName> + <Description>Amazon.com Search</Description> + <Url method="get" type="text/html" template="http://www.amazon.com/exec/obidos/external-search/?field-keywords={searchTerms}"/> + <Image>http://www.amazon.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Bing.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Bing</ShortName> + <Description>Bing Web Search</Description> + <Url method="get" type="text/html" template="http://www.bing.com/search?cc={language}&q={searchTerms}"/> + <Image>http://www.bing.com/s/wlflag.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/DeEn_Beolingus.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>De-En Beolingus</ShortName> + <Description>Beolingus: German-English Dictionary</Description> + <Url method="get" type="text/html" template="http://dict.tu-chemnitz.de/?query={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://dict.tu-chemnitz.de/sugg.php?json=1&s={searchTerms}"/> + <Image>http://dict.tu-chemnitz.de/pics/beo-de.png</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/DefaultSearchEngines.qrc Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,21 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> +<qresource> + <file>YouTube.xml</file> + <file>Amazoncom.xml</file> + <file>Bing.xml</file> + <file>DeEn_Beolingus.xml</file> + <file>DuckDuckGo.xml</file> + <file>Facebook.xml</file> + <file>Google_Im_Feeling_Lucky.xml</file> + <file>Google.xml</file> + <file>LEO_DeuEng.xml</file> + <file>LinuxMagazin.xml</file> + <file>Reddit.xml</file> + <file>Wikia_en.xml</file> + <file>Wikia.xml</file> + <file>Wikipedia.xml</file> + <file>Wiktionary.xml</file> + <file>Yahoo.xml</file> +</qresource> +</RCC>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/DefaultSearchEngines_rc.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,728 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.5.1) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x01\x9b\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x57\x69\x6b\x74\x69\x6f\ +\x6e\x61\x72\x79\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\ +\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x57\x69\x6b\x74\x69\x6f\x6e\x61\x72\x79\x3c\x2f\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\ +\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\ +\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\ +\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x7b\x63\x6f\x75\x6e\x74\x72\x79\x7d\x2e\x77\x69\x6b\ +\x74\x69\x6f\x6e\x61\x72\x79\x2e\x6f\x72\x67\x2f\x77\x2f\x69\x6e\ +\x64\x65\x78\x2e\x70\x68\x70\x3f\x74\x69\x74\x6c\x65\x3d\x53\x70\ +\x65\x63\x69\x61\x6c\x3a\x53\x65\x61\x72\x63\x68\x26\x61\x6d\x70\ +\x3b\x73\x65\x61\x72\x63\x68\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\ +\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\ +\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x65\x6e\x2e\x77\x69\ +\x6b\x74\x69\x6f\x6e\x61\x72\x79\x2e\x6f\x72\x67\x2f\x66\x61\x76\ +\x69\x63\x6f\x6e\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\ +\x0a\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\ +\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x02\x1e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x44\x65\x2d\x45\x6e\x20\ +\x42\x65\x6f\x6c\x69\x6e\x67\x75\x73\x3c\x2f\x53\x68\x6f\x72\x74\ +\x4e\x61\x6d\x65\x3e\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\ +\x69\x70\x74\x69\x6f\x6e\x3e\x42\x65\x6f\x6c\x69\x6e\x67\x75\x73\ +\x3a\x20\x47\x65\x72\x6d\x61\x6e\x2d\x45\x6e\x67\x6c\x69\x73\x68\ +\x20\x44\x69\x63\x74\x69\x6f\x6e\x61\x72\x79\x3c\x2f\x44\x65\x73\ +\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\x55\ +\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\ +\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\x22\ +\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x64\x69\x63\x74\x2e\x74\x75\x2d\x63\x68\x65\x6d\x6e\x69\ +\x74\x7a\x2e\x64\x65\x2f\x3f\x71\x75\x65\x72\x79\x3d\x7b\x73\x65\ +\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\ +\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\ +\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x61\x70\x70\x6c\x69\x63\ +\x61\x74\x69\x6f\x6e\x2f\x78\x2d\x73\x75\x67\x67\x65\x73\x74\x69\ +\x6f\x6e\x73\x2b\x6a\x73\x6f\x6e\x22\x20\x74\x65\x6d\x70\x6c\x61\ +\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x64\x69\x63\x74\x2e\ +\x74\x75\x2d\x63\x68\x65\x6d\x6e\x69\x74\x7a\x2e\x64\x65\x2f\x73\ +\x75\x67\x67\x2e\x70\x68\x70\x3f\x6a\x73\x6f\x6e\x3d\x31\x26\x61\ +\x6d\x70\x3b\x73\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\ +\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\ +\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x64\x69\x63\x74\x2e\x74\x75\x2d\ +\x63\x68\x65\x6d\x6e\x69\x74\x7a\x2e\x64\x65\x2f\x70\x69\x63\x73\ +\x2f\x62\x65\x6f\x2d\x64\x65\x2e\x70\x6e\x67\x3c\x2f\x49\x6d\x61\ +\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x02\x54\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x57\x69\x6b\x69\x61\x3c\ +\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\x20\x20\x20\ +\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x57\x69\x6b\ +\x69\x61\x20\x53\x69\x74\x65\x20\x53\x65\x61\x72\x63\x68\x3c\x2f\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\ +\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\ +\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\ +\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x7b\x63\x6f\x75\x6e\x74\x72\x79\x7d\x2e\x77\ +\x69\x6b\x69\x61\x2e\x63\x6f\x6d\x2f\x69\x6e\x64\x65\x78\x2e\x70\ +\x68\x70\x3f\x74\x69\x74\x6c\x65\x3d\x53\x70\x65\x63\x69\x61\x6c\ +\x3a\x53\x65\x61\x72\x63\x68\x26\x61\x6d\x70\x3b\x73\x65\x61\x72\ +\x63\x68\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\ +\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\ +\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\ +\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x2d\x73\x75\ +\x67\x67\x65\x73\x74\x69\x6f\x6e\x73\x2b\x6a\x73\x6f\x6e\x22\x20\ +\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x7b\x63\x6f\x75\x6e\x74\x72\x79\x7d\x2e\x77\x69\x6b\x69\x61\ +\x2e\x63\x6f\x6d\x2f\x61\x70\x69\x2e\x70\x68\x70\x3f\x61\x63\x74\ +\x69\x6f\x6e\x3d\x6f\x70\x65\x6e\x73\x65\x61\x72\x63\x68\x26\x61\ +\x6d\x70\x3b\x73\x65\x61\x72\x63\x68\x3d\x7b\x73\x65\x61\x72\x63\ +\x68\x54\x65\x72\x6d\x73\x7d\x26\x61\x6d\x70\x3b\x6e\x61\x6d\x65\ +\x73\x70\x61\x63\x65\x3d\x30\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\ +\x49\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x69\x6d\x61\ +\x67\x65\x73\x2e\x77\x69\x6b\x69\x61\x2e\x63\x6f\x6d\x2f\x77\x69\ +\x6b\x69\x61\x67\x6c\x6f\x62\x61\x6c\x2f\x69\x6d\x61\x67\x65\x73\ +\x2f\x36\x2f\x36\x34\x2f\x46\x61\x76\x69\x63\x6f\x6e\x2e\x69\x63\ +\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\x6e\ +\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x0a\ +\x00\x00\x02\x5b\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x57\x69\x6b\x69\x70\x65\ +\x64\x69\x61\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\ +\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\ +\x3e\x46\x75\x6c\x6c\x20\x74\x65\x78\x74\x20\x73\x65\x61\x72\x63\ +\x68\x20\x69\x6e\x20\x57\x69\x6b\x69\x70\x65\x64\x69\x61\x3c\x2f\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\ +\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\ +\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\ +\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x7b\x63\x6f\x75\x6e\x74\x72\x79\x7d\x2e\x77\ +\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\x67\x2f\x77\x69\x6b\ +\x69\x2f\x53\x70\x65\x63\x69\x61\x6c\x3a\x53\x65\x61\x72\x63\x68\ +\x3f\x73\x65\x61\x72\x63\x68\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\ +\x65\x72\x6d\x73\x7d\x26\x61\x6d\x70\x3b\x66\x75\x6c\x6c\x74\x65\ +\x78\x74\x3d\x53\x65\x61\x72\x63\x68\x22\x2f\x3e\x0a\x20\x20\x20\ +\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\ +\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x61\x70\x70\x6c\x69\x63\x61\ +\x74\x69\x6f\x6e\x2f\x78\x2d\x73\x75\x67\x67\x65\x73\x74\x69\x6f\ +\x6e\x73\x2b\x6a\x73\x6f\x6e\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\ +\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x7b\x63\x6f\x75\x6e\x74\ +\x72\x79\x7d\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\ +\x67\x2f\x77\x2f\x61\x70\x69\x2e\x70\x68\x70\x3f\x61\x63\x74\x69\ +\x6f\x6e\x3d\x6f\x70\x65\x6e\x73\x65\x61\x72\x63\x68\x26\x61\x6d\ +\x70\x3b\x73\x65\x61\x72\x63\x68\x3d\x7b\x73\x65\x61\x72\x63\x68\ +\x54\x65\x72\x6d\x73\x7d\x26\x61\x6d\x70\x3b\x6e\x61\x6d\x65\x73\ +\x70\x61\x63\x65\x3d\x30\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\ +\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x65\x6e\x2e\x77\ +\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\x67\x2f\x66\x61\x76\ +\x69\x63\x6f\x6e\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\ +\x0a\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\ +\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x01\x79\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x42\x69\x6e\x67\x3c\x2f\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\x20\x20\x20\x3c\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x42\x69\x6e\x67\ +\x20\x57\x65\x62\x20\x53\x65\x61\x72\x63\x68\x3c\x2f\x44\x65\x73\ +\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\x55\ +\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\ +\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\x22\ +\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x62\x69\x6e\x67\x2e\x63\x6f\x6d\x2f\x73\ +\x65\x61\x72\x63\x68\x3f\x63\x63\x3d\x7b\x6c\x61\x6e\x67\x75\x61\ +\x67\x65\x7d\x26\x61\x6d\x70\x3b\x71\x3d\x7b\x73\x65\x61\x72\x63\ +\x68\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\ +\x49\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ +\x2e\x62\x69\x6e\x67\x2e\x63\x6f\x6d\x2f\x73\x2f\x77\x6c\x66\x6c\ +\x61\x67\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\ +\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\ +\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x02\x64\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x47\x6f\x6f\x67\x6c\x65\ +\x20\x28\x49\x27\x6d\x20\x46\x65\x65\x6c\x69\x6e\x67\x20\x4c\x75\ +\x63\x6b\x79\x29\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\ +\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x47\x6f\x6f\x67\x6c\x65\x20\x57\x65\x62\x20\x53\x65\x61\ +\x72\x63\x68\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\ +\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\ +\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\ +\x78\x74\x2f\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\ +\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x67\x6f\ +\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x73\x65\x61\x72\x63\x68\x3f\ +\x62\x74\x6e\x49\x3d\x26\x61\x6d\x70\x3b\x68\x6c\x3d\x7b\x6c\x61\ +\x6e\x67\x75\x61\x67\x65\x7d\x26\x61\x6d\x70\x3b\x6c\x72\x3d\x6c\ +\x61\x6e\x67\x5f\x7b\x6c\x61\x6e\x67\x75\x61\x67\x65\x7d\x26\x61\ +\x6d\x70\x3b\x71\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\ +\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\ +\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\ +\x3d\x22\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x2d\ +\x73\x75\x67\x67\x65\x73\x74\x69\x6f\x6e\x73\x2b\x6a\x73\x6f\x6e\ +\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x73\x75\x67\x67\x65\x73\x74\x71\x75\x65\x72\x69\x65\ +\x73\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x63\x6f\x6d\ +\x70\x6c\x65\x74\x65\x2f\x73\x65\x61\x72\x63\x68\x3f\x6f\x75\x74\ +\x70\x75\x74\x3d\x66\x69\x72\x65\x66\x6f\x78\x26\x61\x6d\x70\x3b\ +\x68\x6c\x3d\x7b\x6c\x61\x6e\x67\x75\x61\x67\x65\x7d\x26\x61\x6d\ +\x70\x3b\x71\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\ +\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\x3e\ +\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x67\x6f\x6f\x67\x6c\ +\x65\x2e\x63\x6f\x6d\x2f\x66\x61\x76\x69\x63\x6f\x6e\x2e\x69\x63\ +\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\x6e\ +\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x0a\ +\x00\x00\x02\x46\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x57\x69\x6b\x69\x61\x20\ +\x28\x65\x6e\x29\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\ +\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x57\x69\x6b\x69\x61\x20\x28\x65\x6e\x29\x3c\x2f\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\ +\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\ +\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\ +\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x69\x6b\x69\x61\x2e\x63\x6f\x6d\ +\x2f\x69\x6e\x64\x65\x78\x2e\x70\x68\x70\x3f\x74\x69\x74\x6c\x65\ +\x3d\x53\x70\x65\x63\x69\x61\x6c\x3a\x53\x65\x61\x72\x63\x68\x26\ +\x61\x6d\x70\x3b\x73\x65\x61\x72\x63\x68\x3d\x7b\x73\x65\x61\x72\ +\x63\x68\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\ +\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\ +\x22\x20\x74\x79\x70\x65\x3d\x22\x61\x70\x70\x6c\x69\x63\x61\x74\ +\x69\x6f\x6e\x2f\x78\x2d\x73\x75\x67\x67\x65\x73\x74\x69\x6f\x6e\ +\x73\x2b\x6a\x73\x6f\x6e\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x69\x6b\ +\x69\x61\x2e\x63\x6f\x6d\x2f\x61\x70\x69\x2e\x70\x68\x70\x3f\x61\ +\x63\x74\x69\x6f\x6e\x3d\x6f\x70\x65\x6e\x73\x65\x61\x72\x63\x68\ +\x26\x61\x6d\x70\x3b\x73\x65\x61\x72\x63\x68\x3d\x7b\x73\x65\x61\ +\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\x26\x61\x6d\x70\x3b\x6e\x61\ +\x6d\x65\x73\x70\x61\x63\x65\x3d\x31\x22\x2f\x3e\x0a\x20\x20\x20\ +\x20\x3c\x49\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x69\ +\x6d\x61\x67\x65\x73\x2e\x77\x69\x6b\x69\x61\x2e\x63\x6f\x6d\x2f\ +\x77\x69\x6b\x69\x61\x67\x6c\x6f\x62\x61\x6c\x2f\x69\x6d\x61\x67\ +\x65\x73\x2f\x36\x2f\x36\x34\x2f\x46\x61\x76\x69\x63\x6f\x6e\x2e\ +\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\ +\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x01\x7e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x46\x61\x63\x65\x62\x6f\ +\x6f\x6b\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\ +\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\ +\x53\x65\x61\x72\x63\x68\x20\x46\x61\x63\x65\x62\x6f\x6f\x6b\x3c\ +\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\ +\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\ +\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\ +\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\ +\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x66\x61\x63\x65\x62\x6f\ +\x6f\x6b\x2e\x63\x6f\x6d\x2f\x73\x65\x61\x72\x63\x68\x2f\x3f\x73\ +\x72\x63\x3d\x6f\x73\x26\x61\x6d\x70\x3b\x71\x3d\x7b\x73\x65\x61\ +\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\ +\x20\x3c\x49\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x77\ +\x77\x77\x2e\x66\x61\x63\x65\x62\x6f\x6f\x6b\x2e\x63\x6f\x6d\x2f\ +\x66\x61\x76\x69\x63\x6f\x6e\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\ +\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x01\x95\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x41\x6d\x61\x7a\x6f\x6e\ +\x2e\x63\x6f\x6d\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\ +\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x41\x6d\x61\x7a\x6f\x6e\x2e\x63\x6f\x6d\x20\x53\x65\x61\ +\x72\x63\x68\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\ +\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\ +\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\ +\x78\x74\x2f\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\ +\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x61\x6d\ +\x61\x7a\x6f\x6e\x2e\x63\x6f\x6d\x2f\x65\x78\x65\x63\x2f\x6f\x62\ +\x69\x64\x6f\x73\x2f\x65\x78\x74\x65\x72\x6e\x61\x6c\x2d\x73\x65\ +\x61\x72\x63\x68\x2f\x3f\x66\x69\x65\x6c\x64\x2d\x6b\x65\x79\x77\ +\x6f\x72\x64\x73\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\ +\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\ +\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x61\x6d\x61\x7a\ +\x6f\x6e\x2e\x63\x6f\x6d\x2f\x66\x61\x76\x69\x63\x6f\x6e\x2e\x69\ +\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\ +\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\ +\x6f\x6e\x3e\x0a\ +\x00\x00\x02\x7a\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x4c\x45\x4f\x20\x44\x65\ +\x75\x2d\x45\x6e\x67\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\ +\x3e\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\ +\x6f\x6e\x3e\x44\x65\x75\x74\x73\x63\x68\x2d\x45\x6e\x67\x6c\x69\ +\x73\x63\x68\x20\x57\xc3\xb6\x72\x74\x65\x72\x62\x75\x63\x68\x20\ +\x76\x6f\x6e\x20\x4c\x45\x4f\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\ +\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\ +\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\ +\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\ +\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x64\x69\ +\x63\x74\x2e\x6c\x65\x6f\x2e\x6f\x72\x67\x2f\x65\x6e\x64\x65\x3f\ +\x6c\x61\x6e\x67\x3d\x64\x65\x26\x61\x6d\x70\x3b\x73\x65\x61\x72\ +\x63\x68\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\ +\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\ +\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\ +\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x2d\x73\x75\ +\x67\x67\x65\x73\x74\x69\x6f\x6e\x73\x2b\x6a\x73\x6f\x6e\x22\x20\ +\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x64\x69\x63\x74\x2e\x6c\x65\x6f\x2e\x6f\x72\x67\x2f\x64\x69\ +\x63\x74\x51\x75\x65\x72\x79\x2f\x6d\x2d\x71\x75\x65\x72\x79\x2f\ +\x63\x6f\x6e\x66\x2f\x65\x6e\x64\x65\x2f\x71\x75\x65\x72\x79\x2e\ +\x63\x6f\x6e\x66\x2f\x73\x74\x72\x6c\x69\x73\x74\x2e\x6a\x73\x6f\ +\x6e\x3f\x71\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\ +\x7d\x26\x61\x6d\x70\x3b\x73\x6f\x72\x74\x3d\x50\x4c\x61\x26\x61\ +\x6d\x70\x3b\x73\x68\x6f\x72\x74\x51\x75\x65\x72\x79\x26\x61\x6d\ +\x70\x3b\x6e\x6f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x26\ +\x61\x6d\x70\x3b\x6e\x6f\x51\x75\x65\x72\x79\x55\x52\x4c\x73\x22\ +\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\x3e\x68\x74\ +\x74\x70\x3a\x2f\x2f\x64\x69\x63\x74\x2e\x6c\x65\x6f\x2e\x6f\x72\ +\x67\x2f\x69\x6d\x67\x2f\x66\x61\x76\x69\x63\x6f\x6e\x73\x2f\x65\ +\x6e\x64\x65\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\ +\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\ +\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x01\xc9\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x4c\x69\x6e\x75\x78\x2d\ +\x4d\x61\x67\x61\x7a\x69\x6e\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\ +\x6d\x65\x3e\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\ +\x74\x69\x6f\x6e\x3e\x53\x75\x63\x68\x65\x20\x61\x75\x66\x20\x77\ +\x77\x77\x2e\x6c\x69\x6e\x75\x78\x2d\x6d\x61\x67\x61\x7a\x69\x6e\ +\x2e\x64\x65\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\ +\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\ +\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\ +\x78\x74\x2f\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\ +\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x6c\x69\ +\x6e\x75\x78\x2d\x6d\x61\x67\x61\x7a\x69\x6e\x2e\x64\x65\x2f\x63\ +\x6f\x6e\x74\x65\x6e\x74\x2f\x73\x65\x61\x72\x63\x68\x3f\x53\x65\ +\x61\x72\x63\x68\x54\x65\x78\x74\x3d\x7b\x73\x65\x61\x72\x63\x68\ +\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\ +\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x6c\x69\x6e\x75\x78\x2d\x6d\x61\x67\x61\x7a\x69\x6e\x2e\x64\x65\ +\x2f\x65\x78\x74\x65\x6e\x73\x69\x6f\x6e\x2f\x6c\x6e\x6d\x2f\x64\ +\x65\x73\x69\x67\x6e\x2f\x6c\x69\x6e\x75\x78\x5f\x6d\x61\x67\x61\ +\x7a\x69\x6e\x2f\x69\x6d\x61\x67\x65\x73\x2f\x66\x61\x76\x69\x63\ +\x6f\x6e\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\ +\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\ +\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x02\x46\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x47\x6f\x6f\x67\x6c\x65\ +\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\x20\x20\ +\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x47\x6f\ +\x6f\x67\x6c\x65\x20\x57\x65\x62\x20\x53\x65\x61\x72\x63\x68\x3c\ +\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\ +\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\ +\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\ +\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\ +\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x67\x6f\x6f\x67\x6c\x65\ +\x2e\x63\x6f\x6d\x2f\x73\x65\x61\x72\x63\x68\x3f\x68\x6c\x3d\x7b\ +\x6c\x61\x6e\x67\x75\x61\x67\x65\x7d\x26\x61\x6d\x70\x3b\x6c\x72\ +\x3d\x6c\x61\x6e\x67\x5f\x7b\x6c\x61\x6e\x67\x75\x61\x67\x65\x7d\ +\x26\x61\x6d\x70\x3b\x71\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\ +\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\ +\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\ +\x70\x65\x3d\x22\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\ +\x78\x2d\x73\x75\x67\x67\x65\x73\x74\x69\x6f\x6e\x73\x2b\x6a\x73\ +\x6f\x6e\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x73\x75\x67\x67\x65\x73\x74\x71\x75\x65\x72\ +\x69\x65\x73\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x63\ +\x6f\x6d\x70\x6c\x65\x74\x65\x2f\x73\x65\x61\x72\x63\x68\x3f\x6f\ +\x75\x74\x70\x75\x74\x3d\x66\x69\x72\x65\x66\x6f\x78\x26\x61\x6d\ +\x70\x3b\x68\x6c\x3d\x7b\x6c\x61\x6e\x67\x75\x61\x67\x65\x7d\x26\ +\x61\x6d\x70\x3b\x71\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\ +\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\ +\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x67\x6f\x6f\ +\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x66\x61\x76\x69\x63\x6f\x6e\x2e\ +\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\ +\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x02\x27\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x59\x61\x68\x6f\x6f\x21\ +\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\x20\x20\ +\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x59\x61\ +\x68\x6f\x6f\x20\x57\x65\x62\x20\x53\x65\x61\x72\x63\x68\x3c\x2f\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\x20\x20\ +\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\x65\ +\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x68\x74\ +\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x73\x65\x61\x72\x63\x68\x2e\x79\x61\x68\x6f\ +\x6f\x2e\x63\x6f\x6d\x2f\x73\x65\x61\x72\x63\x68\x3f\x65\x69\x3d\ +\x75\x74\x66\x2d\x38\x26\x61\x6d\x70\x3b\x66\x72\x3d\x73\x66\x70\ +\x26\x61\x6d\x70\x3b\x69\x73\x63\x71\x72\x79\x3d\x26\x61\x6d\x70\ +\x3b\x70\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\ +\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\ +\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\ +\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x2d\x73\x75\ +\x67\x67\x65\x73\x74\x69\x6f\x6e\x73\x2b\x6a\x73\x6f\x6e\x22\x20\ +\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x66\x66\x2e\x73\x65\x61\x72\x63\x68\x2e\x79\x61\x68\x6f\x6f\ +\x2e\x63\x6f\x6d\x2f\x67\x6f\x73\x73\x69\x70\x3f\x6f\x75\x74\x70\ +\x75\x74\x3d\x66\x78\x6a\x73\x6f\x6e\x26\x61\x6d\x70\x3b\x63\x6f\ +\x6d\x6d\x61\x6e\x64\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\ +\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\ +\x65\x3e\x68\x74\x74\x70\x3a\x2f\x2f\x6d\x2e\x77\x77\x77\x2e\x79\ +\x61\x68\x6f\x6f\x2e\x63\x6f\x6d\x2f\x66\x61\x76\x69\x63\x6f\x6e\ +\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\ +\x70\x65\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\ +\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x01\x85\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x59\x6f\x75\x54\x75\x62\ +\x65\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\x20\ +\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x59\ +\x6f\x75\x54\x75\x62\x65\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\ +\x74\x68\x6f\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\ +\x22\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\ +\x6c\x61\x74\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ +\x2e\x79\x6f\x75\x74\x75\x62\x65\x2e\x63\x6f\x6d\x2f\x72\x65\x73\ +\x75\x6c\x74\x73\x3f\x73\x65\x61\x72\x63\x68\x5f\x71\x75\x65\x72\ +\x79\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\x26\ +\x61\x6d\x70\x3b\x73\x65\x61\x72\x63\x68\x3d\x53\x65\x61\x72\x63\ +\x68\x22\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\x3e\ +\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x79\x6f\x75\x74\x75\ +\x62\x65\x2e\x63\x6f\x6d\x2f\x66\x61\x76\x69\x63\x6f\x6e\x2e\x69\ +\x63\x6f\x3c\x2f\x49\x6d\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\ +\x6e\x53\x65\x61\x72\x63\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\ +\x6f\x6e\x3e\x0a\ +\x00\x00\x06\xfe\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x44\x75\x63\x6b\x44\x75\ +\x63\x6b\x47\x6f\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\ +\x0a\x20\x20\x20\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x53\x65\x61\x72\x63\x68\x20\x44\x75\x63\x6b\x44\x75\x63\ +\x6b\x47\x6f\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\ +\x3e\x0a\x20\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\ +\x64\x3d\x22\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\ +\x78\x74\x2f\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\ +\x65\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x64\x75\x63\x6b\x64\ +\x75\x63\x6b\x67\x6f\x2e\x63\x6f\x6d\x2f\x3f\x71\x3d\x7b\x73\x65\ +\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\x20\x20\ +\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\x67\ +\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x61\x70\x70\x6c\x69\x63\ +\x61\x74\x69\x6f\x6e\x2f\x78\x2d\x73\x75\x67\x67\x65\x73\x74\x69\ +\x6f\x6e\x73\x2b\x6a\x73\x6f\x6e\x22\x20\x74\x65\x6d\x70\x6c\x61\ +\x74\x65\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x63\x2e\x64\ +\x75\x63\x6b\x64\x75\x63\x6b\x67\x6f\x2e\x63\x6f\x6d\x2f\x61\x63\ +\x2f\x3f\x71\x3d\x7b\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\ +\x7d\x26\x61\x6d\x70\x3b\x74\x79\x70\x65\x3d\x6c\x69\x73\x74\x22\ +\x2f\x3e\x0a\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\x3e\x64\x61\ +\x74\x61\x3a\x69\x6d\x61\x67\x65\x2f\x78\x2d\x69\x63\x6f\x6e\x3b\ +\x62\x61\x73\x65\x36\x34\x2c\x69\x56\x42\x4f\x52\x77\x30\x4b\x47\ +\x67\x6f\x41\x41\x41\x41\x4e\x53\x55\x68\x45\x55\x67\x41\x41\x41\ +\x42\x41\x41\x41\x41\x41\x51\x43\x41\x4d\x41\x41\x41\x41\x6f\x4c\ +\x51\x39\x54\x41\x41\x41\x41\x42\x47\x64\x42\x54\x55\x45\x41\x41\ +\x4c\x47\x50\x43\x2f\x78\x68\x42\x51\x41\x41\x41\x43\x42\x6a\x53\ +\x46\x4a\x4e\x41\x41\x42\x36\x4a\x67\x41\x41\x67\x49\x51\x41\x41\ +\x50\x6f\x41\x41\x41\x43\x41\x36\x41\x41\x41\x64\x54\x41\x41\x41\ +\x4f\x70\x67\x41\x41\x41\x36\x6d\x41\x41\x41\x46\x33\x43\x63\x75\ +\x6c\x45\x38\x41\x41\x41\x42\x38\x6c\x42\x4d\x56\x45\x55\x41\x41\ +\x41\x44\x6b\x52\x51\x7a\x6a\x50\x77\x50\x6a\x51\x51\x58\x6b\x52\ +\x51\x33\x69\x50\x77\x54\x69\x51\x51\x58\x67\x50\x51\x50\x65\x51\ +\x67\x72\x63\x4f\x77\x50\x56\x4e\x67\x44\x56\x4e\x51\x44\x57\x4f\ +\x67\x62\x54\x4d\x77\x44\x52\x4d\x67\x44\x51\x4d\x77\x44\x53\x4d\ +\x77\x44\x52\x4e\x77\x54\x51\x4c\x67\x44\x52\x4a\x67\x44\x53\x4a\ +\x77\x44\x53\x4c\x67\x44\x53\x4e\x77\x54\x6a\x4f\x67\x44\x69\x4f\ +\x41\x44\x6a\x4f\x51\x44\x6b\x50\x41\x44\x68\x51\x41\x58\x7a\x73\ +\x35\x76\x2b\x2f\x66\x76\x2f\x2f\x2f\x2f\x30\x76\x4b\x62\x69\x52\ +\x51\x76\x67\x50\x51\x48\x70\x64\x55\x72\x38\x35\x4e\x7a\x75\x6b\ +\x6e\x50\x64\x4b\x67\x44\x63\x49\x77\x44\x6e\x5a\x7a\x6a\x32\x77\ +\x37\x48\x71\x65\x55\x2f\x67\x50\x51\x4c\x73\x69\x6d\x62\x2f\x2b\ +\x50\x66\x74\x6a\x57\x6e\x39\x37\x4f\x62\x70\x62\x30\x4c\x64\x4a\ +\x51\x44\x65\x4c\x51\x44\x74\x6a\x6d\x76\x73\x69\x32\x6a\x67\x53\ +\x42\x44\x6e\x62\x55\x4c\x67\x4f\x51\x44\x2f\x33\x39\x48\x67\x4c\ +\x51\x44\x65\x4d\x67\x44\x70\x65\x46\x4c\x67\x53\x42\x48\x30\x76\ +\x36\x37\x30\x75\x71\x62\x61\x4a\x51\x44\x32\x71\x49\x6d\x57\x76\ +\x50\x2f\x47\x31\x4f\x62\x35\x2b\x2f\x33\x75\x2f\x2f\x2b\x66\x76\ +\x76\x58\x79\x70\x34\x37\x64\x4d\x77\x44\x61\x4c\x77\x44\x30\x75\ +\x36\x76\x30\x76\x36\x2f\x61\x4e\x51\x44\x69\x58\x69\x2f\x61\x4b\ +\x51\x44\x33\x71\x6f\x7a\x55\x37\x2f\x38\x67\x53\x59\x32\x76\x76\ +\x74\x67\x30\x5a\x4b\x2f\x4f\x71\x4c\x44\x61\x4b\x51\x48\x59\x4b\ +\x67\x4c\x67\x57\x54\x66\x61\x4e\x41\x44\x5a\x4d\x67\x44\x5a\x4d\ +\x41\x44\x5a\x4c\x41\x44\x7a\x71\x70\x44\x37\x2f\x2f\x2b\x78\x77\ +\x64\x7a\x2f\x2f\x39\x48\x2f\x35\x42\x6e\x2f\x37\x42\x6e\x2f\x2f\ +\x41\x44\x6f\x66\x41\x44\x59\x4d\x41\x44\x59\x4d\x51\x44\x5a\x4f\ +\x67\x50\x58\x4c\x67\x44\x69\x5a\x44\x6a\x2f\x2f\x39\x37\x2f\x30\ +\x41\x44\x33\x74\x51\x44\x76\x6c\x67\x48\x5a\x4f\x67\x62\x58\x4c\ +\x41\x54\x58\x4d\x41\x44\x57\x4d\x67\x44\x66\x58\x6a\x4c\x56\x4c\ +\x51\x44\x2f\x2f\x2f\x7a\x2b\x30\x41\x44\x2f\x33\x52\x6e\x2f\x79\ +\x52\x6e\x77\x6e\x51\x44\x63\x56\x6a\x62\x56\x4d\x51\x44\x79\x76\ +\x36\x37\x77\x75\x4b\x54\x53\x4a\x77\x44\x52\x48\x51\x44\x2b\x38\ +\x4f\x2f\x74\x67\x33\x2f\x69\x51\x51\x44\x77\x68\x41\x48\x6e\x61\ +\x77\x48\x57\x4d\x41\x44\x76\x74\x4b\x66\x79\x76\x61\x37\x58\x51\ +\x78\x48\x67\x61\x30\x62\x51\x47\x51\x44\x32\x76\x62\x48\x2f\x75\ +\x38\x4c\x58\x49\x51\x43\x6d\x50\x51\x7a\x6a\x61\x30\x37\x58\x51\ +\x78\x4c\x6c\x69\x47\x6e\x39\x39\x66\x50\x6b\x63\x56\x48\x76\x68\ +\x6e\x47\x5a\x35\x56\x67\x75\x76\x55\x55\x35\x77\x6b\x74\x42\x77\ +\x43\x63\x41\x67\x78\x7a\x79\x64\x56\x76\x2f\x38\x2f\x58\x6d\x69\ +\x47\x6e\x67\x64\x6c\x4c\x2b\x79\x73\x69\x33\x2b\x49\x38\x4c\x74\ +\x43\x45\x38\x30\x56\x36\x50\x33\x59\x6d\x58\x34\x73\x44\x6c\x65\ +\x6c\x6a\x53\x4e\x51\x4c\x7a\x72\x36\x44\x37\x73\x4b\x50\x58\x4e\ +\x51\x54\x53\x49\x77\x41\x45\x41\x62\x4d\x72\x41\x41\x41\x41\x46\ +\x33\x52\x53\x54\x6c\x4d\x41\x52\x71\x53\x6b\x52\x76\x50\x7a\x38\ +\x30\x50\x54\x70\x4b\x52\x47\x33\x66\x50\x65\x33\x68\x69\x6f\x39\ +\x2f\x65\x6f\x47\x50\x35\x30\x6a\x4e\x73\x41\x41\x41\x41\x42\x59\ +\x6b\x74\x48\x52\x42\x35\x79\x43\x69\x41\x72\x41\x41\x41\x41\x79\ +\x45\x6c\x45\x51\x56\x51\x59\x47\x51\x58\x42\x76\x55\x71\x43\x59\ +\x52\x69\x41\x34\x66\x75\x32\x56\x39\x54\x6e\x2b\x55\x51\x64\x64\ +\x49\x33\x61\x43\x70\x78\x61\x4f\x6f\x55\x36\x69\x55\x34\x67\x63\ +\x71\x71\x70\x6f\x59\x62\x41\x4c\x58\x42\x75\x43\x75\x6f\x59\x6d\ +\x74\x74\x61\x6d\x71\x4a\x44\x69\x45\x6f\x68\x34\x59\x50\x2b\x4d\ +\x4f\x69\x36\x42\x4e\x43\x68\x2b\x75\x59\x4b\x45\x47\x69\x4f\x56\ +\x4e\x43\x58\x58\x78\x41\x32\x58\x44\x56\x56\x2f\x55\x79\x66\x4b\ +\x62\x52\x43\x58\x54\x4c\x51\x57\x41\x78\x62\x50\x32\x76\x74\x38\ +\x55\x65\x2f\x75\x59\x44\x76\x66\x69\x6d\x39\x31\x36\x31\x35\x73\ +\x62\x32\x75\x6d\x36\x72\x71\x74\x72\x72\x2f\x4e\x46\x62\x31\x63\ +\x55\x66\x31\x59\x62\x64\x30\x36\x61\x72\x65\x55\x36\x6c\x53\x6c\ +\x59\x70\x4b\x37\x39\x6a\x7a\x4b\x31\x53\x79\x4a\x4f\x6b\x66\x68\ +\x4f\x6c\x38\x4a\x47\x45\x63\x71\x56\x35\x7a\x6f\x4b\x72\x54\x52\ +\x71\x4f\x36\x79\x55\x7a\x49\x7a\x4e\x75\x34\x36\x69\x6a\x64\x4d\ +\x31\x56\x56\x39\x62\x68\x75\x55\x4a\x2f\x6e\x5a\x55\x52\x45\x78\ +\x4c\x52\x7a\x55\x69\x50\x51\x6d\x33\x6b\x4b\x58\x48\x69\x34\x42\ +\x41\x45\x47\x4f\x6d\x4f\x69\x37\x38\x41\x2f\x4c\x31\x51\x6f\x55\ +\x2f\x56\x48\x6f\x54\x73\x41\x41\x41\x41\x6c\x64\x45\x56\x59\x64\ +\x47\x52\x68\x64\x47\x55\x36\x59\x33\x4a\x6c\x59\x58\x52\x6c\x41\ +\x44\x49\x77\x4d\x54\x51\x74\x4d\x44\x45\x74\x4d\x54\x6c\x55\x4d\ +\x6a\x41\x36\x4d\x44\x45\x36\x4d\x54\x45\x74\x4d\x44\x55\x36\x4d\ +\x44\x41\x75\x45\x54\x36\x63\x41\x41\x41\x41\x4a\x58\x52\x46\x57\ +\x48\x52\x6b\x59\x58\x52\x6c\x4f\x6d\x31\x76\x5a\x47\x6c\x6d\x65\ +\x51\x41\x79\x4d\x44\x45\x30\x4c\x54\x41\x78\x4c\x54\x45\x35\x56\ +\x44\x49\x77\x4f\x6a\x41\x78\x4f\x6a\x45\x78\x4c\x54\x41\x31\x4f\ +\x6a\x41\x77\x58\x30\x79\x47\x49\x41\x41\x41\x41\x41\x42\x4a\x52\ +\x55\x35\x45\x72\x6b\x4a\x67\x67\x67\x3d\x3d\x3c\x2f\x49\x6d\x61\ +\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\x68\ +\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +\x00\x00\x01\x6f\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0a\x3c\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x78\x6d\x6c\ +\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x61\x39\x2e\x63\x6f\ +\x6d\x2f\x2d\x2f\x73\x70\x65\x63\x2f\x6f\x70\x65\x6e\x73\x65\x61\ +\x72\x63\x68\x2f\x31\x2e\x31\x2f\x22\x3e\x0a\x20\x20\x20\x20\x3c\ +\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x52\x65\x64\x64\x69\x74\ +\x3c\x2f\x53\x68\x6f\x72\x74\x4e\x61\x6d\x65\x3e\x0a\x20\x20\x20\ +\x20\x3c\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x52\x65\ +\x64\x64\x69\x74\x20\x53\x69\x74\x65\x20\x53\x65\x61\x72\x63\x68\ +\x3c\x2f\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\x20\ +\x20\x20\x20\x3c\x55\x72\x6c\x20\x6d\x65\x74\x68\x6f\x64\x3d\x22\ +\x67\x65\x74\x22\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\ +\x68\x74\x6d\x6c\x22\x20\x74\x65\x6d\x70\x6c\x61\x74\x65\x3d\x22\ +\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x72\x65\x64\x64\x69\ +\x74\x2e\x63\x6f\x6d\x2f\x73\x65\x61\x72\x63\x68\x3f\x71\x3d\x7b\ +\x73\x65\x61\x72\x63\x68\x54\x65\x72\x6d\x73\x7d\x22\x2f\x3e\x0a\ +\x20\x20\x20\x20\x3c\x49\x6d\x61\x67\x65\x3e\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x72\x65\x64\x64\x69\x74\x2e\x63\x6f\x6d\ +\x2f\x66\x61\x76\x69\x63\x6f\x6e\x2e\x69\x63\x6f\x3c\x2f\x49\x6d\ +\x61\x67\x65\x3e\x0a\x3c\x2f\x4f\x70\x65\x6e\x53\x65\x61\x72\x63\ +\x68\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3e\x0a\ +" + +qt_resource_name = b"\ +\x00\x0e\ +\x08\xce\x7c\x3c\ +\x00\x57\ +\x00\x69\x00\x6b\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x61\x00\x72\x00\x79\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x12\ +\x0a\xf9\x0f\x7c\ +\x00\x44\ +\x00\x65\x00\x45\x00\x6e\x00\x5f\x00\x42\x00\x65\x00\x6f\x00\x6c\x00\x69\x00\x6e\x00\x67\x00\x75\x00\x73\x00\x2e\x00\x78\x00\x6d\ +\x00\x6c\ +\x00\x09\ +\x01\xf4\xe3\x3c\ +\x00\x57\ +\x00\x69\x00\x6b\x00\x69\x00\x61\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0d\ +\x06\xf8\x53\x3c\ +\x00\x57\ +\x00\x69\x00\x6b\x00\x69\x00\x70\x00\x65\x00\x64\x00\x69\x00\x61\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x08\ +\x00\x4a\x56\x1c\ +\x00\x42\ +\x00\x69\x00\x6e\x00\x67\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x1b\ +\x0d\x52\x43\x5c\ +\x00\x47\ +\x00\x6f\x00\x6f\x00\x67\x00\x6c\x00\x65\x00\x5f\x00\x49\x00\x6d\x00\x5f\x00\x46\x00\x65\x00\x65\x00\x6c\x00\x69\x00\x6e\x00\x67\ +\x00\x5f\x00\x4c\x00\x75\x00\x63\x00\x6b\x00\x79\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0c\ +\x0e\x81\x61\xdc\ +\x00\x57\ +\x00\x69\x00\x6b\x00\x69\x00\x61\x00\x5f\x00\x65\x00\x6e\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0c\ +\x0f\xd5\x68\x1c\ +\x00\x46\ +\x00\x61\x00\x63\x00\x65\x00\x62\x00\x6f\x00\x6f\x00\x6b\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0d\ +\x0a\x2e\x72\x9c\ +\x00\x41\ +\x00\x6d\x00\x61\x00\x7a\x00\x6f\x00\x6e\x00\x63\x00\x6f\x00\x6d\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0e\ +\x00\xf1\x12\x1c\ +\x00\x4c\ +\x00\x45\x00\x4f\x00\x5f\x00\x44\x00\x65\x00\x75\x00\x45\x00\x6e\x00\x67\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x10\ +\x09\x73\x65\x7c\ +\x00\x4c\ +\x00\x69\x00\x6e\x00\x75\x00\x78\x00\x4d\x00\x61\x00\x67\x00\x61\x00\x7a\x00\x69\x00\x6e\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0a\ +\x0e\x31\x93\x9c\ +\x00\x47\ +\x00\x6f\x00\x6f\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x09\ +\x0f\x62\xe1\xdc\ +\x00\x59\ +\x00\x61\x00\x68\x00\x6f\x00\x6f\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0b\ +\x0b\x48\x8a\x5c\ +\x00\x59\ +\x00\x6f\x00\x75\x00\x54\x00\x75\x00\x62\x00\x65\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0e\ +\x09\x21\x3a\xfc\ +\x00\x44\ +\x00\x75\x00\x63\x00\x6b\x00\x44\x00\x75\x00\x63\x00\x6b\x00\x47\x00\x6f\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +\x00\x0a\ +\x0b\x0c\x48\x7c\ +\x00\x52\ +\x00\x65\x00\x64\x00\x64\x00\x69\x00\x74\x00\x2e\x00\x78\x00\x6d\x00\x6c\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\x01\ +\x00\x00\x00\x84\x00\x00\x00\x00\x00\x01\x00\x00\x08\x78\ +\x00\x00\x01\x32\x00\x00\x00\x00\x00\x01\x00\x00\x11\xc2\ +\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x03\xc1\ +\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x06\x19\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\xc8\x00\x00\x00\x00\x00\x01\x00\x00\x1c\x0b\ +\x00\x00\x01\x54\x00\x00\x00\x00\x00\x01\x00\x00\x14\x40\ +\x00\x00\x01\x12\x00\x00\x00\x00\x00\x01\x00\x00\x10\x29\ +\x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x01\x9f\ +\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x00\x23\x0d\ +\x00\x00\x01\xac\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x82\ +\x00\x00\x00\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x09\xf5\ +\x00\x00\x01\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x16\x0d\ +\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x5d\ +\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x18\x57\ +\x00\x00\x00\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xa7\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/DuckDuckGo.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>DuckDuckGo</ShortName> + <Description>Search DuckDuckGo</Description> + <Url method="get" type="text/html" template="https://duckduckgo.com/?q={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="https://ac.duckduckgo.com/ac/?q={searchTerms}&type=list"/> + <Image>data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB8lBMVEUAAADkRQzjPwPjQQXkRQ3iPwTiQQXgPQPeQgrcOwPVNgDVNQDWOgbTMwDRMgDQMwDSMwDRNwTQLgDRJgDSJwDSLgDSNwTjOgDiOADjOQDkPADhQAXzs5v+/fv////0vKbiRQvgPQHpdUr85NzuknPdKgDcIwDnZzj2w7HqeU/gPQLsimb/+PftjWn97Obpb0LdJQDeLQDtjmvsi2jgSBDnbULgOQD/39HgLQDeMgDpeFLgSBH0v670uqbaJQD2qImWvP/G1Ob5+/3u//+fvvXyp47dMwDaLwD0u6v0v6/aNQDiXi/aKQD3qozU7/8gSY2vvtg0ZK/OqLDaKQHYKgLgWTfaNADZMgDZMADZLADzqpD7//+xwdz//9H/5Bn/7Bn//ADofADYMADYMQDZOgPXLgDiZDj//97/0AD3tQDvlgHZOgbXLATXMADWMgDfXjLVLQD///z+0AD/3Rn/yRnwnQDcVjbVMQDyv67wuKTSJwDRHQD+8O/tg3/iQQDwhAHnawHWMADvtKfyva7XQxHga0bQGQD2vbH/u8LXIQCmPQzja07XQxLliGn99fPkcVHvhnGZ5VguvUU5wktBwCcAgxzydVv/8/XmiGngdlL+ysi3+I8LtCE80V6P3YmX4sDleljSNQLzr6D7sKPXNQTSIwAEAbMrAAAAF3RSTlMARqSkRvPz80PTpKRG3fPe3hio9/eoGP50jNsAAAABYktHRB5yCiArAAAAyElEQVQYGQXBvUqCYRiA4fu2V9Tn+UQddI3aCpxaOoU6iU4gcqqpoYbALXBuCuoYmttamqJDiEoh4YP+MOi6BNCh+uYKEGiOVNCXXxA2XDVV/UyfKbRCXTLQWAxbP2vt8Ue/uYDvfim91615sb2um6rqtrr/NFb1cUf1Ybd06areU6lSlYpK79jzK1SyJOkfhOl8JGEcqV5zoKrTRqO6yUzIzNu46ijdM1VV9bhuUJ/nZURExLRzUiPQm3kKXHi4BAEGOmOi78A/L1QoU/VHoTsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTQtMDEtMTlUMjA6MDE6MTEtMDU6MDAuET6cAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTAxLTE5VDIwOjAxOjExLTA1OjAwX0yGIAAAAABJRU5ErkJggg==</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Facebook.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Facebook</ShortName> + <Description>Search Facebook</Description> + <Url method="get" type="text/html" template="http://www.facebook.com/search/?src=os&q={searchTerms}"/> + <Image>http://www.facebook.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Google.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Google</ShortName> + <Description>Google Web Search</Description> + <Url method="get" type="text/html" template="http://www.google.com/search?hl={language}&lr=lang_{language}&q={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://suggestqueries.google.com/complete/search?output=firefox&hl={language}&q={searchTerms}"/> + <Image>http://www.google.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Google_Im_Feeling_Lucky.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Google (I'm Feeling Lucky)</ShortName> + <Description>Google Web Search</Description> + <Url method="get" type="text/html" template="http://www.google.com/search?btnI=&hl={language}&lr=lang_{language}&q={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://suggestqueries.google.com/complete/search?output=firefox&hl={language}&q={searchTerms}"/> + <Image>http://www.google.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/LEO_DeuEng.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>LEO Deu-Eng</ShortName> + <Description>Deutsch-Englisch Wörterbuch von LEO</Description> + <Url method="get" type="text/html" template="http://dict.leo.org/ende?lang=de&search={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://dict.leo.org/dictQuery/m-query/conf/ende/query.conf/strlist.json?q={searchTerms}&sort=PLa&shortQuery&noDescription&noQueryURLs"/> + <Image>http://dict.leo.org/img/favicons/ende.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/LinuxMagazin.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Linux-Magazin</ShortName> + <Description>Suche auf www.linux-magazin.de</Description> + <Url method="get" type="text/html" template="http://www.linux-magazin.de/content/search?SearchText={searchTerms}"/> + <Image>http://www.linux-magazin.de/extension/lnm/design/linux_magazin/images/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Reddit.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Reddit</ShortName> + <Description>Reddit Site Search</Description> + <Url method="get" type="text/html" template="http://www.reddit.com/search?q={searchTerms}"/> + <Image>http://www.reddit.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Wikia.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Wikia</ShortName> + <Description>Wikia Site Search</Description> + <Url method="get" type="text/html" template="http://{country}.wikia.com/index.php?title=Special:Search&search={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://{country}.wikia.com/api.php?action=opensearch&search={searchTerms}&namespace=0"/> + <Image>http://images.wikia.com/wikiaglobal/images/6/64/Favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Wikia_en.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Wikia (en)</ShortName> + <Description>Wikia (en)</Description> + <Url method="get" type="text/html" template="http://www.wikia.com/index.php?title=Special:Search&search={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://www.wikia.com/api.php?action=opensearch&search={searchTerms}&namespace=1"/> + <Image>http://images.wikia.com/wikiaglobal/images/6/64/Favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Wikipedia.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Wikipedia</ShortName> + <Description>Full text search in Wikipedia</Description> + <Url method="get" type="text/html" template="http://{country}.wikipedia.org/wiki/Special:Search?search={searchTerms}&fulltext=Search"/> + <Url method="get" type="application/x-suggestions+json" template="http://{country}.wikipedia.org/w/api.php?action=opensearch&search={searchTerms}&namespace=0"/> + <Image>http://en.wikipedia.org/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Wiktionary.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Wiktionary</ShortName> + <Description>Wiktionary</Description> + <Url method="get" type="text/html" template="http://{country}.wiktionary.org/w/index.php?title=Special:Search&search={searchTerms}"/> + <Image>http://en.wiktionary.org/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/Yahoo.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Yahoo!</ShortName> + <Description>Yahoo Web Search</Description> + <Url method="get" type="text/html" template="http://search.yahoo.com/search?ei=utf-8&fr=sfp&iscqry=&p={searchTerms}"/> + <Url method="get" type="application/x-suggestions+json" template="http://ff.search.yahoo.com/gossip?output=fxjson&command={searchTerms}"/> + <Image>http://m.www.yahoo.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/YouTube.xml Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>YouTube</ShortName> + <Description>YouTube</Description> + <Url method="get" type="text/html" template="http://www.youtube.com/results?search_query={searchTerms}&search=Search"/> + <Image>http://www.youtube.com/favicon.ico</Image> +</OpenSearchDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/DefaultSearchEngines/__init__.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package conatining the default search engine definitions. +"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchDialog.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog for the configuration of search engines. +""" + +from __future__ import unicode_literals + +from PyQt5.QtWidgets import QDialog +from PyQt5.QtCore import pyqtSlot + +from E5Gui import E5MessageBox, E5FileDialog + +from .OpenSearchEngineModel import OpenSearchEngineModel + +from .Ui_OpenSearchDialog import Ui_OpenSearchDialog + + +class OpenSearchDialog(QDialog, Ui_OpenSearchDialog): + """ + Class implementing a dialog for the configuration of search engines. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object (QWidget) + """ + super(OpenSearchDialog, self).__init__(parent) + self.setupUi(self) + + self.setModal(True) + + self.__mw = parent + + self.__model = \ + OpenSearchEngineModel(self.__mw.openSearchManager(), self) + self.enginesTable.setModel(self.__model) + self.enginesTable.horizontalHeader().resizeSection(0, 200) + self.enginesTable.horizontalHeader().setStretchLastSection(True) + self.enginesTable.verticalHeader().hide() + self.enginesTable.verticalHeader().setDefaultSectionSize( + 1.2 * self.fontMetrics().height()) + + self.enginesTable.selectionModel().selectionChanged.connect( + self.__selectionChanged) + self.editButton.setEnabled(False) + + @pyqtSlot() + def on_addButton_clicked(self): + """ + Private slot to add a new search engine. + """ + fileNames = E5FileDialog.getOpenFileNames( + self, + self.tr("Add search engine"), + "", + self.tr("OpenSearch (*.xml);;All Files (*)")) + + osm = self.__mw.openSearchManager() + for fileName in fileNames: + if not osm.addEngine(fileName): + E5MessageBox.critical( + self, + self.tr("Add search engine"), + self.tr( + """{0} is not a valid OpenSearch 1.1 description or""" + """ is already on your list.""").format(fileName)) + + @pyqtSlot() + def on_deleteButton_clicked(self): + """ + Private slot to delete the selected search engines. + """ + if self.enginesTable.model().rowCount() == 1: + E5MessageBox.critical( + self, + self.tr("Delete selected engines"), + self.tr("""You must have at least one search engine.""")) + + self.enginesTable.removeSelected() + + @pyqtSlot() + def on_restoreButton_clicked(self): + """ + Private slot to restore the default search engines. + """ + self.__mw.openSearchManager().restoreDefaults() + + @pyqtSlot() + def on_editButton_clicked(self): + """ + Private slot to edit the data of the current search engine. + """ + from .OpenSearchEditDialog import OpenSearchEditDialog + + rows = self.enginesTable.selectionModel().selectedRows() + if len(rows) == 0: + row = self.enginesTable.selectionModel().currentIndex().row() + else: + row = rows[0].row() + + osm = self.__mw.openSearchManager() + engineName = osm.allEnginesNames()[row] + engine = osm.engine(engineName) + dlg = OpenSearchEditDialog(engine, self) + if dlg.exec_() == QDialog.Accepted: + osm.enginesChanged() + + def __selectionChanged(self, selected, deselected): + """ + Private slot to handle a change of the selection. + + @param selected item selection of selected items (QItemSelection) + @param deselected item selection of deselected items (QItemSelection) + """ + self.editButton.setEnabled( + len(self.enginesTable.selectionModel().selectedRows()) <= 1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchDialog.ui Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>OpenSearchDialog</class> + <widget class="QDialog" name="OpenSearchDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>650</width> + <height>350</height> + </rect> + </property> + <property name="windowTitle"> + <string>Open Search Engines Configuration</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" rowspan="5"> + <widget class="E5TableView" name="enginesTable"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="addButton"> + <property name="toolTip"> + <string>Press to add a new search engine from file</string> + </property> + <property name="text"> + <string>&Add...</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="deleteButton"> + <property name="toolTip"> + <string>Press to delete the selected engines</string> + </property> + <property name="text"> + <string>&Delete</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QPushButton" name="editButton"> + <property name="toolTip"> + <string>Press to edit the data of the current engine</string> + </property> + <property name="text"> + <string>Edit...</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="restoreButton"> + <property name="toolTip"> + <string>Press to restore the default engines</string> + </property> + <property name="text"> + <string>&Restore Defaults</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="4" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>38</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5TableView</class> + <extends>QTableView</extends> + <header>E5Gui/E5TableView.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>enginesTable</tabstop> + <tabstop>addButton</tabstop> + <tabstop>deleteButton</tabstop> + <tabstop>editButton</tabstop> + <tabstop>restoreButton</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>OpenSearchDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>OpenSearchDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchEditDialog.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to edit the data of a search engine. +""" + +from __future__ import unicode_literals + +from PyQt5.QtWidgets import QDialog + +from .Ui_OpenSearchEditDialog import Ui_OpenSearchEditDialog + + +class OpenSearchEditDialog(QDialog, Ui_OpenSearchEditDialog): + """ + Class implementing a dialog to edit the data of a search engine. + """ + def __init__(self, engine, parent=None): + """ + Constructor + + @param engine reference to the search engine (OpenSearchEngine) + @param parent reference to the parent object (QWidget) + """ + super(OpenSearchEditDialog, self).__init__(parent) + self.setupUi(self) + + self.__engine = engine + + self.nameEdit.setText(engine.name()) + self.descriptionEdit.setText(engine.description()) + self.imageEdit.setText(engine.imageUrl()) + self.searchEdit.setText(engine.searchUrlTemplate()) + self.suggestionsEdit.setText(engine.suggestionsUrlTemplate()) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def accept(self): + """ + Public slot to accept the data entered. + """ + self.__engine.setName(self.nameEdit.text()) + self.__engine.setDescription(self.descriptionEdit.text()) + self.__engine.setImageUrlAndLoad(self.imageEdit.text()) + self.__engine.setSearchUrlTemplate(self.searchEdit.text()) + self.__engine.setSuggestionsUrlTemplate(self.suggestionsEdit.text()) + + super(OpenSearchEditDialog, self).accept()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchEditDialog.ui Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>OpenSearchEditDialog</class> + <widget class="QDialog" name="OpenSearchEditDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>690</width> + <height>218</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit search engine data</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>&Name:</string> + </property> + <property name="buddy"> + <cstring>nameEdit</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="nameEdit"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="toolTip"> + <string>Shows the name of the search engine</string> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>&Description:</string> + </property> + <property name="buddy"> + <cstring>descriptionEdit</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="descriptionEdit"> + <property name="toolTip"> + <string>Enter a description</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>&Image URL:</string> + </property> + <property name="buddy"> + <cstring>imageEdit</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="imageEdit"> + <property name="toolTip"> + <string>Enter the URL of the image</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>&Search URL Template:</string> + </property> + <property name="buddy"> + <cstring>searchEdit</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="searchEdit"> + <property name="toolTip"> + <string>Enter the template of the search URL</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Su&ggestions URL Template:</string> + </property> + <property name="buddy"> + <cstring>suggestionsEdit</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="suggestionsEdit"> + <property name="toolTip"> + <string>Enter the template of the suggestions URL</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>nameEdit</tabstop> + <tabstop>descriptionEdit</tabstop> + <tabstop>imageEdit</tabstop> + <tabstop>searchEdit</tabstop> + <tabstop>suggestionsEdit</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>OpenSearchEditDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>222</x> + <y>232</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>246</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>OpenSearchEditDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>290</x> + <y>238</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>246</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchEngine.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,538 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the open search engine. +""" + +from __future__ import unicode_literals + +import re +import json + +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QLocale, QUrl, QByteArray, \ + QBuffer, QIODevice, QObject, qVersion +from PyQt5.QtGui import QImage +from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, \ + QNetworkReply + +from UI.Info import Program + +import Preferences +import Utilities + + +class OpenSearchEngine(QObject): + """ + Class implementing the open search engine. + + @signal imageChanged() emitted after the icon has been changed + @signal suggestions(list of strings) emitted after the suggestions have + been received + """ + imageChanged = pyqtSignal() + suggestions = pyqtSignal(list) + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object (QObject) + """ + super(OpenSearchEngine, self).__init__(parent) + + self.__suggestionsReply = None + self.__networkAccessManager = None + self._name = "" + self._description = "" + self._searchUrlTemplate = "" + self._suggestionsUrlTemplate = "" + self._searchParameters = [] # list of two tuples + self._suggestionsParameters = [] # list of two tuples + self._imageUrl = "" + self.__image = QImage() + self.__iconMoved = False + self.__searchMethod = "get" + self.__suggestionsMethod = "get" + self.__requestMethods = { + "get": QNetworkAccessManager.GetOperation, + "post": QNetworkAccessManager.PostOperation, + } + + self.__replies = [] + + @classmethod + def parseTemplate(cls, searchTerm, searchTemplate): + """ + Class method to parse a search template. + + @param searchTerm term to search for (string) + @param searchTemplate template to be parsed (string) + @return parsed template (string) + """ + locale = QLocale(Preferences.getWebBrowser("SearchLanguage")) + language = locale.name().replace("_", "-") + country = locale.name().split("_")[0].lower() + + result = searchTemplate + result = result.replace("{count}", "20") + result = result.replace("{startIndex}", "0") + result = result.replace("{startPage}", "0") + result = result.replace("{language}", language) + result = result.replace("{country}", country) + result = result.replace("{inputEncoding}", "UTF-8") + result = result.replace("{outputEncoding}", "UTF-8") + result = result.replace( + "{searchTerms}", + bytes(QUrl.toPercentEncoding(searchTerm)).decode()) + result = re.sub(r"""\{([^\}]*:|)source\??\}""", Program, result) + + return result + + @pyqtSlot(result=str) + def name(self): + """ + Public method to get the name of the engine. + + @return name of the engine (string) + """ + return self._name + + def setName(self, name): + """ + Public method to set the engine name. + + @param name name of the engine (string) + """ + self._name = name + + def description(self): + """ + Public method to get the description of the engine. + + @return description of the engine (string) + """ + return self._description + + def setDescription(self, description): + """ + Public method to set the engine description. + + @param description description of the engine (string) + """ + self._description = description + + def searchUrlTemplate(self): + """ + Public method to get the search URL template of the engine. + + @return search URL template of the engine (string) + """ + return self._searchUrlTemplate + + def setSearchUrlTemplate(self, searchUrlTemplate): + """ + Public method to set the engine search URL template. + + The URL template is processed according to the specification: + <a + href="http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax"> + http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax</a> + + A list of template parameters currently supported and what they are + replaced with: + <table> + <tr><td><b>Parameter</b></td><td><b>Value</b></td></tr> + <tr><td>{count}</td><td>20</td></tr> + <tr><td>{startIndex}</td><td>0</td></tr> + <tr><td>{startPage}</td><td>0</td></tr> + <tr><td>{language}</td> + <td>the default language code (RFC 3066)</td></tr> + <tr><td>{country}</td> + <td>the default country code (first part of language)</td></tr> + <tr><td>{inputEncoding}</td><td>UTF-8</td></tr> + <tr><td>{outputEncoding}</td><td>UTF-8</td></tr> + <tr><td>{searchTerms}</td><td>the string supplied by the user</td></tr> + <tr><td>{*:source}</td> + <td>application name, QCoreApplication::applicationName()</td></tr> + </table> + + @param searchUrlTemplate search URL template of the engine (string) + """ + self._searchUrlTemplate = searchUrlTemplate + + def searchUrl(self, searchTerm): + """ + Public method to get a URL ready for searching. + + @param searchTerm term to search for (string) + @return URL (QUrl) + """ + if not self._searchUrlTemplate: + return QUrl() + + ret = QUrl.fromEncoded( + self.parseTemplate(searchTerm, self._searchUrlTemplate) + .encode("utf-8")) + + if self.__searchMethod != "post": + if qVersion() >= "5.0.0": + from PyQt5.QtCore import QUrlQuery + urlQuery = QUrlQuery(ret) + for parameter in self._searchParameters: + urlQuery.addQueryItem( + parameter[0], + self.parseTemplate(searchTerm, parameter[1])) + ret.setQuery(urlQuery) + else: + for parameter in self._searchParameters: + ret.addQueryItem( + parameter[0], + self.parseTemplate(searchTerm, parameter[1])) + + return ret + + def providesSuggestions(self): + """ + Public method to check, if the engine provides suggestions. + + @return flag indicating suggestions are provided (boolean) + """ + return self._suggestionsUrlTemplate != "" + + def suggestionsUrlTemplate(self): + """ + Public method to get the search URL template of the engine. + + @return search URL template of the engine (string) + """ + return self._suggestionsUrlTemplate + + def setSuggestionsUrlTemplate(self, suggestionsUrlTemplate): + """ + Public method to set the engine suggestions URL template. + + @param suggestionsUrlTemplate suggestions URL template of the + engine (string) + """ + self._suggestionsUrlTemplate = suggestionsUrlTemplate + + def suggestionsUrl(self, searchTerm): + """ + Public method to get a URL ready for suggestions. + + @param searchTerm term to search for (string) + @return URL (QUrl) + """ + if not self._suggestionsUrlTemplate: + return QUrl() + + ret = QUrl.fromEncoded(QByteArray(self.parseTemplate( + searchTerm, self._suggestionsUrlTemplate).encode("utf-8"))) + + if self.__searchMethod != "post": + if qVersion() >= "5.0.0": + from PyQt5.QtCore import QUrlQuery + urlQuery = QUrlQuery(ret) + for parameter in self._suggestionsParameters: + urlQuery.addQueryItem( + parameter[0], + self.parseTemplate(searchTerm, parameter[1])) + ret.setQuery(urlQuery) + else: + for parameter in self._suggestionsParameters: + ret.addQueryItem( + parameter[0], + self.parseTemplate(searchTerm, parameter[1])) + + return ret + + def searchParameters(self): + """ + Public method to get the search parameters of the engine. + + @return search parameters of the engine (list of two tuples) + """ + return self._searchParameters[:] + + def setSearchParameters(self, searchParameters): + """ + Public method to set the engine search parameters. + + @param searchParameters search parameters of the engine + (list of two tuples) + """ + self._searchParameters = searchParameters[:] + + def suggestionsParameters(self): + """ + Public method to get the suggestions parameters of the engine. + + @return suggestions parameters of the engine (list of two tuples) + """ + return self._suggestionsParameters[:] + + def setSuggestionsParameters(self, suggestionsParameters): + """ + Public method to set the engine suggestions parameters. + + @param suggestionsParameters suggestions parameters of the + engine (list of two tuples) + """ + self._suggestionsParameters = suggestionsParameters[:] + + def searchMethod(self): + """ + Public method to get the HTTP request method used to perform search + requests. + + @return HTTP request method (string) + """ + return self.__searchMethod + + def setSearchMethod(self, method): + """ + Public method to set the HTTP request method used to perform search + requests. + + @param method HTTP request method (string) + """ + requestMethod = method.lower() + if requestMethod not in self.__requestMethods: + return + + self.__searchMethod = requestMethod + + def suggestionsMethod(self): + """ + Public method to get the HTTP request method used to perform + suggestions requests. + + @return HTTP request method (string) + """ + return self.__suggestionsMethod + + def setSuggestionsMethod(self, method): + """ + Public method to set the HTTP request method used to perform + suggestions requests. + + @param method HTTP request method (string) + """ + requestMethod = method.lower() + if requestMethod not in self.__requestMethods: + return + + self.__suggestionsMethod = requestMethod + + def imageUrl(self): + """ + Public method to get the image URL of the engine. + + @return image URL of the engine (string) + """ + return self._imageUrl + + def setImageUrl(self, imageUrl): + """ + Public method to set the engine image URL. + + @param imageUrl image URL of the engine (string) + """ + self._imageUrl = imageUrl + + def setImageUrlAndLoad(self, imageUrl): + """ + Public method to set the engine image URL. + + @param imageUrl image URL of the engine (string) + """ + self.setImageUrl(imageUrl) + self.__iconMoved = False + self.loadImage() + + def loadImage(self): + """ + Public method to load the image of the engine. + """ + if self.__networkAccessManager is None or not self._imageUrl: + return + + reply = self.__networkAccessManager.get( + QNetworkRequest(QUrl.fromEncoded(self._imageUrl.encode("utf-8")))) + reply.finished.connect(self.__imageObtained) + self.__replies.append(reply) + + def __imageObtained(self): + """ + Private slot to receive the image of the engine. + """ + reply = self.sender() + if reply is None: + return + + response = reply.readAll() + + reply.close() + if reply in self.__replies: + self.__replies.remove(reply) + reply.deleteLater() + + if response.isEmpty(): + return + + if response.startsWith(b"<html>") or response.startsWith(b"HTML"): + self.__iconMoved = True + self.__image = QImage() + else: + self.__image.loadFromData(response) + self.imageChanged.emit() + + def image(self): + """ + Public method to get the image of the engine. + + @return image of the engine (QImage) + """ + if not self.__iconMoved and self.__image.isNull(): + self.loadImage() + + return self.__image + + def setImage(self, image): + """ + Public method to set the image of the engine. + + @param image image to be set (QImage) + """ + if not self._imageUrl: + imageBuffer = QBuffer() + imageBuffer.open(QIODevice.ReadWrite) + if image.save(imageBuffer, "PNG"): + self._imageUrl = "data:image/png;base64,{0}".format( + bytes(imageBuffer.buffer().toBase64()).decode()) + + self.__image = QImage(image) + self.imageChanged.emit() + + def isValid(self): + """ + Public method to check, if the engine is valid. + + @return flag indicating validity (boolean) + """ + return self._name and self._searchUrlTemplate + + def __eq__(self, other): + """ + Special method implementing the == operator. + + @param other reference to an open search engine (OpenSearchEngine) + @return flag indicating equality (boolean) + """ + if not isinstance(other, OpenSearchEngine): + return NotImplemented + + return self._name == other._name and \ + self._description == other._description and \ + self._imageUrl == other._imageUrl and \ + self._searchUrlTemplate == other._searchUrlTemplate and \ + self._suggestionsUrlTemplate == other._suggestionsUrlTemplate and \ + self._searchParameters == other._searchParameters and \ + self._suggestionsParameters == other._suggestionsParameters + + def __lt__(self, other): + """ + Special method implementing the < operator. + + @param other reference to an open search engine (OpenSearchEngine) + @return flag indicating less than (boolean) + """ + if not isinstance(other, OpenSearchEngine): + return NotImplemented + + return self._name < other._name + + def requestSuggestions(self, searchTerm): + """ + Public method to request suggestions. + + @param searchTerm term to get suggestions for (string) + """ + if not searchTerm or not self.providesSuggestions(): + return + + if self.__networkAccessManager is None: + return + + if self.__suggestionsReply is not None: + self.__suggestionsReply.finished.disconnect( + self.__suggestionsObtained) + self.__suggestionsReply.abort() + self.__suggestionsReply.deleteLater() + self.__suggestionsReply = None + + if self.__suggestionsMethod not in self.__requestMethods: + # ignore + return + + if self.__suggestionsMethod == "get": + self.__suggestionsReply = self.networkAccessManager().get( + QNetworkRequest(self.suggestionsUrl(searchTerm))) + else: + parameters = [] + for parameter in self._suggestionsParameters: + parameters.append(parameter[0] + "=" + parameter[1]) + data = "&".join(parameters) + self.__suggestionsReply = self.networkAccessManager().post( + QNetworkRequest(self.suggestionsUrl(searchTerm)), data) + self.__suggestionsReply.finished.connect( + self.__suggestionsObtained) + + def __suggestionsObtained(self): + """ + Private slot to receive the suggestions. + """ + if self.__suggestionsReply.error() == QNetworkReply.NoError: + buffer = bytes(self.__suggestionsReply.readAll()) + response = Utilities.decodeBytes(buffer) + response = response.strip() + + self.__suggestionsReply.close() + self.__suggestionsReply.deleteLater() + self.__suggestionsReply = None + + if len(response) == 0: + return + + try: + result = json.loads(response) + except ValueError: + return + + try: + suggestions = result[1] + except IndexError: + return + + self.suggestions.emit(suggestions) + + def networkAccessManager(self): + """ + Public method to get a reference to the network access manager object. + + @return reference to the network access manager object + (QNetworkAccessManager) + """ + return self.__networkAccessManager + + def setNetworkAccessManager(self, networkAccessManager): + """ + Public method to set the reference to the network access manager. + + @param networkAccessManager reference to the network access manager + object (QNetworkAccessManager) + """ + self.__networkAccessManager = networkAccessManager
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchEngineAction.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a QAction subclass for open search. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QUrl +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import QAction + + +class OpenSearchEngineAction(QAction): + """ + Class implementing a QAction subclass for open search. + """ + def __init__(self, engine, parent=None): + """ + Constructor + + @param engine reference to the open search engine object + (OpenSearchEngine) + @param parent reference to the parent object (QObject) + """ + super(OpenSearchEngineAction, self).__init__(parent) + + self.__engine = engine + if self.__engine.networkAccessManager() is None: + import WebBrowser.WebBrowserWindow + self.__engine.setNetworkAccessManager( + WebBrowser.WebBrowserWindow.WebBrowserWindow.networkManager()) + + self.setText(engine.name()) + self.__imageChanged() + + engine.imageChanged.connect(self.__imageChanged) + + def __imageChanged(self): + """ + Private slot handling a change of the associated image. + """ + image = self.__engine.image() + if image.isNull(): + import WebBrowser.WebBrowserWindow + self.setIcon( + WebBrowser.WebBrowserWindow.WebBrowserWindow.icon( + QUrl(self.__engine.imageUrl()))) + else: + self.setIcon(QIcon(QPixmap.fromImage(image)))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchEngineModel.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a model for search engines. +""" + +from __future__ import unicode_literals + +import re + +from PyQt5.QtCore import Qt, QUrl, QAbstractTableModel, QModelIndex +from PyQt5.QtGui import QPixmap, QIcon + + +class OpenSearchEngineModel(QAbstractTableModel): + """ + Class implementing a model for search engines. + """ + def __init__(self, manager, parent=None): + """ + Constructor + + @param manager reference to the search engine manager + (OpenSearchManager) + @param parent reference to the parent object (QObject) + """ + super(OpenSearchEngineModel, self).__init__(parent) + + self.__manager = manager + manager.changed.connect(self.__enginesChanged) + + self.__headers = [ + self.tr("Name"), + self.tr("Keywords"), + ] + + def removeRows(self, row, count, parent=QModelIndex()): + """ + Public method to remove entries from the model. + + @param row start row (integer) + @param count number of rows to remove (integer) + @param parent parent index (QModelIndex) + @return flag indicating success (boolean) + """ + if parent.isValid(): + return False + + if count <= 0: + return False + + if self.rowCount() <= 1: + return False + + lastRow = row + count - 1 + + self.beginRemoveRows(parent, row, lastRow) + + nameList = self.__manager.allEnginesNames() + for index in range(row, lastRow + 1): + self.__manager.removeEngine(nameList[index]) + + # removeEngine emits changed() + #self.endRemoveRows() + + return True + + def rowCount(self, parent=QModelIndex()): + """ + Public method to get the number of rows of the model. + + @param parent parent index (QModelIndex) + @return number of rows (integer) + """ + if parent.isValid(): + return 0 + else: + return self.__manager.enginesCount() + + def columnCount(self, parent=QModelIndex()): + """ + Public method to get the number of columns of the model. + + @param parent parent index (QModelIndex) + @return number of columns (integer) + """ + return 2 + + def flags(self, index): + """ + Public method to get flags for a model cell. + + @param index index of the model cell (QModelIndex) + @return flags (Qt.ItemFlags) + """ + if index.column() == 1: + return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable + else: + return Qt.ItemIsEnabled | Qt.ItemIsSelectable + + def data(self, index, role): + """ + Public method to get data from the model. + + @param index index to get data for (QModelIndex) + @param role role of the data to retrieve (integer) + @return requested data + """ + if index.row() >= self.__manager.enginesCount() or index.row() < 0: + return None + + engine = self.__manager.engine( + self.__manager.allEnginesNames()[index.row()]) + + if engine is None: + return None + + if index.column() == 0: + if role == Qt.DisplayRole: + return engine.name() + + elif role == Qt.DecorationRole: + image = engine.image() + if image.isNull(): + from WebBrowser.WebBrowserWindow import WebBrowserWindow + icon = WebBrowserWindow.icon(QUrl(engine.imageUrl())) + else: + icon = QIcon(QPixmap.fromImage(image)) + return icon + + elif role == Qt.ToolTipRole: + description = self.tr("<strong>Description:</strong> {0}")\ + .format(engine.description()) + if engine.providesSuggestions(): + description += "<br/>" + description += self.tr( + "<strong>Provides contextual suggestions</strong>") + + return description + elif index.column() == 1: + if role in [Qt.EditRole, Qt.DisplayRole]: + return ",".join(self.__manager.keywordsForEngine(engine)) + elif role == Qt.ToolTipRole: + return self.tr( + "Comma-separated list of keywords that may" + " be entered in the location bar followed by search terms" + " to search with this engine") + + return None + + def setData(self, index, value, role=Qt.EditRole): + """ + Public method to set the data of a model cell. + + @param index index of the model cell (QModelIndex) + @param value value to be set + @param role role of the data (integer) + @return flag indicating success (boolean) + """ + if not index.isValid() or index.column() != 1: + return False + + if index.row() >= self.rowCount() or index.row() < 0: + return False + + if role != Qt.EditRole: + return False + + engineName = self.__manager.allEnginesNames()[index.row()] + keywords = re.split("[ ,]+", value) + self.__manager.setKeywordsForEngine( + self.__manager.engine(engineName), keywords) + + return True + + def headerData(self, section, orientation, role=Qt.DisplayRole): + """ + Public method to get the header data. + + @param section section number (integer) + @param orientation header orientation (Qt.Orientation) + @param role data role (integer) + @return header data + """ + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + try: + return self.__headers[section] + except IndexError: + pass + + return None + + def __enginesChanged(self): + """ + Private slot handling a change of the registered engines. + """ + self.beginResetModel() + self.endResetModel()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchManager.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,599 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a manager for open search engines. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import pyqtSignal, QObject, QUrl, QFile, QDir, QIODevice, \ + QUrlQuery +from PyQt5.QtWidgets import QLineEdit, QInputDialog +from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply + +from E5Gui.E5Application import e5App +from E5Gui import E5MessageBox + +from Utilities.AutoSaver import AutoSaver +import Utilities +import Preferences + + +class OpenSearchManager(QObject): + """ + Class implementing a manager for open search engines. + + @signal changed() emitted to indicate a change + @signal currentEngineChanged() emitted to indicate a change of + the current search engine + """ + changed = pyqtSignal() + currentEngineChanged = pyqtSignal() + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object (QObject) + """ + if parent is None: + parent = e5App() + super(OpenSearchManager, self).__init__(parent) + + self.__replies = [] + self.__engines = {} + self.__keywords = {} + self.__current = "" + self.__loading = False + self.__saveTimer = AutoSaver(self, self.save) + + self.changed.connect(self.__saveTimer.changeOccurred) + + self.load() + + def close(self): + """ + Public method to close the open search engines manager. + """ + self.__saveTimer.saveIfNeccessary() + + def currentEngineName(self): + """ + Public method to get the name of the current search engine. + + @return name of the current search engine (string) + """ + return self.__current + + def setCurrentEngineName(self, name): + """ + Public method to set the current engine by name. + + @param name name of the new current engine (string) + """ + if name not in self.__engines: + return + + self.__current = name + self.currentEngineChanged.emit() + self.changed.emit() + + def currentEngine(self): + """ + Public method to get a reference to the current engine. + + @return reference to the current engine (OpenSearchEngine) + """ + if not self.__current or self.__current not in self.__engines: + return None + + return self.__engines[self.__current] + + def setCurrentEngine(self, engine): + """ + Public method to set the current engine. + + @param engine reference to the new current engine (OpenSearchEngine) + """ + if engine is None: + return + + for engineName in self.__engines: + if self.__engines[engineName] == engine: + self.setCurrentEngineName(engineName) + break + + def engine(self, name): + """ + Public method to get a reference to the named engine. + + @param name name of the engine (string) + @return reference to the engine (OpenSearchEngine) + """ + if name not in self.__engines: + return None + + return self.__engines[name] + + def engineExists(self, name): + """ + Public method to check, if an engine exists. + + @param name name of the engine (string) + @return flag indicating an existing engine (boolean) + """ + return name in self.__engines + + def allEnginesNames(self): + """ + Public method to get a list of all engine names. + + @return sorted list of all engine names (list of strings) + """ + return sorted(self.__engines.keys()) + + def enginesCount(self): + """ + Public method to get the number of available engines. + + @return number of engines (integer) + """ + return len(self.__engines) + + def addEngine(self, engine): + """ + Public method to add a new search engine. + + @param engine URL of the engine definition file (QUrl) or + name of a file containing the engine definition (string) + or reference to an engine object (OpenSearchEngine) + @return flag indicating success (boolean) + """ + from .OpenSearchEngine import OpenSearchEngine + if isinstance(engine, QUrl): + return self.__addEngineByUrl(engine) + elif isinstance(engine, OpenSearchEngine): + return self.__addEngineByEngine(engine) + else: + return self.__addEngineByFile(engine) + + def __addEngineByUrl(self, url): + """ + Private method to add a new search engine given its URL. + + @param url URL of the engine definition file (QUrl) + @return flag indicating success (boolean) + """ + if not url.isValid(): + return + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + + reply = WebBrowserWindow.networkManager().get(QNetworkRequest(url)) + reply.finished.connect(self.__engineFromUrlAvailable) + reply.setParent(self) + self.__replies.append(reply) + + return True + + def __addEngineByFile(self, filename): + """ + Private method to add a new search engine given a filename. + + @param filename name of a file containing the engine definition + (string) + @return flag indicating success (boolean) + """ + file_ = QFile(filename) + if not file_.open(QIODevice.ReadOnly): + return False + + from .OpenSearchReader import OpenSearchReader + reader = OpenSearchReader() + engine = reader.read(file_) + + if not self.__addEngineByEngine(engine): + return False + + return True + + def __addEngineByEngine(self, engine): + """ + Private method to add a new search engine given a reference to an + engine. + + @param engine reference to an engine object (OpenSearchEngine) + @return flag indicating success (boolean) + """ + if engine is None: + return False + + if not engine.isValid(): + return False + + if engine.name() in self.__engines: + return False + + engine.setParent(self) + self.__engines[engine.name()] = engine + + self.changed.emit() + + return True + + # TODO: Open Search: implement this right + def addEngineFromForm(self, res, view): + """ + Private method to add a new search engine from a form. + + @param res result of the JavaScript run on by + WebBrowserView.__addSearchEngine() + @type dict or None + @param view reference to the web browser view + @type WebBrowserView + """ + if not res: + return + + method = res["method"] + isPost = method == "post" + actionUrl = QUrl(res["action"]) + inputName = res["inputName"] + + if method != "get": + E5MessageBox.warning( + self, + self.tr("Method not supported"), + self.tr( + """{0} method is not supported.""").format(method.upper())) + return + + if actionUrl.isRelative(): + actionUrl = view.url().resolved(actionUrl) + + searchUrlQuery = QUrlQuery(actionUrl) + searchUrlQuery.addQueryItem(inputName, "{searchTerms}") + + inputFields = res["inputs"] + for inputField in inputFields: + name = inputField[0] + value = inputField[1] + + if not name or name == inputName or not value: + continue + + searchUrlQuery.addQueryItem(name, value) + + engineName, ok = QInputDialog.getText( + view, + self.tr("Engine name"), + self.tr("Enter a name for the engine"), + QLineEdit.Normal) + if not ok: + return + + actionUrl.setQuery(searchUrlQuery) + + from .OpenSearchEngine import OpenSearchEngine + engine = OpenSearchEngine() + engine.setName(engineName) + engine.setDescription(engineName) + engine.setSearchUrlTemplate(actionUrl.toDisplayString(QUrl.FullyDecoded)) + engine.setImage(view.icon().pixmap(16, 16).toImage()) + + self.__addEngineByEngine(engine) + + def removeEngine(self, name): + """ + Public method to remove an engine. + + @param name name of the engine (string) + """ + if len(self.__engines) <= 1: + return + + if name not in self.__engines: + return + + engine = self.__engines[name] + for keyword in [k for k in self.__keywords + if self.__keywords[k] == engine]: + del self.__keywords[keyword] + del self.__engines[name] + + file_ = QDir(self.enginesDirectory()).filePath( + self.generateEngineFileName(name)) + QFile.remove(file_) + + if name == self.__current: + self.setCurrentEngineName(list(self.__engines.keys())[0]) + + self.changed.emit() + + def generateEngineFileName(self, engineName): + """ + Public method to generate a valid engine file name. + + @param engineName name of the engine (string) + @return valid engine file name (string) + """ + fileName = "" + + # strip special characters + for c in engineName: + if c.isspace(): + fileName += '_' + continue + + if c.isalnum(): + fileName += c + + fileName += ".xml" + + return fileName + + def saveDirectory(self, dirName): + """ + Public method to save the search engine definitions to files. + + @param dirName name of the directory to write the files to (string) + """ + dir = QDir() + if not dir.mkpath(dirName): + return + dir.setPath(dirName) + + from .OpenSearchWriter import OpenSearchWriter + writer = OpenSearchWriter() + + for engine in list(self.__engines.values()): + name = self.generateEngineFileName(engine.name()) + fileName = dir.filePath(name) + + file = QFile(fileName) + if not file.open(QIODevice.WriteOnly): + continue + + writer.write(file, engine) + + def save(self): + """ + Public method to save the search engines configuration. + """ + if self.__loading: + return + + self.saveDirectory(self.enginesDirectory()) + + Preferences.setHelp("WebSearchEngine", self.__current) + keywords = [] + for k in self.__keywords: + if self.__keywords[k]: + keywords.append((k, self.__keywords[k].name())) + Preferences.setHelp("WebSearchKeywords", keywords) + + def loadDirectory(self, dirName): + """ + Public method to load the search engine definitions from files. + + @param dirName name of the directory to load the files from (string) + @return flag indicating success (boolean) + """ + if not QFile.exists(dirName): + return False + + success = False + + dir = QDir(dirName) + for name in dir.entryList(["*.xml"]): + fileName = dir.filePath(name) + if self.__addEngineByFile(fileName): + success = True + + return success + + def load(self): + """ + Public method to load the search engines configuration. + """ + self.__loading = True + self.__current = Preferences.getWebBrowser("WebSearchEngine") + keywords = Preferences.getWebBrowser("WebSearchKeywords") + + if not self.loadDirectory(self.enginesDirectory()): + self.restoreDefaults() + + for keyword, engineName in keywords: + self.__keywords[keyword] = self.engine(engineName) + + if self.__current not in self.__engines and \ + len(self.__engines) > 0: + self.__current = list(self.__engines.keys())[0] + + self.__loading = False + self.currentEngineChanged.emit() + + def restoreDefaults(self): + """ + Public method to restore the default search engines. + """ + from .OpenSearchReader import OpenSearchReader + from .DefaultSearchEngines import DefaultSearchEngines_rc # __IGNORE_WARNING__ + + defaultEngineFiles = ["Amazoncom.xml", "Bing.xml", + "DeEn_Beolingus.xml", "DuckDuckGo.xml", + "Facebook.xml", "Google.xml", + "Google_Im_Feeling_Lucky.xml", "LEO_DeuEng.xml", + "LinuxMagazin.xml", "Reddit.xml", "Wikia.xml", + "Wikia_en.xml", "Wikipedia.xml", + "Wiktionary.xml", "Yahoo.xml", "YouTube.xml", ] + # Keep this list in sync with the contents of the resource file. + + reader = OpenSearchReader() + for engineFileName in defaultEngineFiles: + engineFile = QFile(":/" + engineFileName) + if not engineFile.open(QIODevice.ReadOnly): + continue + engine = reader.read(engineFile) + self.__addEngineByEngine(engine) + + def enginesDirectory(self): + """ + Public method to determine the directory containing the search engine + descriptions. + + @return directory name (string) + """ + return os.path.join( + Utilities.getConfigDir(), "web_browser", "searchengines") + + def __confirmAddition(self, engine): + """ + Private method to confirm the addition of a new search engine. + + @param engine reference to the engine to be added (OpenSearchEngine) + @return flag indicating the engine shall be added (boolean) + """ + if engine is None or not engine.isValid(): + return False + + host = QUrl(engine.searchUrlTemplate()).host() + + res = E5MessageBox.yesNo( + None, + "", + self.tr( + """<p>Do you want to add the following engine to your""" + """ list of search engines?<br/><br/>Name: {0}<br/>""" + """Searches on: {1}</p>""").format(engine.name(), host)) + return res + + def __engineFromUrlAvailable(self): + """ + Private slot to add a search engine from the net. + """ + reply = self.sender() + if reply is None: + return + + if reply.error() != QNetworkReply.NoError: + reply.close() + if reply in self.__replies: + self.__replies.remove(reply) + return + + from .OpenSearchReader import OpenSearchReader + reader = OpenSearchReader() + engine = reader.read(reply) + + reply.close() + if reply in self.__replies: + self.__replies.remove(reply) + + if not engine.isValid(): + return + + if self.engineExists(engine.name()): + return + + if not self.__confirmAddition(engine): + return + + if not self.__addEngineByEngine(engine): + return + + def convertKeywordSearchToUrl(self, keywordSearch): + """ + Public method to get the search URL for a keyword search. + + @param keywordSearch search string for keyword search (string) + @return search URL (QUrl) + """ + try: + keyword, term = keywordSearch.split(" ", 1) + except ValueError: + return QUrl() + + if not term: + return QUrl() + + engine = self.engineForKeyword(keyword) + if engine: + return engine.searchUrl(term) + + return QUrl() + + def engineForKeyword(self, keyword): + """ + Public method to get the engine for a keyword. + + @param keyword keyword to get engine for (string) + @return reference to the search engine object (OpenSearchEngine) + """ + if keyword and keyword in self.__keywords: + return self.__keywords[keyword] + + return None + + def setEngineForKeyword(self, keyword, engine): + """ + Public method to set the engine for a keyword. + + @param keyword keyword to get engine for (string) + @param engine reference to the search engine object (OpenSearchEngine) + or None to remove the keyword + """ + if not keyword: + return + + if engine is None: + try: + del self.__keywords[keyword] + except KeyError: + pass + else: + self.__keywords[keyword] = engine + + self.changed.emit() + + def keywordsForEngine(self, engine): + """ + Public method to get the keywords for a given engine. + + @param engine reference to the search engine object (OpenSearchEngine) + @return list of keywords (list of strings) + """ + return [k for k in self.__keywords if self.__keywords[k] == engine] + + def setKeywordsForEngine(self, engine, keywords): + """ + Public method to set the keywords for an engine. + + @param engine reference to the search engine object (OpenSearchEngine) + @param keywords list of keywords (list of strings) + """ + if engine is None: + return + + for keyword in self.keywordsForEngine(engine): + del self.__keywords[keyword] + + for keyword in keywords: + if not keyword: + continue + + self.__keywords[keyword] = engine + + self.changed.emit() + + def enginesChanged(self): + """ + Public slot to tell the search engine manager, that something has + changed. + """ + self.changed.emit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchReader.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a reader for open search engine descriptions. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QXmlStreamReader, QIODevice, QCoreApplication + + +class OpenSearchReader(QXmlStreamReader): + """ + Class implementing a reader for open search engine descriptions. + """ + def read(self, device): + """ + Public method to read the description. + + @param device device to read the description from (QIODevice) + @return search engine object (OpenSearchEngine) + """ + self.clear() + + if not device.isOpen(): + device.open(QIODevice.ReadOnly) + + self.setDevice(device) + return self.__read() + + def __read(self): + """ + Private method to read and parse the description. + + @return search engine object (OpenSearchEngine) + """ + from .OpenSearchEngine import OpenSearchEngine + engine = OpenSearchEngine() + + while not self.isStartElement() and not self.atEnd(): + self.readNext() + + if self.name() != "OpenSearchDescription" or \ + self.namespaceUri() != "http://a9.com/-/spec/opensearch/1.1/": + self.raiseError(QCoreApplication.translate( + "OpenSearchReader", + "The file is not an OpenSearch 1.1 file.")) + return engine + + while not self.atEnd(): + self.readNext() + + if not self.isStartElement(): + continue + + if self.name() == "ShortName": + engine.setName(self.readElementText()) + + elif self.name() == "Description": + engine.setDescription(self.readElementText()) + + elif self.name() == "Url": + type_ = self.attributes().value("type") + url = self.attributes().value("template") + method = self.attributes().value("method") + + if type_ == "application/x-suggestions+json" and \ + engine.suggestionsUrlTemplate(): + continue + + if (not type_ or + type_ == "text/html" or + type_ == "application/xhtml+xml") and \ + engine.suggestionsUrlTemplate(): + continue + + if not url: + continue + + parameters = [] + + self.readNext() + + while not (self.isEndElement() and self.name() == "Url"): + if not self.isStartElement() or \ + (self.name() != "Param" and self.name() != "Parameter"): + self.readNext() + continue + + key = self.attributes().value("name") + value = self.attributes().value("value") + + if key and value: + parameters.append((key, value)) + + while not self.isEndElement(): + self.readNext() + + if type_ == "application/x-suggestions+json": + engine.setSuggestionsUrlTemplate(url) + engine.setSuggestionsParameters(parameters) + engine.setSuggestionsMethod(method) + elif not type_ or \ + type_ == "text/html" or \ + type_ == "application/xhtml+xml": + engine.setSearchUrlTemplate(url) + engine.setSearchParameters(parameters) + engine.setSearchMethod(method) + + elif self.name() == "Image": + engine.setImageUrl(self.readElementText()) + + if engine.name() and \ + engine.description() and \ + engine.suggestionsUrlTemplate() and \ + engine.searchUrlTemplate() and \ + engine.imageUrl(): + break + + return engine
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/OpenSearchWriter.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a writer for open search engine descriptions. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QXmlStreamWriter, QIODevice + + +class OpenSearchWriter(QXmlStreamWriter): + """ + Class implementing a writer for open search engine descriptions. + """ + def __init__(self): + """ + Constructor + """ + super(OpenSearchWriter, self).__init__() + + self.setAutoFormatting(True) + + def write(self, device, engine): + """ + Public method to write the description of an engine. + + @param device reference to the device to write to (QIODevice) + @param engine reference to the engine (OpenSearchEngine) + @return flag indicating success (boolean) + """ + if engine is None: + return False + + if not device.isOpen(): + if not device.open(QIODevice.WriteOnly): + return False + + self.setDevice(device) + self.__write(engine) + return True + + def __write(self, engine): + """ + Private method to write the description of an engine. + + @param engine reference to the engine (OpenSearchEngine) + """ + self.writeStartDocument() + self.writeStartElement("OpenSearchDescription") + self.writeDefaultNamespace("http://a9.com/-/spec/opensearch/1.1/") + + if engine.name(): + self.writeTextElement("ShortName", engine.name()) + + if engine.description(): + self.writeTextElement("Description", engine.description()) + + if engine.searchUrlTemplate(): + self.writeStartElement("Url") + self.writeAttribute("method", engine.searchMethod()) + self.writeAttribute("type", "text/html") + self.writeAttribute("template", engine.searchUrlTemplate()) + + if len(engine.searchParameters()) > 0: + self.writeNamespace( + "http://a9.com/-/spec/opensearch/extensions/" + "parameters/1.0/", "p") + for parameter in engine.searchParameters(): + self.writeStartElement("p:Parameter") + self.writeAttribute("name", parameter[0]) + self.writeAttribute("value", parameter[1]) + + self.writeEndElement() + + if engine.suggestionsUrlTemplate(): + self.writeStartElement("Url") + self.writeAttribute("method", engine.suggestionsMethod()) + self.writeAttribute("type", "application/x-suggestions+json") + self.writeAttribute("template", engine.suggestionsUrlTemplate()) + + if len(engine.suggestionsParameters()) > 0: + self.writeNamespace( + "http://a9.com/-/spec/opensearch/extensions/" + "parameters/1.0/", "p") + for parameter in engine.suggestionsParameters(): + self.writeStartElement("p:Parameter") + self.writeAttribute("name", parameter[0]) + self.writeAttribute("value", parameter[1]) + + self.writeEndElement() + + if engine.imageUrl(): + self.writeTextElement("Image", engine.imageUrl()) + + self.writeEndElement() + self.writeEndDocument()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/OpenSearch/__init__.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the opensearch search engine interfaces. +"""
--- a/WebBrowser/Tools/Scripts.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/Tools/Scripts.py Sun Feb 14 16:47:40 2016 +0100 @@ -14,6 +14,8 @@ from __future__ import unicode_literals +from PyQt5.QtCore import QUrlQuery, QUrl + from .WebBrowserTools import readAllFileContents @@ -176,5 +178,68 @@ }); } return out; - })()""" + })()""" + return source + + +def getOpenSearchLinks(): + """ + Function generating a script to extract all open search links. + + @return script to extract all open serach links + @rtype str + """ + source = """ + (function() { + var out = []; + var links = document.getElementsByTagName('link'); + for (var i = 0; i < links.length; ++i) { + var e = links[i]; + if (e.type == 'application/opensearchdescription+xml') { + out.push({ + url: e.getAttribute('href'), + title: e.getAttribute('title') + }); + } + } + return out; + })()""" return source + + +def sendPostData(url, data): + """ + Function generating a script to send Post data. + + @param url URL to send the data to + @type QUrl + @param data data to be sent + @type QByteArray + @return script to send Post data + @rtype str + """ + source = """ + (function() {{ + var form = document.createElement('form'); + form.setAttribute('method', 'POST'); + form.setAttribute('action', '{0}'); + var val; + {1} + form.submit(); + }})()""" + + valueSource = """ + val = document.createElement('input'); + val.setAttribute('type', 'hidden'); + val.setAttribute('name', '{0}'); + val.setAttribute('value', '{1}'); + form.appendChild(val);""" + + values = "" + query = QUrlQuery(data) + for name, value in query.queryItems(QUrl.FullyDecoded): + value = value.replace("'", "\\'") + name = name.replace("'", "\\'") + values += valueSource.format(name, value) + + return source.format(url.toString(), values)
--- a/WebBrowser/Tools/WebBrowserTools.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/Tools/WebBrowserTools.py Sun Feb 14 16:47:40 2016 +0100 @@ -44,3 +44,19 @@ return contents return QByteArray() + + +def containsSpace(string): + """ + Function to check, if a string contains whitespace characters. + + @param string string to be checked + @type str + @return flag indicating the presence of at least one whitespace character + @rtype bool + """ + for ch in string: + if ch.isspace(): + return True + + return False
--- a/WebBrowser/Tools/WebIconProvider.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/Tools/WebIconProvider.py Sun Feb 14 16:47:40 2016 +0100 @@ -185,8 +185,19 @@ urlStr = self.__urlToString(url) if urlStr in self.__iconsDB: return self.__iconsDB[urlStr] + elif scheme == "https": + return UI.PixmapCache.getIcon("securityHigh32.png") else: return UI.PixmapCache.getIcon("defaultIcon.png") + + def clear(self): + """ + Public method to clear the icons cache. + """ + self.load() + self.__iconsDB = {} + self.changed.emit() + self.__saveTimer.saveIfNeccessary() __WebIconProvider = None
--- a/WebBrowser/UrlBar/UrlBar.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/UrlBar/UrlBar.py Sun Feb 14 16:47:40 2016 +0100 @@ -304,13 +304,13 @@ progress = self.__browser.progress() if progress == 0 or progress == 100: if self.__browser.url().scheme() == "https": - if QSslCertificate is not None: - if self.__browser.page().hasValidSslInfo(): - backgroundColor = Preferences.getWebBrowser( - "SaveUrlColor") - else: - backgroundColor = Preferences.getWebBrowser( - "SaveUrlColor") +## if QSslCertificate is not None: +## if self.__browser.page().hasValidSslInfo(): +## backgroundColor = Preferences.getWebBrowser( +## "SaveUrlColor") +## else: + backgroundColor = Preferences.getWebBrowser( + "SaveUrlColor") p.setBrush(QPalette.Base, backgroundColor) p.setBrush(QPalette.Text, foregroundColor) else:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/WebBrowserClearPrivateDataDialog.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to select which private data to clear. +""" + +from __future__ import unicode_literals + +from PyQt5.QtWidgets import QDialog + +from .Ui_WebBrowserClearPrivateDataDialog import \ + Ui_WebBrowserClearPrivateDataDialog + + +class WebBrowserClearPrivateDataDialog(QDialog, + Ui_WebBrowserClearPrivateDataDialog): + """ + Class implementing a dialog to select which private data to clear. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super(WebBrowserClearPrivateDataDialog, self).__init__(parent) + self.setupUi(self) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def getData(self): + """ + Public method to get the data from the dialog. + + @return tuple with flags indicating which data to clear + (browsing history, search history, favicons, disk cache, cookies, + passwords, web databases, downloads, flash, zoom values) and the + selected history period in milliseconds (tuple of booleans and + integer) + """ + index = self.historyCombo.currentIndex() + if index == 0: + # last hour + historyPeriod = 60 * 60 * 1000 + elif index == 1: + # last day + historyPeriod = 24 * 60 * 60 * 1000 + elif index == 2: + # last week + historyPeriod = 7 * 24 * 60 * 60 * 1000 + elif index == 3: + # last four weeks + historyPeriod = 4 * 7 * 24 * 60 * 60 * 1000 + elif index == 4: + # clear all + historyPeriod = 0 + + return (self.historyCheckBox.isChecked(), + self.searchCheckBox.isChecked(), + self.iconsCheckBox.isChecked(), + self.cacheCheckBox.isChecked(), + self.cookiesCheckBox.isChecked(), + self.passwordsCheckBox.isChecked(), + self.databasesCheckBox.isChecked(), + self.downloadsCheckBox.isChecked(), + self.flashCookiesCheckBox.isChecked(), + self.zoomCheckBox.isChecked(), + historyPeriod)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/WebBrowserClearPrivateDataDialog.ui Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,282 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>WebBrowserClearPrivateDataDialog</class> + <widget class="QDialog" name="WebBrowserClearPrivateDataDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>305</width> + <height>353</height> + </rect> + </property> + <property name="windowTitle"> + <string>Clear Private Data</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="historyCheckBox"> + <property name="toolTip"> + <string>Select to clear the browsing history</string> + </property> + <property name="text"> + <string>&Browsing History</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QComboBox" name="historyCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the history period to be deleted</string> + </property> + <item> + <property name="text"> + <string>Last Hour</string> + </property> + </item> + <item> + <property name="text"> + <string>Last Day</string> + </property> + </item> + <item> + <property name="text"> + <string>Last Week</string> + </property> + </item> + <item> + <property name="text"> + <string>Last 4 Weeks</string> + </property> + </item> + <item> + <property name="text"> + <string>Whole Period</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="searchCheckBox"> + <property name="toolTip"> + <string>Select to clear the search history</string> + </property> + <property name="text"> + <string>&Search History</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="downloadsCheckBox"> + <property name="toolTip"> + <string>Select to clear the download history</string> + </property> + <property name="text"> + <string>Download &History</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="cookiesCheckBox"> + <property name="toolTip"> + <string>Select to clear the cookies</string> + </property> + <property name="text"> + <string>&Cookies</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="cacheCheckBox"> + <property name="toolTip"> + <string>Select to clear the disk cache</string> + </property> + <property name="text"> + <string>Cached &Web Pages</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="iconsCheckBox"> + <property name="toolTip"> + <string>Select to clear the website icons</string> + </property> + <property name="text"> + <string>Website &Icons</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="passwordsCheckBox"> + <property name="toolTip"> + <string>Select to clear the saved passwords</string> + </property> + <property name="text"> + <string>Saved &Passwords</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="databasesCheckBox"> + <property name="toolTip"> + <string>Select to delete all web databases</string> + </property> + <property name="text"> + <string>Web &Databases</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="zoomCheckBox"> + <property name="toolTip"> + <string>Select to delete all remembered zoom settings</string> + </property> + <property name="text"> + <string>&Zoom Settings</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="flashCookiesCheckBox"> + <property name="toolTip"> + <string>Select to clear cookies set by the Adobe Flash Player</string> + </property> + <property name="text"> + <string>Cookies from Adobe &Flash Player</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>historyCheckBox</tabstop> + <tabstop>historyCombo</tabstop> + <tabstop>searchCheckBox</tabstop> + <tabstop>downloadsCheckBox</tabstop> + <tabstop>cookiesCheckBox</tabstop> + <tabstop>cacheCheckBox</tabstop> + <tabstop>iconsCheckBox</tabstop> + <tabstop>passwordsCheckBox</tabstop> + <tabstop>databasesCheckBox</tabstop> + <tabstop>zoomCheckBox</tabstop> + <tabstop>flashCookiesCheckBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>WebBrowserClearPrivateDataDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>267</x> + <y>342</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>252</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>WebBrowserClearPrivateDataDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>342</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>252</y> + </hint> + </hints> + </connection> + <connection> + <sender>historyCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>historyCombo</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>65</x> + <y>19</y> + </hint> + <hint type="destinationlabel"> + <x>83</x> + <y>45</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- a/WebBrowser/WebBrowserPage.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/WebBrowserPage.py Sun Feb 14 16:47:40 2016 +0100 @@ -35,6 +35,7 @@ from .JavaScript.ExternalJsObject import ExternalJsObject from .Tools.WebHitTestResult import WebHitTestResult +from .Tools import Scripts import Preferences import UI.PixmapCache @@ -434,7 +435,7 @@ def __loadStarted(self): """ - Private method to handle the loadStarted signal. + Private slot to handle the loadStarted signal. """ self.__adBlockedEntries = [] ##
--- a/WebBrowser/WebBrowserTabWidget.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/WebBrowserTabWidget.py Sun Feb 14 16:47:40 2016 +0100 @@ -297,14 +297,12 @@ """ self.newBrowser() - # TODO: remove requestData from signature def newBrowser(self, link=None, requestData=None, position=-1): """ Public method to create a new web browser tab. @param link link to be shown (string or QUrl) - @param requestData tuple containing the request data (QNetworkRequest, - QNetworkAccessManager.Operation, QByteArray) + @param requestData page load request data (LoadRequest) @keyparam position position to create the new tab at or -1 to add it to the end (integer) """ @@ -317,18 +315,18 @@ from .UrlBar.UrlBar import UrlBar urlbar = UrlBar(self.__mainWindow, self) -## if self.__historyCompleter is None: -## import Helpviewer.HelpWindow -## from .History.HistoryCompleter import HistoryCompletionModel, \ -## HistoryCompleter -## self.__historyCompletionModel = HistoryCompletionModel(self) -## self.__historyCompletionModel.setSourceModel( -## Helpviewer.HelpWindow.HelpWindow.historyManager() -## .historyFilterModel()) -## self.__historyCompleter = HistoryCompleter( -## self.__historyCompletionModel, self) -## self.__historyCompleter.activated[str].connect(self.__pathSelected) -## urlbar.setCompleter(self.__historyCompleter) + if self.__historyCompleter is None: + import Helpviewer.HelpWindow + from .History.HistoryCompleter import HistoryCompletionModel, \ + HistoryCompleter + self.__historyCompletionModel = HistoryCompletionModel(self) + self.__historyCompletionModel.setSourceModel( + Helpviewer.HelpWindow.HelpWindow.historyManager() + .historyFilterModel()) + self.__historyCompleter = HistoryCompleter( + self.__historyCompletionModel, self) + self.__historyCompleter.activated[str].connect(self.__pathSelected) + urlbar.setCompleter(self.__historyCompleter) urlbar.returnPressed.connect(self.__lineEditReturnPressed) if position == -1: self.__stackedUrlBar.addWidget(urlbar) @@ -364,8 +362,7 @@ self.__closeButton.setEnabled(True) self.__navigationButton.setEnabled(True) -## if not linkName and not requestData: - if not linkName: + if not linkName and not requestData: if Preferences.getWebBrowser("StartupBehavior") == 0: linkName = Preferences.getWebBrowser("HomePage") elif Preferences.getWebBrowser("StartupBehavior") == 1: @@ -381,18 +378,16 @@ index, self.__elide(browser.documentTitle().replace("&", "&&"))) self.setTabToolTip(index, browser.documentTitle()) -## elif requestData: -## browser.load(*requestData) + elif requestData: + browser.load(requestData) - # TODO: remove requestData from signature def newBrowserAfter(self, browser, link=None, requestData=None): """ Public method to create a new web browser tab after a given one. @param browser reference to the browser to add after (WebBrowserView) @param link link to be shown (string or QUrl) - @param requestData tuple containing the request data (QNetworkRequest, - QNetworkAccessManager.Operation, QByteArray) + @param requestData page load request data (LoadRequest) """ if browser: position = self.indexOf(browser) + 1 @@ -893,14 +888,14 @@ ## None, (request, QNetworkAccessManager.GetOperation, b"")) self.currentBrowser().setFocus() -## def __pathSelected(self, path): -## """ -## Private slot called when a URL is selected from the completer. -## -## @param path path to be shown (string) -## """ -## url = self.__guessUrlFromPath(path) -## self.currentBrowser().setSource(url) + def __pathSelected(self, path): + """ + Private slot called when a URL is selected from the completer. + + @param path path to be shown (string) + """ + url = self.__guessUrlFromPath(path) + self.currentBrowser().setSource(url) def __guessUrlFromPath(self, path): """ @@ -909,12 +904,11 @@ @param path path string to guess an URL for (string) @return guessed URL (QUrl) """ - # TODO: Open Search -## manager = self.__mainWindow.openSearchManager() -## path = Utilities.fromNativeSeparators(path) -## url = manager.convertKeywordSearchToUrl(path) -## if url.isValid(): -## return url + manager = self.__mainWindow.openSearchManager() + path = Utilities.fromNativeSeparators(path) + url = manager.convertKeywordSearchToUrl(path) + if url.isValid(): + return url try: url = QUrl.fromUserInput(path) @@ -927,8 +921,8 @@ # TODO: extend this logic to about:config (open config dialog) -## if url.scheme() in ["s", "search"]: -## url = manager.currentEngine().searchUrl(url.path().strip()) + if url.scheme() in ["s", "search"]: + url = manager.currentEngine().searchUrl(url.path().strip()) if url.scheme() != "" and \ (url.host() != "" or url.path() != ""):
--- a/WebBrowser/WebBrowserView.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/WebBrowserView.py Sun Feb 14 16:47:40 2016 +0100 @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \ QLineEdit, QLabel, QToolTip, QFrame, QDialog from PyQt5.QtPrintSupport import QPrinter, QPrintDialog -from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest, QHostInfo from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from E5Gui import E5MessageBox, E5FileDialog @@ -31,6 +31,9 @@ from .WebBrowserPage import WebBrowserPage from .Tools.WebIconLoader import WebIconLoader +from .Tools import WebBrowserTools, Scripts + +from .Network.LoadRequest import LoadRequest, LoadRequestOperations import Preferences import UI.PixmapCache @@ -128,9 +131,8 @@ ## self.page().databaseQuotaExceeded.connect(self.__databaseQuotaExceeded) - # TODO: Open Search -## self.__mw.openSearchManager().currentEngineChanged.connect( -## self.__currentEngineChanged) + self.__mw.openSearchManager().currentEngineChanged.connect( + self.__currentEngineChanged) self.setAcceptDrops(True) @@ -190,48 +192,12 @@ ## self.__addExternalBinding) ## frame.addToJavaScriptWindowObject("external", self.__javaScriptBinding) ## -## def linkedResources(self, relation=""): -## """ -## Public method to extract linked resources. -## -## @param relation relation to extract (string) -## @return list of linked resources (list of LinkedResource) -## """ -## resources = [] -## -## baseUrl = self.page().mainFrame().baseUrl() -## -## linkElements = self.page().mainFrame().findAllElements( -## "html > head > link") -## -## for linkElement in linkElements.toList(): -## rel = linkElement.attribute("rel") -## href = linkElement.attribute("href") -## type_ = linkElement.attribute("type") -## title = linkElement.attribute("title") -## -## if href == "" or type_ == "": -## continue -## if relation and rel != relation: -## continue -## -## resource = LinkedResource() -## resource.rel = rel -## resource.type_ = type_ -## resource.href = baseUrl.resolved( -## QUrl.fromEncoded(href.encode("utf-8"))) -## resource.title = title -## -## resources.append(resource) -## -## return resources -## -## def __currentEngineChanged(self): -## """ -## Private slot to track a change of the current search engine. -## """ -## if self.url().toString() == "eric:home": -## self.reload() + def __currentEngineChanged(self): + """ + Private slot to track a change of the current search engine. + """ + if self.url().toString() == "eric:home": + self.reload() def mainWindow(self): """ @@ -242,6 +208,61 @@ """ return self.__mw + def load(self, urlOrRequest): + """ + Public method to load a web site. + + @param urlOrRequest URL or request object + @type QUrl or LoadRequest + """ + if isinstance(urlOrRequest, QUrl): + super(WebBrowserView, self).load(urlOrRequest) + elif isinstance(urlOrRequest, LoadRequest): + reqUrl = urlOrRequest.url() + if reqUrl.isEmpty(): + return + + if reqUrl.scheme() == "javascript": + script = reqUrl.toString()[11:] + # check if the javascript script is percent encode + # i.e. it contains '%' characters + if '%' in script: + script = QUrl.fromPercentEncoding( + QByteArray(script.encode("utf-8"))) + self.page().runJavaScript(script) + return + + if self.__isUrlValid(reqUrl): + self.loadRequest(urlOrRequest) + return + + # ensure proper loading of hosts without a '.' + if not reqUrl.isEmpty() and \ + reqUrl.scheme() and \ + not WebBrowserTools.containsSpace(reqUrl.path()) and \ + '.' not in reqUrl.path(): + u = QUrl("http://" + reqUrl.path()) + if u.isValid(): + info = QHostInfo.fromName(u.path()) + if info.error() == QHostInfo.NoError: + req = LoadRequest(urlOrRequest) + req.setUrl(u) + self.loadRequest(req) + return + + def loadRequest(self, req): + """ + Public method to load a page via a load request object. + + @param req loaf request object + @type LoadRequest + """ + if req.Operation == LoadRequestOperations.GetOperation: + self.load(req.url()) + else: + self.page().runJavaScript( + Scripts.sendPostData(req.url(), req.data())) + # TODO: eliminate requestData, add param to get rid of __ctrlPressed def setSource(self, name, requestData=None): """ @@ -786,21 +807,20 @@ UI.PixmapCache.getIcon("mailSend.png"), self.tr("Send Text"), self.__sendLink).setData(self.selectedText()) - # TODO: OpenSearch # TODO: OpenSearch: add a search entry using the current engine -## self.__searchMenu = menu.addMenu(self.tr("Search with...")) -## -## from .OpenSearch.OpenSearchEngineAction import \ -## OpenSearchEngineAction -## engineNames = self.__mw.openSearchManager().allEnginesNames() -## for engineName in engineNames: -## engine = self.__mw.openSearchManager().engine(engineName) -## act = OpenSearchEngineAction(engine, self.__searchMenu) -## act.setData(engineName) -## self.__searchMenu.addAction(act) -## self.__searchMenu.triggered.connect(self.__searchRequested) -## -## menu.addSeparator() + self.__searchMenu = menu.addMenu(self.tr("Search with...")) + + from .OpenSearch.OpenSearchEngineAction import \ + OpenSearchEngineAction + engineNames = self.__mw.openSearchManager().allEnginesNames() + for engineName in engineNames: + engine = self.__mw.openSearchManager().engine(engineName) + act = OpenSearchEngineAction(engine, self.__searchMenu) + act.setData(engineName) + self.__searchMenu.addAction(act) + self.__searchMenu.triggered.connect(self.__searchRequested) + + menu.addSeparator() # TODO: Languages Dialog ## from .HelpLanguagesDialog import HelpLanguagesDialog @@ -1074,11 +1094,10 @@ """ from .Tools import Scripts script = Scripts.getFormData(self.__clickedPos) - # TODO: OpenSearch: add ew method -## self.page().runJavaScript( -## script, -## lambda res: self.__mw.openSearchManager().addEngineFromForm( -## res, self)) + self.page().runJavaScript( + script, + lambda res: self.__mw.openSearchManager().addEngineFromForm( + res, self)) # TODO: WebInspector ## def __webInspector(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/WebBrowserWebSearchWidget.py Sun Feb 14 16:47:40 2016 +0100 @@ -0,0 +1,404 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a web search widget for the web browser. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSignal, QUrl, QModelIndex, QTimer, Qt +from PyQt5.QtGui import QStandardItem, QStandardItemModel, QFont, QIcon, \ + QPixmap +from PyQt5.QtWidgets import QMenu, QCompleter +from PyQt5.QtWebEngineWidgets import QWebEnginePage + +import UI.PixmapCache + +import Preferences + +from E5Gui.E5LineEdit import E5ClearableLineEdit + + +class WebBrowserWebSearchWidget(E5ClearableLineEdit): + """ + Class implementing a web search widget for the web browser. + + @signal search(QUrl) emitted when the search should be done + """ + search = pyqtSignal(QUrl) + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super(WebBrowserWebSearchWidget, self).__init__(parent) + + from E5Gui.E5LineEdit import E5LineEdit + from E5Gui.E5LineEditButton import E5LineEditButton + from .OpenSearch.OpenSearchManager import OpenSearchManager + + self.__mw = parent + + self.__openSearchManager = OpenSearchManager(self) + self.__openSearchManager.currentEngineChanged.connect( + self.__currentEngineChanged) + self.__currentEngine = "" + + self.__enginesMenu = QMenu(self) + + self.__engineButton = E5LineEditButton(self) + self.__engineButton.setMenu(self.__enginesMenu) + self.addWidget(self.__engineButton, E5LineEdit.LeftSide) + + self.__searchButton = E5LineEditButton(self) + self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch.png")) + self.addWidget(self.__searchButton, E5LineEdit.LeftSide) + + self.__model = QStandardItemModel(self) + self.__completer = QCompleter() + self.__completer.setModel(self.__model) + self.__completer.setCompletionMode( + QCompleter.UnfilteredPopupCompletion) + self.__completer.setWidget(self) + + self.__searchButton.clicked.connect(self.__searchButtonClicked) + self.textEdited.connect(self.__textEdited) + self.returnPressed.connect(self.__searchNow) + self.__completer.activated[QModelIndex].connect( + self.__completerActivated) + self.__completer.highlighted[QModelIndex].connect( + self.__completerHighlighted) + self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu) + + self.__suggestionsItem = None + self.__suggestions = [] + self.__suggestTimer = None + self.__suggestionsEnabled = Preferences.getWebBrowser( + "WebSearchSuggestions") + + self.__recentSearchesItem = None + self.__recentSearches = [] + self.__maxSavedSearches = 10 + + self.__engine = None + self.__loadSearches() + self.__setupCompleterMenu() + self.__currentEngineChanged() + + def __searchNow(self): + """ + Private slot to perform the web search. + """ + searchText = self.text() + if not searchText: + return + + import WebBrowser.WebBrowserWindow + if WebBrowser.WebBrowserWindow.WebBrowserWindow\ + .mainWindow().getWindow().isPrivate(): + return + + if searchText in self.__recentSearches: + self.__recentSearches.remove(searchText) + self.__recentSearches.insert(0, searchText) + if len(self.__recentSearches) > self.__maxSavedSearches: + self.__recentSearches = \ + self.__recentSearches[:self.__maxSavedSearches] + self.__setupCompleterMenu() + + self.__mw.currentBrowser().setFocus() + self.__mw.currentBrowser().load( + self.__openSearchManager.currentEngine().searchUrl(searchText)) + + def __setupCompleterMenu(self): + """ + Private method to create the completer menu. + """ + if not self.__suggestions or \ + (self.__model.rowCount() > 0 and + self.__model.item(0) != self.__suggestionsItem): + self.__model.clear() + self.__suggestionsItem = None + else: + self.__model.removeRows(1, self.__model.rowCount() - 1) + + boldFont = QFont() + boldFont.setBold(True) + + if self.__suggestions: + if self.__model.rowCount() == 0: + if not self.__suggestionsItem: + self.__suggestionsItem = QStandardItem( + self.tr("Suggestions")) + self.__suggestionsItem.setFont(boldFont) + self.__model.appendRow(self.__suggestionsItem) + + for suggestion in self.__suggestions: + self.__model.appendRow(QStandardItem(suggestion)) + + if not self.__recentSearches: + self.__recentSearchesItem = QStandardItem( + self.tr("No Recent Searches")) + self.__recentSearchesItem.setFont(boldFont) + self.__model.appendRow(self.__recentSearchesItem) + else: + self.__recentSearchesItem = QStandardItem( + self.tr("Recent Searches")) + self.__recentSearchesItem.setFont(boldFont) + self.__model.appendRow(self.__recentSearchesItem) + for recentSearch in self.__recentSearches: + self.__model.appendRow(QStandardItem(recentSearch)) + + view = self.__completer.popup() + view.setFixedHeight(view.sizeHintForRow(0) * self.__model.rowCount() + + view.frameWidth() * 2) + + self.__searchButton.setEnabled( + bool(self.__recentSearches or self.__suggestions)) + + def __completerActivated(self, index): + """ + Private slot handling the selection of an entry from the completer. + + @param index index of the item (QModelIndex) + """ + if self.__suggestionsItem and \ + self.__suggestionsItem.index().row() == index.row(): + return + + if self.__recentSearchesItem and \ + self.__recentSearchesItem.index().row() == index.row(): + return + + self.__searchNow() + + def __completerHighlighted(self, index): + """ + Private slot handling the highlighting of an entry of the completer. + + @param index index of the item (QModelIndex) + @return flah indicating a successful highlighting (boolean) + """ + if self.__suggestionsItem and \ + self.__suggestionsItem.index().row() == index.row(): + return False + + if self.__recentSearchesItem and \ + self.__recentSearchesItem.index().row() == index.row(): + return False + + self.setText(index.data()) + return True + + def __textEdited(self, txt): + """ + Private slot to handle changes of the search text. + + @param txt search text (string) + """ + if self.__suggestionsEnabled: + if self.__suggestTimer is None: + self.__suggestTimer = QTimer(self) + self.__suggestTimer.setSingleShot(True) + self.__suggestTimer.setInterval(200) + self.__suggestTimer.timeout.connect(self.__getSuggestions) + self.__suggestTimer.start() + else: + self.__completer.setCompletionPrefix(txt) + self.__completer.complete() + + def __getSuggestions(self): + """ + Private slot to get search suggestions from the configured search + engine. + """ + searchText = self.text() + if searchText: + self.__openSearchManager.currentEngine()\ + .requestSuggestions(searchText) + + def __newSuggestions(self, suggestions): + """ + Private slot to receive a new list of suggestions. + + @param suggestions list of suggestions (list of strings) + """ + self.__suggestions = suggestions + self.__setupCompleterMenu() + self.__completer.complete() + + def __showEnginesMenu(self): + """ + Private slot to handle the display of the engines menu. + """ + self.__enginesMenu.clear() + + from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction + engineNames = self.__openSearchManager.allEnginesNames() + for engineName in engineNames: + engine = self.__openSearchManager.engine(engineName) + action = OpenSearchEngineAction(engine, self.__enginesMenu) + action.setData(engineName) + action.triggered.connect(self.__changeCurrentEngine) + self.__enginesMenu.addAction(action) + + if self.__openSearchManager.currentEngineName() == engineName: + action.setCheckable(True) + action.setChecked(True) + + cb = self.__mw.currentBrowser() + from .Tools import Scripts + script = Scripts.getOpenSearchLinks() + cb.page().runJavaScript(script, self.__showEnginesMenuCallback) + + def __showEnginesMenuCallback(self, res): + """ + Private method handling the open search links callback. + + @param res result of the JavaScript + @type list of dict + """ + cb = self.__mw.currentBrowser() + if res: + self.__enginesMenu.addSeparator() + for entry in res: + url = cb.url().resolved(QUrl(entry["url"])) + title = entry["title"] + if url.isEmpty(): + continue + if not title: + title = cb.title() + + action = self.__enginesMenu.addAction( + self.tr("Add '{0}'").format(title), + self.__addEngineFromUrl) + action.setData(url) + action.setIcon(cb.icon()) + + self.__enginesMenu.addSeparator() + self.__enginesMenu.addAction(self.__mw.searchEnginesAction()) + + if self.__recentSearches: + self.__enginesMenu.addAction(self.tr("Clear Recent Searches"), + self.clear) + + def __changeCurrentEngine(self): + """ + Private slot to handle the selection of a search engine. + """ + action = self.sender() + if action is not None: + name = action.data() + self.__openSearchManager.setCurrentEngineName(name) + + def __addEngineFromUrl(self): + """ + Private slot to add a search engine given its URL. + """ + action = self.sender() + if action is not None: + url = action.data() + if not isinstance(url, QUrl): + return + + self.__openSearchManager.addEngine(url) + + def __searchButtonClicked(self): + """ + Private slot to show the search menu via the search button. + """ + self.__setupCompleterMenu() + self.__completer.complete() + + def clear(self): + """ + Public method to clear all private data. + """ + self.__recentSearches = [] + self.__setupCompleterMenu() + super(WebBrowserWebSearchWidget, self).clear() + self.clearFocus() + + def preferencesChanged(self): + """ + Public method to handle the change of preferences. + """ + self.__suggestionsEnabled = Preferences.getWebBrowser( + "WebSearchSuggestions") + if not self.__suggestionsEnabled: + self.__suggestions = [] + self.__setupCompleterMenu() + + def saveSearches(self): + """ + Public method to save the recently performed web searches. + """ + Preferences.Prefs.settings.setValue( + 'WebBrowser/WebSearches', self.__recentSearches) + + def __loadSearches(self): + """ + Private method to load the recently performed web searches. + """ + searches = Preferences.Prefs.settings.value('WebBrowser/WebSearches') + if searches is not None: + self.__recentSearches = searches + + def openSearchManager(self): + """ + Public method to get a reference to the opensearch manager object. + + @return reference to the opensearch manager object (OpenSearchManager) + """ + return self.__openSearchManager + + def __currentEngineChanged(self): + """ + Private slot to track a change of the current search engine. + """ + if self.__openSearchManager.engineExists(self.__currentEngine): + oldEngine = self.__openSearchManager.engine(self.__currentEngine) + oldEngine.imageChanged.disconnect(self.__engineImageChanged) + if self.__suggestionsEnabled: + oldEngine.suggestions.disconnect(self.__newSuggestions) + + newEngine = self.__openSearchManager.currentEngine() + if newEngine.networkAccessManager() is None: + newEngine.setNetworkAccessManager(self.__mw.networkManager()) + newEngine.imageChanged.connect(self.__engineImageChanged) + if self.__suggestionsEnabled: + newEngine.suggestions.connect(self.__newSuggestions) + + self.setInactiveText(self.__openSearchManager.currentEngineName()) + self.__currentEngine = self.__openSearchManager.currentEngineName() + self.__engineButton.setIcon(QIcon(QPixmap.fromImage( + self.__openSearchManager.currentEngine().image()))) + self.__suggestions = [] + self.__setupCompleterMenu() + + def __engineImageChanged(self): + """ + Private slot to handle a change of the current search engine icon. + """ + self.__engineButton.setIcon(QIcon(QPixmap.fromImage( + self.__openSearchManager.currentEngine().image()))) + + def mousePressEvent(self, evt): + """ + Protected method called by a mouse press event. + + @param evt reference to the mouse event (QMouseEvent) + """ + if evt.button() == Qt.XButton1: + self.__mw.currentBrowser().triggerPageAction( + QWebEnginePage.Back) + elif evt.button() == Qt.XButton2: + self.__mw.currentBrowser().triggerPageAction( + QWebEnginePage.Forward) + else: + super(WebBrowserWebSearchWidget, self).mousePressEvent(evt)
--- a/WebBrowser/WebBrowserWindow.py Sat Feb 13 14:17:39 2016 +0100 +++ b/WebBrowser/WebBrowserWindow.py Sun Feb 14 16:47:40 2016 +0100 @@ -1492,57 +1492,55 @@ ## self.__searchEngine.reindexDocumentation) ## self.__actions.append(self.reindexDocumentationAct) - # TODO: Clear Private Data -## self.clearPrivateDataAct = E5Action( -## self.tr('Clear private data'), -## self.tr('&Clear private data'), -## 0, 0, -## self, 'webbrowser_clear_private_data') -## self.clearPrivateDataAct.setStatusTip(self.tr( -## 'Clear private data')) -## self.clearPrivateDataAct.setWhatsThis(self.tr( -## """<b>Clear private data</b>""" -## """<p>Clears the private data like browsing history, search""" -## """ history or the favicons database.</p>""" -## )) -## if not self.__initShortcutsOnly: -## self.clearPrivateDataAct.triggered.connect( -## self.__clearPrivateData) -## self.__actions.append(self.clearPrivateDataAct) -## -## self.clearIconsAct = E5Action( -## self.tr('Clear icons database'), -## self.tr('Clear &icons database'), -## 0, 0, -## self, 'webbrowser_clear_icons_db') -## self.clearIconsAct.setStatusTip(self.tr( -## 'Clear the database of favicons')) -## self.clearIconsAct.setWhatsThis(self.tr( -## """<b>Clear icons database</b>""" -## """<p>Clears the database of favicons of previously visited""" -## """ URLs.</p>""" -## )) -## if not self.__initShortcutsOnly: -## self.clearIconsAct.triggered.connect(self.__clearIconsDatabase) -## self.__actions.append(self.clearIconsAct) + self.clearPrivateDataAct = E5Action( + self.tr('Clear private data'), + self.tr('&Clear private data'), + 0, 0, + self, 'webbrowser_clear_private_data') + self.clearPrivateDataAct.setStatusTip(self.tr( + 'Clear private data')) + self.clearPrivateDataAct.setWhatsThis(self.tr( + """<b>Clear private data</b>""" + """<p>Clears the private data like browsing history, search""" + """ history or the favicons database.</p>""" + )) + if not self.__initShortcutsOnly: + self.clearPrivateDataAct.triggered.connect( + self.__clearPrivateData) + self.__actions.append(self.clearPrivateDataAct) - # TODO: Open Search -## self.searchEnginesAct = E5Action( -## self.tr('Configure Search Engines'), -## self.tr('Configure Search &Engines...'), -## 0, 0, -## self, 'webbrowser_search_engines') -## self.searchEnginesAct.setStatusTip(self.tr( -## 'Configure the available search engines')) -## self.searchEnginesAct.setWhatsThis(self.tr( -## """<b>Configure Search Engines...</b>""" -## """<p>Opens a dialog to configure the available search""" -## """ engines.</p>""" -## )) -## if not self.__initShortcutsOnly: -## self.searchEnginesAct.triggered.connect( -## self.__showEnginesConfigurationDialog) -## self.__actions.append(self.searchEnginesAct) + self.clearIconsAct = E5Action( + self.tr('Clear icons database'), + self.tr('Clear &icons database'), + 0, 0, + self, 'webbrowser_clear_icons_db') + self.clearIconsAct.setStatusTip(self.tr( + 'Clear the database of favicons')) + self.clearIconsAct.setWhatsThis(self.tr( + """<b>Clear icons database</b>""" + """<p>Clears the database of favicons of previously visited""" + """ URLs.</p>""" + )) + if not self.__initShortcutsOnly: + self.clearIconsAct.triggered.connect(self.__clearIconsDatabase) + self.__actions.append(self.clearIconsAct) + + self.searchEnginesAct = E5Action( + self.tr('Configure Search Engines'), + self.tr('Configure Search &Engines...'), + 0, 0, + self, 'webbrowser_search_engines') + self.searchEnginesAct.setStatusTip(self.tr( + 'Configure the available search engines')) + self.searchEnginesAct.setWhatsThis(self.tr( + """<b>Configure Search Engines...</b>""" + """<p>Opens a dialog to configure the available search""" + """ engines.</p>""" + )) + if not self.__initShortcutsOnly: + self.searchEnginesAct.triggered.connect( + self.__showEnginesConfigurationDialog) + self.__actions.append(self.searchEnginesAct) # TODO: Passwords ## self.passwordsAct = E5Action( @@ -1863,8 +1861,8 @@ ## menu.addAction(self.featurePermissionAct) ## menu.addSeparator() menu.addAction(self.editMessageFilterAct) -## menu.addSeparator() -## menu.addAction(self.searchEnginesAct) + menu.addSeparator() + menu.addAction(self.searchEnginesAct) ## menu.addSeparator() ## menu.addAction(self.passwordsAct) ## if SSL_AVAILABLE: @@ -1890,8 +1888,8 @@ ## menu.addAction(self.manageQtHelpFiltersAct) ## menu.addAction(self.reindexDocumentationAct) ## menu.addSeparator() -## menu.addAction(self.clearPrivateDataAct) -## menu.addAction(self.clearIconsAct) + menu.addAction(self.clearPrivateDataAct) + menu.addAction(self.clearIconsAct) ## menu = mb.addMenu(self.tr("&Tools")) ## menu.setTearOffEnabled(True) @@ -2030,14 +2028,14 @@ self.__navigationSplitter = QSplitter(gotb) self.__navigationSplitter.addWidget(self.__tabWidget.stackedUrlBar()) -## from .HelpWebSearchWidget import HelpWebSearchWidget -## self.searchEdit = HelpWebSearchWidget(self) -## sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) -## sizePolicy.setHorizontalStretch(2) -## sizePolicy.setVerticalStretch(0) -## self.searchEdit.setSizePolicy(sizePolicy) -## self.searchEdit.search.connect(self.__linkActivated) -## self.__navigationSplitter.addWidget(self.searchEdit) + from .WebBrowserWebSearchWidget import WebBrowserWebSearchWidget + self.searchEdit = WebBrowserWebSearchWidget(self) + sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(2) + sizePolicy.setVerticalStretch(0) + self.searchEdit.setSizePolicy(sizePolicy) + self.searchEdit.search.connect(self.__linkActivated) + self.__navigationSplitter.addWidget(self.searchEdit) gotb.addWidget(self.__navigationSplitter) self.__navigationSplitter.setSizePolicy( @@ -2136,9 +2134,8 @@ @param browser reference to the browser (WebBrowserView) @param title new title (string) """ - pass -## self.historyManager().updateHistoryEntry( -## browser.url().toString(), title) + self.historyManager().updateHistoryEntry( + browser.url().toString(), title) @pyqtSlot() def newTab(self, link=None, requestData=None, addNextTo=None): @@ -2146,8 +2143,7 @@ Public slot called to open a new web browser tab. @param link file to be displayed in the new window (string or QUrl) - @param requestData tuple containing the request data (QNetworkRequest, - QNetworkAccessManager.Operation, QByteArray) + @param requestData page load request data (LoadRequest) @param addNextTo reference to the browser to open the tab after (HelpBrowser) """ @@ -2155,7 +2151,6 @@ self.__tabWidget.newBrowserAfter(addNextTo, link, requestData) else: self.__tabWidget.newBrowser(link, requestData) - # TODO: check the above @pyqtSlot() def newWindow(self, link=None): @@ -2176,7 +2171,7 @@ # TODO: Private Window - # TODO: check if this is still needed + # TODO: check if this is still needed/possible def previewer(self): """ Public method to get a reference to the previewer tab. @@ -2505,8 +2500,8 @@ ## ## self.flashCookieManager().shutdown() ## -## self.searchEdit.openSearchManager().close() -## + self.searchEdit.openSearchManager().close() + ## if WebBrowserWindow.UseQtHelp: ## self.__searchEngine.cancelIndexing() ## self.__searchEngine.cancelSearching() @@ -2514,8 +2509,8 @@ ## if self.__helpInstaller: ## self.__helpInstaller.stop() ## -## self.searchEdit.saveSearches() -## + self.searchEdit.saveSearches() + self.__tabWidget.closeAllBrowsers() state = self.saveState() @@ -2806,9 +2801,8 @@ self.__tabWidget.preferencesChanged() - # TODO: OpenSearch -## self.searchEdit.preferencesChanged() -## + self.searchEdit.preferencesChanged() + # TODO: VirusTotal ## self.__virusTotal.preferencesChanged() ## if not Preferences.getWebBrowser("VirusTotalEnabled") or \ @@ -2923,12 +2917,12 @@ ## cls._cookieJar = CookieJar() ## return cls.networkAccessManager().cookieJar() ## -## def __clearIconsDatabase(self): -## """ -## Private slot to clear the icons databse. -## """ -## QWebSettings.clearIconDatabase() -## + def __clearIconsDatabase(self): + """ + Private slot to clear the icons databse. + """ + WebIconProvider.instance().clear() + @pyqtSlot(QUrl) def __linkActivated(self, url): """ @@ -3285,37 +3279,43 @@ # go forward history.goToItem(history.forwardItems(historyCount)[offset - 1]) -## def __clearPrivateData(self): -## """ -## Private slot to clear the private data. -## """ -## from .HelpClearPrivateDataDialog import HelpClearPrivateDataDialog -## dlg = HelpClearPrivateDataDialog(self) -## if dlg.exec_() == QDialog.Accepted: -## # browsing history, search history, favicons, disk cache, cookies, -## # passwords, web databases, downloads, Flash cookies -## (history, searches, favicons, cache, cookies, -## passwords, databases, downloads, flashCookies, zoomValues, -## historyPeriod) = dlg.getData() -## if history: -## self.historyManager().clear(historyPeriod) -## self.__tabWidget.clearClosedTabsList() -## if searches: -## self.searchEdit.clear() + def __clearPrivateData(self): + """ + Private slot to clear the private data. + """ + from .WebBrowserClearPrivateDataDialog import \ + WebBrowserClearPrivateDataDialog + dlg = WebBrowserClearPrivateDataDialog(self) + if dlg.exec_() == QDialog.Accepted: + # browsing history, search history, favicons, disk cache, cookies, + # passwords, web databases, downloads, Flash cookies + (history, searches, favicons, cache, cookies, + passwords, databases, downloads, flashCookies, zoomValues, + historyPeriod) = dlg.getData() + if history: + self.historyManager().clear(historyPeriod) + self.__tabWidget.clearClosedTabsList() + if searches: + self.searchEdit.clear() + # TODO: Downloads ## if downloads: ## self.downloadManager().cleanup() ## self.downloadManager().hide() -## if favicons: -## self.__clearIconsDatabase() + if favicons: + self.__clearIconsDatabase() + # TODO: Cache Cleaning ## if cache: ## try: ## self.networkAccessManager().cache().clear() ## except AttributeError: ## pass + # TODO: Cookies ## if cookies: ## self.cookieJar().clear() + # TODO: Passwords ## if passwords: ## self.passwordManager().clear() + # TODO: Web Databases ## if databases: ## if hasattr(QWebDatabase, "removeAllDatabases"): ## QWebDatabase.removeAllDatabases() @@ -3323,6 +3323,7 @@ ## for securityOrigin in QWebSecurityOrigin.allOrigins(): ## for database in securityOrigin.databases(): ## QWebDatabase.removeDatabase(database) + # TODO: Flash Cookie Manager ## if flashCookies: ## from .HelpLanguagesDialog import HelpLanguagesDialog ## languages = Preferences.toList( @@ -3336,27 +3337,27 @@ ## "http://www.macromedia.com/support/documentation/" ## "{0}/flashplayer/help/settings_manager07.html".format( ## langCode)) -## if zoomValues: -## ZoomManager.instance().clear() -## -## def __showEnginesConfigurationDialog(self): -## """ -## Private slot to show the search engines configuration dialog. -## """ -## from .OpenSearch.OpenSearchDialog import OpenSearchDialog -## -## dlg = OpenSearchDialog(self) -## dlg.exec_() -## -## def searchEnginesAction(self): -## """ -## Public method to get a reference to the search engines configuration -## action. -## -## @return reference to the search engines configuration action (QAction) -## """ -## return self.searchEnginesAct -## + if zoomValues: + ZoomManager.instance().clear() + + def __showEnginesConfigurationDialog(self): + """ + Private slot to show the search engines configuration dialog. + """ + from .OpenSearch.OpenSearchDialog import OpenSearchDialog + + dlg = OpenSearchDialog(self) + dlg.exec_() + + def searchEnginesAction(self): + """ + Public method to get a reference to the search engines configuration + action. + + @return reference to the search engines configuration action (QAction) + """ + return self.searchEnginesAct + ## def __showPasswordsDialog(self): ## """ ## Private slot to show the passwords management dialog. @@ -3661,14 +3662,14 @@ return self.mainWindow() -## def openSearchManager(self): -## """ -## Public method to get a reference to the opensearch manager object. -## -## @return reference to the opensearch manager object (OpenSearchManager) -## """ -## return self.searchEdit.openSearchManager() -## + def openSearchManager(self): + """ + Public method to get a reference to the opensearch manager object. + + @return reference to the opensearch manager object (OpenSearchManager) + """ + return self.searchEdit.openSearchManager() + def __aboutToShowTextEncodingMenu(self): """ Private slot to populate the text encoding menu.
--- a/eric6.e4p Sat Feb 13 14:17:39 2016 +0100 +++ b/eric6.e4p Sun Feb 14 16:47:40 2016 +0100 @@ -1301,8 +1301,20 @@ <Source>WebBrowser/JavaScript/ExternalJsObject.py</Source> <Source>WebBrowser/JavaScript/__init__.py</Source> <Source>WebBrowser/Network/FollowRedirectReply.py</Source> + <Source>WebBrowser/Network/LoadRequest.py</Source> <Source>WebBrowser/Network/NetworkManager.py</Source> <Source>WebBrowser/Network/__init__.py</Source> + <Source>WebBrowser/OpenSearch/DefaultSearchEngines/DefaultSearchEngines_rc.py</Source> + <Source>WebBrowser/OpenSearch/DefaultSearchEngines/__init__.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchDialog.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchEditDialog.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchEngine.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchEngineAction.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchEngineModel.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchManager.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchReader.py</Source> + <Source>WebBrowser/OpenSearch/OpenSearchWriter.py</Source> + <Source>WebBrowser/OpenSearch/__init__.py</Source> <Source>WebBrowser/SearchWidget.py</Source> <Source>WebBrowser/Tools/Scripts.py</Source> <Source>WebBrowser/Tools/WebBrowserTools.py</Source> @@ -1316,10 +1328,12 @@ <Source>WebBrowser/UrlBar/StackedUrlBar.py</Source> <Source>WebBrowser/UrlBar/UrlBar.py</Source> <Source>WebBrowser/UrlBar/__init__.py</Source> + <Source>WebBrowser/WebBrowserClearPrivateDataDialog.py</Source> <Source>WebBrowser/WebBrowserPage.py</Source> <Source>WebBrowser/WebBrowserTabBar.py</Source> <Source>WebBrowser/WebBrowserTabWidget.py</Source> <Source>WebBrowser/WebBrowserView.py</Source> + <Source>WebBrowser/WebBrowserWebSearchWidget.py</Source> <Source>WebBrowser/WebBrowserWindow.py</Source> <Source>WebBrowser/ZoomManager/ZoomManager.py</Source> <Source>WebBrowser/ZoomManager/ZoomValuesDialog.py</Source> @@ -1729,9 +1743,12 @@ <Form>WebBrowser/Bookmarks/BookmarksDialog.ui</Form> <Form>WebBrowser/Bookmarks/BookmarksImportDialog.ui</Form> <Form>WebBrowser/History/HistoryDialog.ui</Form> + <Form>WebBrowser/OpenSearch/OpenSearchDialog.ui</Form> + <Form>WebBrowser/OpenSearch/OpenSearchEditDialog.ui</Form> <Form>WebBrowser/SearchWidget.ui</Form> <Form>WebBrowser/UrlBar/BookmarkActionSelectionDialog.ui</Form> <Form>WebBrowser/UrlBar/BookmarkInfoDialog.ui</Form> + <Form>WebBrowser/WebBrowserClearPrivateDataDialog.ui</Form> <Form>WebBrowser/ZoomManager/ZoomValuesDialog.ui</Form> </Forms> <Translations> @@ -1765,6 +1782,7 @@ <Resource>Helpviewer/data/javascript.qrc</Resource> <Resource>IconEditor/cursors/cursors.qrc</Resource> <Resource>WebBrowser/Bookmarks/DefaultBookmarks.qrc</Resource> + <Resource>WebBrowser/OpenSearch/DefaultSearchEngines/DefaultSearchEngines.qrc</Resource> <Resource>WebBrowser/data/javascript.qrc</Resource> </Resources> <Interfaces/> @@ -1871,6 +1889,22 @@ <Other>ThirdParty/Send2Trash/LICENSE</Other> <Other>ThirdParty/enum/LICENSE</Other> <Other>WebBrowser/Bookmarks/DefaultBookmarks.xbel</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Amazoncom.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Bing.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/DeEn_Beolingus.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/DuckDuckGo.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Facebook.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Google.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Google_Im_Feeling_Lucky.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/LEO_DeuEng.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/LinuxMagazin.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Reddit.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Wikia.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Wikia_en.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Wikipedia.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Wiktionary.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/Yahoo.xml</Other> + <Other>WebBrowser/OpenSearch/DefaultSearchEngines/YouTube.xml</Other> <Other>WebBrowser/data/javascript/jquery-ui.js</Other> <Other>WebBrowser/data/javascript/jquery.js</Other> <Other>WebBrowser/data/javascript/qwebchannel.js</Other>