123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- ########################################################################
- # Searx-qt - Lightweight desktop application for SearX.
- # Copyright (C) 2020 CYBERDEViL
- #
- # This file is part of Searx-qt.
- #
- # Searx-qt is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # Searx-qt is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
- #
- ########################################################################
- from urllib.parse import urlparse
- from PyQt5.QtWidgets import (
- QWidget,
- QFrame,
- QVBoxLayout,
- QFormLayout,
- QCheckBox,
- QLabel,
- QDoubleSpinBox,
- QLineEdit,
- QComboBox,
- QHBoxLayout,
- QSizePolicy,
- QDialog,
- QSpacerItem
- )
- from PyQt5.QtCore import Qt, pyqtSignal
- from searxqt.widgets.groupBox import CollapsableGroupBox
- from searxqt.widgets.buttons import Button
- HAVE_SOCKS = False
- try:
- import socks
- HAVE_SOCKS = True
- except ImportError:
- print("pysocks not installed! No socks proxy support.")
- class ProxyWidget(QWidget):
- changed = pyqtSignal(str) # self.str()
- def __init__(self, parent=None):
- QWidget.__init__(self, parent=parent)
- l = QVBoxLayout(self)
- hLayout = QHBoxLayout()
- self._proxyType = QComboBox(self)
- self._proxyType.setSizePolicy(QSizePolicy(
- QSizePolicy.Maximum,
- QSizePolicy.Fixed))
- typeList = ['http']
- if HAVE_SOCKS:
- typeList += ['socks4', 'socks5']
- for item in typeList:
- self._proxyType.addItem(item)
- self._proxyStr = QLineEdit(self)
- self._proxyStr.setPlaceholderText("user:pass@host:port")
- hLayout.addWidget(self._proxyType)
- hLayout.addWidget(self._proxyStr)
- l.addLayout(hLayout)
- self._proxyDns = QCheckBox("Proxy DNS", self)
- l.addWidget(self._proxyDns)
- if not HAVE_SOCKS:
- self._proxyDns.setToolTip("Install pysocks for socks support.")
- self._proxyDns.setEnabled(False)
- self._proxyStr.textChanged.connect(self.__changed)
- self._proxyType.currentIndexChanged.connect(self.__typeChanged)
- self._proxyDns.toggled.connect(self.__dnsChanged)
- def __changed(self):
- self.changed.emit(self.str())
- def __typeChanged(self, index):
- """ From proxy type combobox
- """
- if index == 0:
- self._proxyDns.setEnabled(False)
- self._proxyDns.setToolTip("Not available for http proxy.")
- else:
- self._proxyDns.setEnabled(True)
- if self.str(): self.__changed()
- def __dnsChanged(self, state):
- """ From proxy dns checkbox
- """
- if self.str(): self.__changed()
- def reset(self):
- self._proxyDns.setChecked(True)
- self._proxyStr.setText("")
- self._proxyType.setCurrentIndex(0)
- self.__typeChanged(0)
- def setStr(self, _str):
- self.reset()
- seq = _str.split(':')
- if len(seq) > 1:
- index = self._proxyType.findText(seq[0].rstrip('h'))
- if index != -1:
- self._proxyType.setCurrentIndex(index)
- self._proxyStr.setText(_str[len(seq[0]) + 3:])
- if seq[0] not in ['socks5h', 'socks4h']:
- self._proxyDns.setChecked(False)
- def str(self):
- if self._proxyStr.text():
- return "{0}://{1}".format(
- self.protocolStr(),
- self._proxyStr.text())
- return ""
- def protocol(self): return self._proxyType.currentText()
- def protocolStr(self):
- if (self.protocol() in ['socks4', 'socks5']
- and self._proxyDns.isChecked()):
- return "{0}h".format(self.protocol())
- return self.protocol()
- class UrlDialog(QDialog):
- def __init__(self, url='', parent=None):
- QDialog.__init__(self, parent=parent)
- layout = QFormLayout(self)
- label = QLabel("URL:")
- self._urlEdit = QLineEdit(self)
- self._urlEdit.setToolTip(
- "Make sure the URL is valid and has a valid scheme! Valid"
- " schemes are http:// and https://"
- )
- self._urlEdit.setText(url)
- layout.addRow(label, self._urlEdit)
- self._cancelButton = Button("Cancel", self)
- self._saveButton = Button("Save", self)
- layout.addRow(self._cancelButton, self._saveButton)
- self._urlEdit.textChanged.connect(self.__inputChanged)
- self._saveButton.clicked.connect(self.accept)
- self._cancelButton.clicked.connect(self.reject)
- def __inputChanged(self, text):
- if self.isValid():
- self._saveButton.setEnabled(True)
- else:
- self._saveButton.setEnabled(False)
- def isValid(self):
- parsedUrl = urlparse(self._urlEdit.text())
- if parsedUrl.scheme in ['http', 'https'] and parsedUrl.netloc:
- return True
- return False
- @property
- def url(self):
- if self.isValid():
- return self._urlEdit.text()
- return ''
- class RequestsSettings(CollapsableGroupBox):
- def __init__(self, model, parent=None):
- CollapsableGroupBox.__init__(self, txt="Connection", parent=parent)
- self._model = model
- l = QFormLayout(self.contentWidget)
- # Verify checkbox
- self._verifyCheck = QCheckBox(self.contentWidget)
- l.addRow(QLabel("Verify (SSL):"), self._verifyCheck)
- # Timeout double spinbox
- self._timeoutSpin = QDoubleSpinBox(self.contentWidget)
- self._timeoutSpin.setSuffix(" sec")
- self._timeoutSpin.setMinimum(3)
- self._timeoutSpin.setMaximum(300)
- l.addRow(QLabel("Timeout:"), self._timeoutSpin)
- # Proxy group box
- self._proxyBox = CollapsableGroupBox(
- txt="Proxy", parent=self.contentWidget)
- l.addRow(QLabel("Proxy:"), self._proxyBox)
- proxyL = QFormLayout(self._proxyBox.contentWidget)
- self._httpProxy = ProxyWidget(self._proxyBox)
- self._httpsProxy = ProxyWidget(self._proxyBox)
- proxyL.addRow(QLabel("Http:"), self._httpProxy)
- proxyL.addRow(QLabel("Https:"), self._httpsProxy)
- self._changed()
- self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)
- self._verifyCheck.stateChanged.connect(self.__verifyEdited)
- self._httpProxy.changed.connect(self.__proxyEdited)
- self._httpsProxy.changed.connect(self.__proxyEdited)
- def __timeoutEdited(self, value):
- self._model.timeout = value
- def __verifyEdited(self, state):
- self._model.verify = bool(state)
- def __proxyEdited(self, text):
- self._model.proxies = {
- 'http': self._httpProxy.str(), 'https': self._httpsProxy.str()}
- def _changed(self):
- self._verifyCheck.setChecked(self._model.verify)
- self._timeoutSpin.setValue(self._model.timeout)
- self._httpProxy.setStr(self._model.proxies.get('http', 'socks5h://'))
- self._httpsProxy.setStr(self._model.proxies.get('https', 'socks5h://'))
- class Stats2Settings(CollapsableGroupBox):
- def __init__(self, model, parent=None):
- """
- @type model: SearxStats2Model
- """
- CollapsableGroupBox.__init__(self, txt="searx-stats2", parent=parent)
- self._model = model
- layout = QVBoxLayout(self.contentWidget)
- infoLabel = QLabel(
- "The searx-stats2 project lists public searx instances with"
- " statistics. The original instance is running at"
- " https://searx.space/. This is where searx-qt will request"
- " a list with instances when the update button is pressed."
- , self
- )
- infoLabel.setWordWrap(True)
- layout.addWidget(infoLabel)
- hLayout = QHBoxLayout(self.contentWidget)
- label = QLabel("URL:", self)
- label.setSizePolicy(
- QSizePolicy(
- QSizePolicy.Maximum, QSizePolicy.Maximum
- )
- )
- self._urlLabel = QLabel(model.url, self)
- self._urlEditButton = Button("Edit", self)
- self._urlResetButton = Button("Reset", self)
- hLayout.addWidget(label)
- hLayout.addWidget(self._urlLabel)
- hLayout.addWidget(self._urlEditButton)
- hLayout.addWidget(self._urlResetButton)
- layout.addLayout(hLayout)
- self._urlEditButton.clicked.connect(self.__urlEditClicked)
- self._urlResetButton.clicked.connect(self.__urlResetClicked)
- model.changed.connect(self.__modelChanged)
- def __modelChanged(self):
- self._urlLabel.setText(self._model.url)
- def __urlEditClicked(self):
- dialog = UrlDialog(self._model.url)
- if dialog.exec():
- self._model.url = dialog.url
- def __urlResetClicked(self):
- self._model.reset()
- class SettingsWindow(QFrame):
- def __init__(self, model, parent=None):
- """
- @type model: SettingsModel
- """
- QFrame.__init__(self, parent=parent)
- layout = QVBoxLayout(self)
- self.setWindowTitle("Settings")
- self._requestsView = RequestsSettings(model.requests, self)
- layout.addWidget(self._requestsView, 0, Qt.AlignTop)
- self._stats2View = Stats2Settings(model.stats2, self)
- layout.addWidget(self._stats2View, 0, Qt.AlignTop)
- # keep widgets aligned on top
- spacer = QSpacerItem(
- 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
- )
- layout.addItem(spacer)
- self.resize(480, 400)
|