settings.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. ########################################################################
  2. # Searx-Qt - Lightweight desktop application for Searx.
  3. # Copyright (C) 2020-2022 CYBERDEViL
  4. #
  5. # This file is part of Searx-Qt.
  6. #
  7. # Searx-Qt is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Searx-Qt is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. ########################################################################
  21. from PyQt5.QtWidgets import (
  22. QWidget,
  23. QFrame,
  24. QVBoxLayout,
  25. QFormLayout,
  26. QGridLayout,
  27. QCheckBox,
  28. QLabel,
  29. QSpinBox,
  30. QDoubleSpinBox,
  31. QLineEdit,
  32. QComboBox,
  33. QHBoxLayout,
  34. QSizePolicy,
  35. QTabWidget,
  36. QPlainTextEdit,
  37. QSpacerItem
  38. )
  39. from PyQt5.QtCore import Qt, pyqtSignal, QVariant
  40. from searxqt.views.guard import GuardSettings
  41. from searxqt.widgets.buttons import Button
  42. from searxqt.widgets.dialogs import UrlDialog
  43. from searxqt.translations import _
  44. from searxqt.themes import Themes
  45. from searxqt.core import log
  46. from searxqt.core.customAnchorCmd import AnchorCMD
  47. from searxqt.core.requests import HAVE_SOCKS, ProxyProtocol
  48. class ProxyWidget(QWidget):
  49. """
  50. @param model:
  51. @type model: searxqt.models.RequestSettingsModel
  52. """
  53. def __init__(self, model, parent=None):
  54. QWidget.__init__(self, parent=parent)
  55. self.__model = model
  56. layout = QFormLayout(self)
  57. self._proxyEnabledCheck = QCheckBox(self)
  58. self._proxyDNSCheck = QCheckBox(self)
  59. self._proxyType = QComboBox(self)
  60. self._proxyType.addItem("http")
  61. if HAVE_SOCKS:
  62. self._proxyType.addItem("socks4")
  63. self._proxyType.addItem("socks5")
  64. self._proxyStr = QLineEdit(self)
  65. self._proxyStr.setPlaceholderText(_("user:pass@host:port"))
  66. layout.addRow(QLabel(_("Enabled")), self._proxyEnabledCheck)
  67. layout.addRow(QLabel(_("Proxy DNS")), self._proxyDNSCheck)
  68. layout.addRow(QLabel(_("Protocol")), self._proxyType)
  69. layout.addRow(QLabel(_("Address")), self._proxyStr)
  70. # Set values
  71. self._setValuesFromModel(model)
  72. # Connections
  73. self._proxyEnabledCheck.toggled.connect(self.__enabledToggled)
  74. self._proxyDNSCheck.toggled.connect(self.__dnsToggled)
  75. self._proxyType.currentIndexChanged.connect(self.__protocolChanged)
  76. self._proxyStr.textEdited.connect(self.__hostTextEdited)
  77. def _setValuesFromModel(self, model):
  78. self._proxyEnabledCheck.setChecked(model.proxyEnabled)
  79. self.enableWidgets(model.proxyEnabled) # enable/disable widgets
  80. self._proxyDNSCheck.setChecked(model.proxyDNS)
  81. if model.proxyProtocol == ProxyProtocol.HTTP:
  82. self._proxyType.setCurrentIndex(0)
  83. elif HAVE_SOCKS:
  84. if model.proxyProtocol == ProxyProtocol.SOCKS4:
  85. self._proxyType.setCurrentIndex(1)
  86. elif model.proxyProtocol == ProxyProtocol.SOCKS5:
  87. self._proxyType.setCurrentIndex(2)
  88. self._proxyStr.setText(model.proxyHost)
  89. def __enabledToggled(self, state):
  90. self.__model.proxyEnabled = bool(state)
  91. self.enableWidgets(state)
  92. def enableWidgets(self, state):
  93. self._proxyDNSCheck.setEnabled(state)
  94. self._proxyType.setEnabled(state)
  95. self._proxyStr.setEnabled(state)
  96. def __dnsToggled(self, state):
  97. self.__model.proxyDNS = bool(state)
  98. def __protocolChanged(self, index):
  99. if index == 0:
  100. self.__model.proxyProtocol = ProxyProtocol.HTTP
  101. self._proxyDNSCheck.setEnabled(False)
  102. self._proxyDNSCheck.setToolTip(_("Not available for http proxy."))
  103. return
  104. elif index == 1:
  105. self.__model.proxyProtocol = ProxyProtocol.SOCKS4
  106. else: # index == 2
  107. self.__model.proxyProtocol = ProxyProtocol.SOCKS5
  108. self._proxyDNSCheck.setEnabled(True)
  109. def __hostTextEdited(self, newText):
  110. # TODO verify format
  111. self.__model.proxyHost = newText
  112. class TextEdit(QFrame):
  113. def __init__(self, model, parent=None):
  114. QFrame.__init__(self, parent)
  115. self._model = model
  116. layout = QVBoxLayout(self)
  117. buttonLayout = QHBoxLayout()
  118. self._textEdit = QPlainTextEdit(self)
  119. self._textEdit.setReadOnly(True)
  120. self._textEdit.setLineWrapMode(QPlainTextEdit.NoWrap)
  121. self._editButton = Button(_("Edit"), self)
  122. self._editButton.setCheckable(True)
  123. self._editButton.setChecked(False)
  124. self._cancelButton = Button(_("Cancel"), self)
  125. self._cancelButton.setCheckable(False)
  126. self._cancelButton.hide()
  127. buttonLayout.addWidget(self._cancelButton, 0, Qt.AlignLeft)
  128. buttonLayout.addWidget(self._editButton, 0, Qt.AlignLeft)
  129. buttonLayout.addStretch(1)
  130. layout.addWidget(self._textEdit)
  131. layout.addLayout(buttonLayout)
  132. self._editButton.toggled.connect(self.__toggleEdit)
  133. self._cancelButton.clicked.connect(self.__cancelEdit)
  134. def __toggleEdit(self, state):
  135. if state:
  136. self._editButton.setText(_("Save"))
  137. self._textEdit.setReadOnly(False)
  138. self._cancelButton.show()
  139. else:
  140. self._editButton.setText(_("Edit"))
  141. self._textEdit.setReadOnly(True)
  142. self._cancelButton.hide()
  143. self.updateToModel()
  144. def __cancelEdit(self, state):
  145. self._editButton.setText(_("Edit"))
  146. self._cancelButton.hide()
  147. self._textEdit.setReadOnly(True)
  148. self._editButton.setChecked(False)
  149. self.updateFromModel()
  150. def updateFromModel(self):
  151. pass # reimplement this
  152. def updateToModel(self):
  153. pass # reimplement this
  154. class RequestsHeaderEdit(TextEdit):
  155. def __init__(self, model, parent=None):
  156. TextEdit.__init__(self, model, parent)
  157. warningLabel = QLabel(_("WARNING! Edit below only when you know what "
  158. "you are doing!"), self)
  159. warningLabel.setWordWrap(True)
  160. self.layout().insertWidget(0, warningLabel)
  161. self.updateFromModel()
  162. def updateFromModel(self):
  163. text = ""
  164. for key, value in self._model.extraHeaders.items():
  165. text += f"\"{key}\" \"{value}\"\n"
  166. self._textEdit.setPlainText(text)
  167. def updateToModel(self):
  168. self._model.extraHeaders.clear()
  169. for line in self._textEdit.toPlainText().split("\n"):
  170. if " " not in line:
  171. continue
  172. key, value = line.split(" ", 1)
  173. value = value.lstrip().rstrip()
  174. if not key.startswith("\"") or not key.endswith("\""):
  175. # TODO malformed, inform the user
  176. continue
  177. if not value.startswith("\"") or not value.endswith("\""):
  178. # TODO malformed, inform the user
  179. continue
  180. key = key[1:len(key)-1]
  181. value = value[1:len(value)-1]
  182. if key.lower() == "user-agent":
  183. # TODO inform the user
  184. continue
  185. if "\"" in key:
  186. # TODO malformed, inform the user
  187. continue
  188. if "\"" in value:
  189. # TODO malformed, inform the user
  190. continue
  191. self._model.extraHeaders.update({key: value})
  192. self.updateFromModel()
  193. class UserAgentsEdit(TextEdit):
  194. def __init__(self, model, parent=None):
  195. TextEdit.__init__(self, model, parent)
  196. self._textEdit.setToolTip(
  197. """- One user-agent string per line.
  198. - Default user-agent string is the first (top) line.
  199. - Empty lines will be removed.
  200. - Leave empty to not send any user-agent string."""
  201. )
  202. self._randomUserAgent = QCheckBox(_("Random"), self)
  203. self._randomUserAgent.setToolTip(
  204. """When checked it will pick a random
  205. user-agent from the list for each request."""
  206. )
  207. self.layout().addWidget(self._randomUserAgent)
  208. self.updateFromModel()
  209. self._randomUserAgent.stateChanged.connect(self._randomUserAgentEdited)
  210. def _randomUserAgentEdited(self, state):
  211. self._model.randomUserAgent = bool(state)
  212. def updateToModel(self):
  213. text = self._textEdit.toPlainText()
  214. self._model.useragents = [s for s in text.split('\n') if s]
  215. self.updateFromModel()
  216. def updateFromModel(self):
  217. text = ""
  218. for userAgentStr in self._model.useragents:
  219. if not text:
  220. text = userAgentStr
  221. else:
  222. text += f"\n{userAgentStr}"
  223. self._textEdit.setPlainText(text)
  224. class RequestsSettings(QWidget):
  225. def __init__(self, model, parent=None):
  226. """
  227. @param model:
  228. @type model: searxqt.models.RequestSettingsModel
  229. """
  230. QWidget.__init__(self, parent=parent)
  231. self._model = model
  232. layout = QFormLayout(self)
  233. # Verify checkbox
  234. self._verifyCheck = QCheckBox(self)
  235. layout.addRow(QLabel(_("Verify") + " (SSL):"), self._verifyCheck)
  236. # Timeout double spinbox
  237. self._timeoutSpin = QDoubleSpinBox(self)
  238. self._timeoutSpin.setSuffix(" sec")
  239. self._timeoutSpin.setMinimum(3)
  240. self._timeoutSpin.setMaximum(300)
  241. layout.addRow(QLabel(_("Timeout") + ":"), self._timeoutSpin)
  242. # Limits: Max receive in KiB
  243. self._recvLimitSpin = QSpinBox(self)
  244. self._recvLimitSpin.setSuffix(" KiB")
  245. self._recvLimitSpin.setMinimum(1) # Min 1 KiB
  246. # Max 100 MiB (idk what json data or thumb is greater then this ..)
  247. self._recvLimitSpin.setMaximum(1024 * 100)
  248. layout.addRow(QLabel(_("Receive limit") + ":"), self._recvLimitSpin)
  249. # Chunk size in KiB
  250. self._chunkSizeSpin = QSpinBox(self)
  251. self._chunkSizeSpin.setSuffix(" KiB")
  252. self._chunkSizeSpin.setMinimum(1) # Min 1 KiB
  253. self._chunkSizeSpin.setMaximum(1024 * 50) # Max 50 MiB
  254. layout.addRow(QLabel(_("Chunk limit") + ":"), self._chunkSizeSpin)
  255. # Proxy
  256. self._proxyWidget = ProxyWidget(self._model, self)
  257. layout.addRow(QLabel(_("Proxy") + ":"), self._proxyWidget)
  258. # Headers
  259. # User-agent
  260. self._useragents = UserAgentsEdit(model, self)
  261. layout.addRow(QLabel(_("User-Agents") + ":"), self._useragents)
  262. # Additional headers
  263. self._extraHeaders = RequestsHeaderEdit(model, self)
  264. layout.addRow(QLabel(_("Extra headers") + ":"), self._extraHeaders)
  265. # Init values for view
  266. self._changed()
  267. # Connections
  268. self._verifyCheck.stateChanged.connect(self.__verifyEdited)
  269. self._timeoutSpin.valueChanged.connect(self.__timeoutEdited)
  270. self._recvLimitSpin.valueChanged.connect(self.__recvLimitEdited)
  271. self._chunkSizeSpin.valueChanged.connect(self.__chunkSizeEdited)
  272. def __timeoutEdited(self, value):
  273. self._model.timeout = value
  274. def __verifyEdited(self, state):
  275. self._model.verifySSL = bool(state)
  276. def __recvLimitEdited(self, value):
  277. self._model.maxSize = int(value * 1024)
  278. def __chunkSizeEdited(self, value):
  279. self._model.chunkSize = int(value * 1024)
  280. def _changed(self):
  281. self._verifyCheck.setChecked(self._model.verifySSL)
  282. self._timeoutSpin.setValue(self._model.timeout)
  283. self._recvLimitSpin.setValue(int(self._model.maxSize / 1024))
  284. self._chunkSizeSpin.setValue(int(self._model.chunkSize / 1024))
  285. class SearxngSettings(QWidget):
  286. def __init__(self, model, parent=None):
  287. """
  288. model: SearchModel
  289. """
  290. QWidget.__init__(self, parent=parent)
  291. layout = QGridLayout(self)
  292. self.__searchModel = model
  293. self._useHtmlParser = QCheckBox(self)
  294. self._useHtmlParser.setChecked(model.parseHtml)
  295. self._safeSearch = QCheckBox(self)
  296. self._safeSearch.setChecked(model.safeSearch)
  297. infoLabel = QLabel(
  298. _("Since many SearXNG instances block API requests "
  299. "we now have a option to not use it and parse HTML "
  300. "response instead. Check \"Parse HTML\" below for "
  301. "that, when left unchecked it will use the API "
  302. "instead."), self)
  303. infoLabel.setWordWrap(True)
  304. layout.addWidget(infoLabel, 0, 0, 1, 2, Qt.AlignTop)
  305. layout.addWidget(QLabel(_("Parse HTML"), self), 1, 0,
  306. Qt.AlignLeft|Qt.AlignTop)
  307. layout.addWidget(self._useHtmlParser, 1, 1,
  308. Qt.AlignLeft|Qt.AlignTop)
  309. infoLabel = QLabel(_("Enable \"Safe search\" for search engines that "
  310. "support it."), self)
  311. infoLabel.setWordWrap(True)
  312. layout.addWidget(infoLabel, 2, 0, 1, 2, Qt.AlignTop)
  313. layout.addWidget(QLabel(_("Safe Search"), self), 3, 0,
  314. Qt.AlignLeft|Qt.AlignTop)
  315. layout.addWidget(self._safeSearch, 3, 1, Qt.AlignLeft|Qt.AlignTop)
  316. layout.setRowStretch(3, 1)
  317. layout.setColumnStretch(1, 1)
  318. self._useHtmlParser.stateChanged.connect(self.__useHtmlParserChanged)
  319. self._safeSearch.stateChanged.connect(self.__safeSearchChanged)
  320. def __useHtmlParserChanged(self, state):
  321. self.__searchModel.parseHtml = bool(state)
  322. def __safeSearchChanged(self, state):
  323. self.__searchModel.safeSearch = bool(state)
  324. class Stats2Settings(QWidget): # TODO rename to SearxSpaceSettings and rename stats2 to searxspace or something..
  325. def __init__(self, model, parent=None):
  326. """
  327. @type model: SearxStats2Model
  328. """
  329. QWidget.__init__(self, parent=parent)
  330. self._model = model
  331. layout = QVBoxLayout(self)
  332. infoLabel = QLabel(_(
  333. "The Searx-Stats2 project lists public Searx instances with"
  334. " statistics. The original instance is running at"
  335. " https://searx.space/. This is where Searx-Qt will request"
  336. " a list with instances when the update button is pressed."),
  337. self
  338. )
  339. infoLabel.setWordWrap(True)
  340. layout.addWidget(infoLabel, 0, Qt.AlignTop)
  341. hLayout = QHBoxLayout()
  342. label = QLabel("URL:", self)
  343. label.setSizePolicy(
  344. QSizePolicy(
  345. QSizePolicy.Maximum, QSizePolicy.Maximum
  346. )
  347. )
  348. self._urlLabel = QLabel(model.url, self)
  349. self._urlEditButton = Button(_("Edit"), self)
  350. self._urlResetButton = Button(_("Reset"), self)
  351. hLayout.addWidget(label, 0, Qt.AlignTop)
  352. hLayout.addWidget(self._urlLabel, 0, Qt.AlignTop)
  353. hLayout.addWidget(self._urlEditButton, 0, Qt.AlignTop)
  354. hLayout.addWidget(self._urlResetButton, 0, Qt.AlignTop)
  355. spacer = QSpacerItem(
  356. 20, 40, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding
  357. )
  358. layout.addLayout(hLayout)
  359. layout.addItem(spacer)
  360. self._urlEditButton.clicked.connect(self.__urlEditClicked)
  361. self._urlResetButton.clicked.connect(self.__urlResetClicked)
  362. model.changed.connect(self.__modelChanged)
  363. def __modelChanged(self):
  364. self._urlLabel.setText(self._model.url)
  365. def __urlEditClicked(self):
  366. dialog = UrlDialog(self._model.url)
  367. if dialog.exec():
  368. self._model.url = dialog.url
  369. def __urlResetClicked(self):
  370. self._model.reset()
  371. class LogLevelSettings(QWidget):
  372. def __init__(self, parent=None):
  373. QWidget.__init__(self, parent=parent)
  374. layout = QVBoxLayout(self)
  375. label = QLabel(_("<h2>CLI output level</h2>"), self)
  376. self.__cbInfo = QCheckBox(_("Info"), self)
  377. self.__cbWarning = QCheckBox(_("Warning"), self)
  378. self.__cbDebug = QCheckBox(_("Debug"), self)
  379. self.__cbError = QCheckBox(_("Error"), self)
  380. if log.LogLevel & log.LogLevels.INFO:
  381. self.__cbInfo.setChecked(True)
  382. if log.LogLevel & log.LogLevels.WARNING:
  383. self.__cbWarning.setChecked(True)
  384. if log.LogLevel & log.LogLevels.DEBUG:
  385. self.__cbDebug.setChecked(True)
  386. if log.LogLevel & log.LogLevels.ERROR:
  387. self.__cbError.setChecked(True)
  388. layout.addWidget(label)
  389. if log.DebugMode == True:
  390. label = QLabel(
  391. _("Debug mode enabled via environment variable"
  392. " 'SEARXQT_DEBUG'. The settings below are ignored,"
  393. " unset 'SEARXQT_DEBUG' and restart Searx-Qt to disable"
  394. " debug mode."),
  395. parent=self
  396. )
  397. label.setWordWrap(True)
  398. layout.addWidget(label)
  399. layout.addWidget(self.__cbInfo)
  400. layout.addWidget(self.__cbWarning)
  401. layout.addWidget(self.__cbDebug)
  402. layout.addWidget(self.__cbError)
  403. self.__cbInfo.stateChanged.connect(self.__stateChangedInfo)
  404. self.__cbWarning.stateChanged.connect(self.__stateChangedWarning)
  405. self.__cbDebug.stateChanged.connect(self.__stateChangedDebug)
  406. self.__cbError.stateChanged.connect(self.__stateChangedError)
  407. def __stateChanged(self, logLevel, state):
  408. if state:
  409. log.LogLevel |= logLevel
  410. else:
  411. log.LogLevel &= ~logLevel
  412. def __stateChangedInfo(self, state):
  413. self.__stateChanged(log.LogLevels.INFO, state)
  414. def __stateChangedWarning(self, state):
  415. self.__stateChanged(log.LogLevels.WARNING, state)
  416. def __stateChangedDebug(self, state):
  417. self.__stateChanged(log.LogLevels.DEBUG, state)
  418. def __stateChangedError(self, state):
  419. self.__stateChanged(log.LogLevels.ERROR, state)
  420. class CustomAnchorCmdSettings(QFrame):
  421. def __init__(self, parent=None):
  422. QWidget.__init__(self, parent=parent)
  423. layout = QGridLayout(self)
  424. # Title
  425. layout.addWidget(
  426. QLabel(f"<h2>{_('Custom anchor commands')}</h2>", self),
  427. 0, 0, 1, 2, Qt.AlignTop | Qt.AlignLeft)
  428. # Info
  429. infoText = _("When the custom command for a scheme is not enabled, "
  430. "QDesktopServices will do its thing. '%url' will be "
  431. "replaced with the url.")
  432. label = QLabel(infoText, self)
  433. label.setWordWrap(True)
  434. layout.addWidget(label , 1, 0, 1, 2)
  435. # Table headers
  436. layout.addWidget(QLabel(_("<b>Enabled</b>"), self), 2, 0, 1, 1)
  437. layout.addWidget(QLabel(_("<b>Command</b>"), self), 2, 1, 1, 1)
  438. self.__checkHttp = QCheckBox("HTTP/HTTPS", self)
  439. self.__checkFtp = QCheckBox("FTP", self)
  440. self.__checkMagnet = QCheckBox("Magnet", self)
  441. self.__editHttp = QLineEdit(self)
  442. self.__editFtp = QLineEdit(self)
  443. self.__editMagnet = QLineEdit(self)
  444. self.__editHttp.setPlaceholderText("iceweasel %url")
  445. self.__editFtp.setPlaceholderText("iceweasel %url")
  446. self.__editMagnet.setPlaceholderText("deluge %url")
  447. self.__editHttp.setEnabled(False)
  448. self.__editFtp.setEnabled(False)
  449. self.__editMagnet.setEnabled(False)
  450. layout.addWidget(self.__checkHttp , 3, 0, 1, 1)
  451. layout.addWidget(self.__editHttp , 3, 1, 1, 1)
  452. layout.addWidget(self.__checkFtp , 4, 0, 1, 1)
  453. layout.addWidget(self.__editFtp , 4, 1, 1, 1)
  454. layout.addWidget(self.__checkMagnet , 5, 0, 1, 1)
  455. layout.addWidget(self.__editMagnet , 5, 1, 1, 1)
  456. layout.setColumnStretch(0, 0)
  457. layout.setColumnStretch(1, 1)
  458. # Connections
  459. self.__checkHttp.stateChanged.connect(self.__checkChangedHttp)
  460. self.__checkFtp.stateChanged.connect(self.__checkChangedFtp)
  461. self.__checkMagnet.stateChanged.connect(self.__checkChangedMagnet)
  462. self.__editHttp.textEdited.connect(self.__cmdEditedHttp)
  463. self.__editFtp.textEdited.connect(self.__cmdEditedFtp)
  464. self.__editMagnet.textEdited.connect(self.__cmdEditedMagnet)
  465. # Set values (after connections so stuff gets triggered)
  466. self.__checkHttp.setChecked(AnchorCMD.http.enabled)
  467. self.__checkFtp.setChecked(AnchorCMD.ftp.enabled)
  468. self.__checkMagnet.setChecked(AnchorCMD.magnet.enabled)
  469. self.__editHttp.setText(AnchorCMD.http.cmd)
  470. self.__editFtp.setText(AnchorCMD.ftp.cmd)
  471. self.__editMagnet.setText(AnchorCMD.magnet.cmd)
  472. def __checkChangedHttp(self, state):
  473. state = bool(state)
  474. AnchorCMD.http.enabled = state
  475. self.__editHttp.setEnabled(state)
  476. def __checkChangedFtp(self, state):
  477. state = bool(state)
  478. AnchorCMD.ftp.enabled = state
  479. self.__editFtp.setEnabled(state)
  480. def __checkChangedMagnet(self, state):
  481. state = bool(state)
  482. AnchorCMD.magnet.enabled = state
  483. self.__editMagnet.setEnabled(state)
  484. def __cmdEditedHttp(self, newCmd):
  485. AnchorCMD.http.cmd = newCmd
  486. def __cmdEditedFtp(self, newCmd):
  487. AnchorCMD.ftp.cmd = newCmd
  488. def __cmdEditedMagnet(self, newCmd):
  489. AnchorCMD.magnet.cmd = newCmd
  490. class GeneralSettings(QWidget):
  491. def __init__(self, parent=None):
  492. QWidget.__init__(self, parent=parent)
  493. layout = QVBoxLayout(self)
  494. # Theme
  495. label = QLabel(f"<h2>{_('Theme')}</h2>", self)
  496. layout.addWidget(label, 0, Qt.AlignTop)
  497. formLayout = QFormLayout()
  498. layout.addLayout(formLayout)
  499. self.__themesCombo = QComboBox(self)
  500. currentTheme = Themes.currentTheme
  501. indexOfCurrentTheme = 0
  502. index = 1
  503. self.__themesCombo.addItem("None", QVariant(None))
  504. for theme in Themes.themes:
  505. data = QVariant(theme)
  506. self.__themesCombo.addItem(theme.name, data)
  507. if theme.key == currentTheme:
  508. indexOfCurrentTheme = index
  509. index += 1
  510. self.__themesCombo.setCurrentIndex(indexOfCurrentTheme)
  511. formLayout.addRow(
  512. QLabel(_("Theme:"), self),
  513. self.__themesCombo
  514. )
  515. self.__stylesCombo = QComboBox(self)
  516. currentStyle = Themes.currentStyle
  517. indexOfCurrentStyle = 0
  518. index = 0
  519. for style in Themes.styles:
  520. self.__stylesCombo.addItem(style, QVariant(style))
  521. if style == currentStyle:
  522. indexOfCurrentStyle = index
  523. index += 1
  524. self.__stylesCombo.setCurrentIndex(indexOfCurrentStyle)
  525. formLayout.addRow(
  526. QLabel(_("Base style:"), self),
  527. self.__stylesCombo
  528. )
  529. applyButton = Button("Apply", self)
  530. applyButton.clicked.connect(self.__applyTheme)
  531. layout.addWidget(applyButton, 0, Qt.AlignTop)
  532. # Log level
  533. logLevelSettings = LogLevelSettings(self)
  534. layout.addWidget(logLevelSettings, 0, Qt.AlignTop)
  535. # Custom anchor commands
  536. customAnchorCmds = CustomAnchorCmdSettings(self)
  537. layout.addWidget(customAnchorCmds, 1, Qt.AlignTop)
  538. def __applyTheme(self):
  539. index = self.__stylesCombo.currentIndex()
  540. style = self.__stylesCombo.itemData(index, Qt.UserRole)
  541. Themes.setStyle(style)
  542. index = self.__themesCombo.currentIndex()
  543. theme = self.__themesCombo.itemData(index, Qt.UserRole)
  544. Themes.setTheme(theme.key if theme is not None else "")
  545. Themes.repolishAllWidgets()
  546. class SettingsWindow(QTabWidget):
  547. closed = pyqtSignal()
  548. def __init__(self, model, searchModel, guard, parent=None):
  549. """
  550. @type model: SettingsModel
  551. """
  552. QTabWidget.__init__(self, parent=parent)
  553. self.setWindowTitle(_("Settings"))
  554. # General settings
  555. self._generalView = GeneralSettings(self)
  556. self.addTab(self._generalView, _("General"))
  557. # Requests settings
  558. self._requestsView = RequestsSettings(model.requests, self)
  559. self.addTab(self._requestsView, _("Connection"))
  560. # SearXNG settings
  561. self._searxView = SearxngSettings(searchModel, self)
  562. self.addTab(self._searxView, "SearXNG")
  563. # Stats2 settings
  564. if model.stats2:
  565. self._stats2View = Stats2Settings(model.stats2, self)
  566. self.addTab(self._stats2View, "Searx-Stats2")
  567. # Guard settings
  568. self._guardView = GuardSettings(guard, self)
  569. self.addTab(self._guardView, _("Guard"))
  570. def closeEvent(self, event):
  571. QTabWidget.closeEvent(self, event)
  572. self.closed.emit()