123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- ########################################################################
- # Searx-Qt - Lightweight desktop application for Searx.
- # Copyright (C) 2020-2022 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 uuid import uuid4
- from PyQt5.QtWidgets import (
- QLabel,
- QDialog,
- QHBoxLayout,
- QVBoxLayout,
- QFormLayout,
- QListWidget,
- QComboBox,
- QLineEdit,
- QListWidgetItem,
- QCheckBox,
- QMessageBox
- )
- from PyQt5.QtCore import Qt, QVariant
- from searxqt.widgets.buttons import Button
- from searxqt.models.profiles import ProfileItem
- from searxqt.translations import _
- class AddProfileDialog(QDialog):
- def __init__(self, profiles, parent=None):
- QDialog.__init__(self, parent=parent)
- self.setWindowTitle(_("Add profile"))
- self._profiles = profiles
- self._id = None
- layout = QVBoxLayout(self)
- # Top message
- labelTxt = _("There are two types of profiles:\n"
- " 1. stats2 profile\n"
- " 2. user profile\n"
- "\n"
- "Choose stats2 profile type when you want to get a\n"
- "list of searx-instances from a stats2-instance.\n"
- "For example from the default https://searx.space\n"
- "instance."
- "\n"
- "Choose user profile type when you want to manage\n"
- "your own searx-instance's list.")
- label = QLabel(labelTxt, self)
- layout.addWidget(label)
- # Form layout
- formLayout = QFormLayout()
- layout.addLayout(formLayout)
- # Profile type
- self._typeBox = QComboBox(self)
- formLayout.addRow(QLabel(_("Type") + ":"), self._typeBox)
- # Do these in order to match InstancesModelTypes -1
- self._typeBox.addItem("stats2")
- self._typeBox.addItem("user")
- # Profile name
- self._nameEdit = QLineEdit(self)
- self._nameEdit.setMaxLength(32)
- formLayout.addRow(QLabel(_("Name") + ":"), self._nameEdit)
- # Profile preset
- from searxqt.defaults import Presets
- self._presetCombo = QComboBox(self)
- self._presetCombo.addItem("None")
- for preset in Presets:
- self._presetCombo.addItem(preset)
- del Presets
- formLayout.addRow(QLabel(_("Preset") + ":"), self._presetCombo)
- # Feedback info label
- self._infoLabel = QLabel("", self)
- formLayout.addRow("", self._infoLabel)
- # Add / Cancel button
- buttonLayout = QHBoxLayout()
- layout.addLayout(buttonLayout)
- self._addButton = Button(_("Add"), self)
- self._addButton.setEnabled(False)
- buttonLayout.addWidget(self._addButton, 1, Qt.AlignRight)
- self._cancelButton = Button(_("Cancel"), self)
- buttonLayout.addWidget(self._cancelButton, 0, Qt.AlignRight)
- # Signals
- self._addButton.clicked.connect(self.__addButtonClicked)
- self._cancelButton.clicked.connect(self.reject)
- self._nameEdit.textEdited.connect(self.__nameEdited)
- def id(self): return self._id
- def __getSanitizedName(self):
- return self._nameEdit.text().rstrip().lstrip()
- def __addButtonClicked(self, state):
- self._id = str(uuid4())
- presetKey = self._presetCombo.currentText()
- preset = None if presetKey == "None" else presetKey
- profile = ProfileItem(
- self._id,
- self.__getSanitizedName(),
- # +1 to match InstancesModelTypes
- self._typeBox.currentIndex() + 1,
- preset=preset)
- self._profiles.add(profile)
- self.accept()
- def __doChecks(self):
- # Check if input name already exists.
- # profiles.conf is not re-read! So possible duplicate name if the
- # user where to add the same name at the same time in 2 different
- # instances of searx-qt.
- sanName = self.__getSanitizedName()
- if not sanName:
- self._addButton.setEnabled(False)
- self._infoLabel.setText("")
- elif sanName in self._profiles.names():
- self._addButton.setEnabled(False)
- self._infoLabel.setText(_("Name already exists."))
- else:
- self._addButton.setEnabled(True)
- self._infoLabel.setText("")
- def __nameEdited(self, name):
- self.__doChecks()
- class ProfileChooserDialog(QDialog):
- def __init__(self, profiles, parent=None):
- QDialog.__init__(self, parent=parent)
- self.setWindowTitle(_("Profile select"))
- self._profiles = profiles
- self._items = {}
- self._selectedProfile = ProfileItem()
- self._activeProfiles = []
- # current profile index in the profile list (self._profileListWidget)
- self._currentIndex = -1
- # keep track of removed profile id's so their files can be
- # deleted on save.
- self._removedIds = []
- # keep track of new profile id's, these profiles don't have a
- # .conf file stored yet. When one of these id's get deleted we
- # don't have to remove the file because there is none.
- self._newIds = []
- layout = QVBoxLayout(self)
- # Top message
- labelTxt = _("Please select a profile")
- label = QLabel(labelTxt, self)
- layout.addWidget(label)
- hLayout = QHBoxLayout()
- layout.addLayout(hLayout)
- # Actions
- actionsLayout = QVBoxLayout()
- hLayout.addLayout(actionsLayout)
- self._addButton = Button(_("Add"), self)
- actionsLayout.addWidget(self._addButton, 0, Qt.AlignTop)
- self._delButton = Button(_("Delete"), self)
- self._delButton.setEnabled(False)
- actionsLayout.addWidget(self._delButton, 1, Qt.AlignTop)
- self._resetButton = Button(_("Force\nrelease\nprofiles"), self)
- self._resetButton.setEnabled(False)
- actionsLayout.addWidget(self._resetButton, 0, Qt.AlignBottom)
- # Profile list
- self._profileListWidget = QListWidget(self)
- hLayout.addWidget(self._profileListWidget)
- # Bottom stuff
- bottomLayout = QHBoxLayout()
- layout.addLayout(bottomLayout)
- self._defaultCheckBox = QCheckBox(_("Use as default"), self)
- bottomLayout.addWidget(self._defaultCheckBox, 1, Qt.AlignRight)
- exitButtonTxt = ""
- if profiles.current().id:
- exitButtonTxt = _("Cancel")
- else:
- exitButtonTxt = _("Exit")
- self._exitButton = Button(exitButtonTxt, self)
- bottomLayout.addWidget(self._exitButton, 0, Qt.AlignRight)
- self._selectButton = Button(_("Load profile"), self)
- self._selectButton.setEnabled(False)
- bottomLayout.addWidget(self._selectButton, 0, Qt.AlignRight)
- # Signals
- self._addButton.clicked.connect(self.__addButtonClicked)
- self._delButton.clicked.connect(self.__delButtonClicked)
- self._exitButton.clicked.connect(self.reject)
- self._selectButton.clicked.connect(self.__selectButtonClicked)
- self._profileListWidget.currentItemChanged.connect(self.__itemChanged)
- # Only show the 'force release profiles' button when there is
- # currently no profile set for this searx-qt instance.
- if not profiles.current().id:
- self._resetButton.clicked.connect(self.__resetButtonClicked)
- else:
- self._resetButton.hide()
- if not len(profiles):
- self.__addDialog()
- else:
- self.refresh()
- def selectedProfile(self): return self._selectedProfile
- def removedProfiles(self): return self._removedIds
- def promtProfileLoadFailed(self, msg):
- """
- @param msg: Message with what went wrong.
- @type msg: str
- """
- QMessageBox.warning(
- self,
- _("Failed to load profile."),
- msg
- )
- def __selectButtonClicked(self):
- item = self._profileListWidget.currentItem()
- selectedProfile = item.data(Qt.UserRole)
- profiles = self._profiles
- # Check if profile didn't get active in meantime.
- if profiles.profileActive(selectedProfile):
- self.promtProfileLoadFailed(
- _("Profile already in use."),
- )
- self.refresh()
- return
- # Check if profile still exists
- if (selectedProfile.id not in self._newIds and
- not profiles.profileExists(selectedProfile)):
- self.promtProfileLoadFailed(
- _("Profile doesn't exist anymore.")
- )
- self.refresh()
- return
- # Set default profile
- if self._defaultCheckBox.isChecked():
- self._profiles.setDefault(selectedProfile)
- else:
- self._profiles.setDefault(None)
- self._selectedProfile = selectedProfile
- self.accept()
- def __addDialog(self):
- dialog = AddProfileDialog(self._profiles)
- if dialog.exec():
- self._newIds.append(dialog.id())
- self.refresh()
- def __addButtonClicked(self):
- self.__addDialog()
- def __delButtonClicked(self):
- item = self._profileListWidget.currentItem()
- profile = item.data(Qt.UserRole)
- profiles = self._profiles
- # Confirmation
- confirmDialog = QMessageBox()
- confirmDialog.setWindowTitle(
- _("Delete profile {profileName}")
- .format(profileName=profile.name)
- )
- confirmDialog.setText(
- _("Are you sure you want to delete profile '{profileName}'?")
- .format(profileName=profile.name)
- )
- confirmDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
- confirmDialog.button(QMessageBox.Yes).setText(_("Yes"))
- confirmDialog.button(QMessageBox.No).setText(_("No"))
- if confirmDialog.exec() != QMessageBox.Yes:
- return
- if profiles.profileActive(profile):
- QMessageBox.warning(
- self,
- _("Failed to delete profile."),
- _("Cannot remove active profiles!")
- )
- self.refresh()
- return
- if profiles.default().id == profile.id:
- # Default got removed.
- profiles.setDefault(ProfileItem())
- # Remove profile from profiles.conf
- profiles.remove(profile)
- if profile.id in self._newIds:
- self._newIds.remove(profile.id)
- else:
- # Register the profile id as removed.
- self._removedIds.append(profile.id)
- self._profileListWidget.currentItemChanged.disconnect(
- self.__itemChanged
- )
- self.refresh()
- self._profileListWidget.currentItemChanged.connect(
- self.__itemChanged
- )
- def __resetButtonClicked(self):
- # Confirmation
- confirmDialog = QMessageBox()
- confirmDialog.setIcon(QMessageBox.Warning)
- confirmDialog.setWindowTitle(_("Force release active profiles"))
- confirmDialog.setText(
- _("This will force release all active profiles.\n"
- "\n"
- "This should only be run if Searx-Qt didn't exit properly\n"
- "and you can't access your profile anymore.\n"
- "\n"
- "Make sure to close all other running Searx-Qt instances\n"
- "before continuing!\n"
- "\n"
- "Do you want to continue?")
- )
- confirmDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
- confirmDialog.button(QMessageBox.Yes).setText(_("Yes"))
- confirmDialog.button(QMessageBox.No).setText(_("No"))
- if confirmDialog.exec() != QMessageBox.Yes:
- return
- self._profiles.releaseAll()
- self.refresh()
- def __itemChanged(self, item, previousItem):
- if item:
- profile = item.data(Qt.UserRole)
- if (self._profiles.current().id == profile.id or
- profile.id in self._activeProfiles):
- # Can't remove current profile
- self._delButton.setEnabled(False)
- self._selectButton.setEnabled(False)
- else:
- self._delButton.setEnabled(True)
- self._selectButton.setEnabled(True)
- else:
- self._delButton.setEnabled(False)
- self._selectButton.setEnabled(False)
- self._currentIndex = self._profileListWidget.currentRow()
- def refresh(self):
- self._profileListWidget.clear()
- self._items.clear()
- self._resetButton.setEnabled(False)
- profiles = self._profiles
- settings = profiles.settings()
- self._activeProfiles = profiles.getActiveProfiles(settings)
- for profile in profiles:
- itemTxt = f"{profile.type} - {profile.name}"
- item = QListWidgetItem()
- currentProfile = self._profiles.current()
- if currentProfile.id == profile.id:
- itemTxt = f"* {itemTxt}"
- item.setText(itemTxt)
- if profile.id in self._activeProfiles:
- item.setFlags(item.flags() & Qt.ItemIsSelectable)
- self._resetButton.setEnabled(True)
- item.setData(Qt.UserRole, QVariant(profile))
- self._items.update({profile.id: item})
- self._profileListWidget.addItem(item)
- # Restore index
- if self._currentIndex >= 0:
- self._currentIndex = min(
- self._currentIndex, self._profileListWidget.count() - 1
- )
- self._profileListWidget.setCurrentRow(self._currentIndex)
|