-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduced password input widget (#1662)
Move existing code for password input widgets into common classes to increase maintainability and reusability alongside reducing redundancy. This implements a `PasswordLineEdit` that can show a red border when an invalid password is entered. It also features a button for showing/hiding the password entered. When combining two of these entries for setting a new password `PasswordInput` can be used from now on. It combines a form for entering and confirming a password with a label to show a message when there is an issue with the password. It also checks the entered password against some rules regarding its length. This PR replaces existing widgets for entering passwords with these two new widgets. * src/vorta/views/partials/password_input.py : Implement common input widgets/classes * src/vorta/views/repo_add_dialog.py : Use new widgets. * src/vorta/assets/UI/repoadd.ui : ^^^
- Loading branch information
Showing
6 changed files
with
476 additions
and
230 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
from typing import Optional | ||
|
||
from PyQt6.QtCore import QObject | ||
from PyQt6.QtGui import QAction | ||
from PyQt6.QtWidgets import QFormLayout, QLabel, QLineEdit, QWidget | ||
|
||
from vorta.i18n import translate | ||
from vorta.views.utils import get_colored_icon | ||
|
||
|
||
class PasswordLineEdit(QLineEdit): | ||
def __init__(self, *, parent: Optional[QWidget] = None, show_visibility_button: bool = True) -> None: | ||
super().__init__(parent) | ||
|
||
self._show_visibility_button = show_visibility_button | ||
self._error_state = False | ||
self._visible = False | ||
|
||
self.setEchoMode(QLineEdit.EchoMode.Password) | ||
|
||
if self._show_visibility_button: | ||
self.showHideAction = QAction(self.tr("Show password"), self) | ||
self.showHideAction.setCheckable(True) | ||
self.showHideAction.toggled.connect(self.toggle_visibility) | ||
self.showHideAction.setIcon(get_colored_icon("eye")) | ||
self.addAction(self.showHideAction, QLineEdit.ActionPosition.TrailingPosition) | ||
|
||
def get_password(self) -> str: | ||
"""Return password text""" | ||
return self.text() | ||
|
||
@property | ||
def visible(self) -> bool: | ||
"""Return password visibility""" | ||
return self._visible | ||
|
||
@visible.setter | ||
def visible(self, value: bool) -> None: | ||
"""Set password visibility""" | ||
if not isinstance(value, bool): | ||
raise TypeError("visible must be a boolean value") | ||
self._visible = value | ||
self.setEchoMode(QLineEdit.EchoMode.Normal if self._visible else QLineEdit.EchoMode.Password) | ||
|
||
if self._show_visibility_button: | ||
if self._visible: | ||
self.showHideAction.setIcon(get_colored_icon("eye-slash")) | ||
self.showHideAction.setText(self.tr("Hide password")) | ||
|
||
else: | ||
self.showHideAction.setIcon(get_colored_icon("eye")) | ||
self.showHideAction.setText(self.tr("Show password")) | ||
|
||
def toggle_visibility(self) -> None: | ||
"""Toggle password visibility""" | ||
self.visible = not self._visible | ||
|
||
@property | ||
def error_state(self) -> bool: | ||
"""Return error state""" | ||
return self._error_state | ||
|
||
@error_state.setter | ||
def error_state(self, error: bool) -> None: | ||
"""Set error state and update style""" | ||
self._error_state = error | ||
if error: | ||
self.setStyleSheet("QLineEdit { border: 2px solid red; }") | ||
else: | ||
self.setStyleSheet('') | ||
|
||
|
||
class PasswordInput(QObject): | ||
def __init__(self, *, parent=None, minimum_length: int = 9, show_error: bool = True, label: list = None) -> None: | ||
super().__init__(parent) | ||
self._minimum_length = minimum_length | ||
self._show_error = show_error | ||
|
||
if label: | ||
self._label_password = QLabel(label[0]) | ||
self._label_confirm = QLabel(label[1]) | ||
else: | ||
self._label_password = QLabel(self.tr("Enter passphrase:")) | ||
self._label_confirm = QLabel(self.tr("Confirm passphrase:")) | ||
|
||
# Create password line edits | ||
self.passwordLineEdit = PasswordLineEdit() | ||
self.confirmLineEdit = PasswordLineEdit() | ||
self.validation_label = QLabel("") | ||
|
||
self.passwordLineEdit.editingFinished.connect(self.on_editing_finished) | ||
self.confirmLineEdit.textChanged.connect(self.validate) | ||
|
||
def on_editing_finished(self) -> None: | ||
self.passwordLineEdit.editingFinished.disconnect(self.on_editing_finished) | ||
self.passwordLineEdit.textChanged.connect(self.validate) | ||
self.validate() | ||
|
||
def set_labels(self, label_1: str, label_2: str) -> None: | ||
self._label_password.setText(label_1) | ||
self._label_confirm.setText(label_2) | ||
|
||
def set_error_label(self, text: str) -> None: | ||
self.validation_label.setText(text) | ||
|
||
def set_validation_enabled(self, enable: bool) -> None: | ||
self._show_error = enable | ||
self.passwordLineEdit.error_state = False | ||
self.confirmLineEdit.error_state = False | ||
if not enable: | ||
self.set_error_label("") | ||
|
||
def clear(self) -> None: | ||
self.passwordLineEdit.clear() | ||
self.confirmLineEdit.clear() | ||
self.passwordLineEdit.error_state = False | ||
self.confirmLineEdit.error_state = False | ||
self.set_error_label("") | ||
|
||
def get_password(self) -> str: | ||
return self.passwordLineEdit.text() | ||
|
||
def validate(self) -> bool: | ||
if not self._show_error: | ||
return True | ||
|
||
first_pass = self.passwordLineEdit.text() | ||
second_pass = self.confirmLineEdit.text() | ||
|
||
pass_equal = first_pass == second_pass | ||
pass_long = len(first_pass) >= self._minimum_length | ||
|
||
self.passwordLineEdit.error_state = False | ||
self.confirmLineEdit.error_state = False | ||
self.set_error_label("") | ||
|
||
if not pass_long and not pass_equal: | ||
self.passwordLineEdit.error_state = True | ||
self.confirmLineEdit.error_state = True | ||
self.set_error_label( | ||
translate('PasswordInput', "Passwords must be identical and atleast {0} characters long.").format( | ||
self._minimum_length | ||
) | ||
) | ||
elif not pass_equal: | ||
self.confirmLineEdit.error_state = True | ||
self.set_error_label(translate('PasswordInput', "Passwords must be identical.")) | ||
elif not pass_long: | ||
self.passwordLineEdit.error_state = True | ||
self.set_error_label( | ||
translate('PasswordInput', "Passwords must be atleast {0} characters long.").format( | ||
self._minimum_length | ||
) | ||
) | ||
|
||
return not bool(self.validation_label.text()) | ||
|
||
def add_form_to_layout(self, form_layout: QFormLayout) -> None: | ||
"""Adds form to layout""" | ||
form_layout.addRow(self._label_password, self.passwordLineEdit) | ||
form_layout.addRow(self._label_confirm, self.confirmLineEdit) | ||
form_layout.addRow(self.validation_label) | ||
|
||
def create_form_widget(self, parent: Optional[QWidget] = None) -> QWidget: | ||
""" "Creates and Returns a new QWidget with form layout""" | ||
widget = QWidget(parent=parent) | ||
form_layout = QFormLayout(widget) | ||
form_layout.setContentsMargins(0, 0, 0, 0) | ||
form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) | ||
self.add_form_to_layout(form_layout) | ||
widget.setLayout(form_layout) | ||
return widget |
Oops, something went wrong.