File: C:/Users/fred/anaconda3/Lib/site-packages/anaconda_navigator/widgets/common.py
# -*- coding: utf-8 -*-
# pylint: disable=too-few-public-methods
"""Common widgets and their bases."""
from __future__ import annotations
__all__ = ['IconButton']
import typing
from qtpy import QtCore
from qtpy import QtGui
from qtpy import QtWidgets
from anaconda_navigator import widgets
from anaconda_navigator.static import images
from anaconda_navigator.utils import singletons
from anaconda_navigator.widgets.styling import PasswordHiddenIcon, PasswordVisibleIcon
class IconButton(QtWidgets.QPushButton):
"""Common control for icon-only buttons."""
class WarningIcon(QtWidgets.QLabel):
"""
Icon with exclamation sign.
Used by :class:`~WarningLabel` and its derivatives.
"""
class ErrorIcon(QtWidgets.QLabel):
"""
Icon with exclamation sign in circle.
"""
class InfoIcon(QtWidgets.QLabel):
"""
Icon with exclamation sign in circle.
"""
class LabelWithIcon(QtWidgets.QFrame):
"""
Combined label with icon at the beginning.
Icon is shown only when the main text is not empty.
"""
sig_button_clicked = QtCore.Signal()
def __init__( # pylint: disable=too-many-arguments
self,
icon: typing.Optional[widgets.QLabel] = None,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~WarningLabel` instance."""
super().__init__()
self.__icon: typing.Final[widgets.QLabel] = icon
self.__content: typing.Final[widgets.LabelBase] = widgets.LabelBase()
self.__content.setToolTip(tooltip)
self.__content.setWordWrap(True)
self.__button: typing.Final[widgets.LabelBase] = widgets.LabelBase()
self.__button.setText(button)
self.__button.sig_clicked.connect(self.sig_button_clicked)
self.__layout: typing.Final[QtWidgets.QHBoxLayout] = QtWidgets.QHBoxLayout()
self.__layout.addWidget(self.__icon, 0, alignment)
self.__layout.addWidget(self.__content, 1, alignment)
if button:
self.__layout.addWidget(self.__button, 0, alignment)
self.__layout.setContentsMargins(0, 0, 0, 0)
self.__layout.setSpacing(4)
self.setLayout(self.__layout)
self.text = text
@property
def text(self) -> str: # noqa: D401
"""Content of the main label."""
return self.__content.text()
@text.setter
def text(self, value: str) -> None:
"""Update `text` value."""
self.__content.setText(value)
self.__icon.setVisible(bool(value))
class WarningLabel(LabelWithIcon):
"""
Combined label with warning icon at the beginning.
Icon is shown only when the main text is not empty.
"""
def __init__(
self,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~WarningLabel` instance."""
super().__init__(icon=WarningIcon(), text=text, tooltip=tooltip, button=button, alignment=alignment)
class WarningBlock(WarningLabel):
"""Same as :class:`~WarningLabel`, but with colored background."""
def __init__(
self,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~WarningBlock` instance."""
super().__init__(text=text, tooltip=tooltip, button=button, alignment=alignment)
class ErrorLabel(LabelWithIcon):
"""
Combined label with error icon at the beginning.
Icon is shown only when the main text is not empty.
"""
def __init__(
self,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~ErrorLabel` instance."""
super().__init__(icon=ErrorIcon(), text=text, tooltip=tooltip, button=button, alignment=alignment)
class ErrorBlock(ErrorLabel):
"""Same as :class:`~ErrorLabel`, but with colored background."""
def __init__(
self,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~ErrorBlock` instance."""
super().__init__(text=text, tooltip=tooltip, button=button, alignment=alignment)
class InfoLabel(LabelWithIcon):
"""
Combined label with info icon at the beginning.
Icon is shown only when the main text is not empty.
"""
def __init__(
self,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~InfoLabel` instance."""
super().__init__(icon=InfoIcon(), text=text, tooltip=tooltip, button=button, alignment=alignment)
class InfoBlock(InfoLabel):
"""Same as :class:`~InfoLabel`, but with colored background."""
def __init__(
self,
text: str = '',
tooltip: str = '',
button: str = '',
alignment: typing.Any = QtCore.Qt.AlignTop,
) -> None:
"""Initialize new :class:`~InfoBlock` instance."""
super().__init__(text=text, tooltip=tooltip, button=button, alignment=alignment)
class TeamEditionServerAlert(QtWidgets.QWidget):
"""Show combined labels with icon about connectivity problems to TE Server."""
def __init__(self) -> None:
super().__init__()
self.error_label: typing.Final[ErrorBlock] = ErrorBlock(
text=(
'<b>Anaconda Server cannot be reached.</b> '
'Some actions may not work as expected until connection is restored.'
),
alignment=QtCore.Qt.AlignVCenter,
)
self.info_label: typing.Final[InfoBlock] = InfoBlock(
text='<b>Connection to Anaconda Anaconda Server server restored.</b>',
button=f'<img src="{images.BLOCK_CLOSE_ICON_PATH}" height="16" width="16">',
alignment=QtCore.Qt.AlignVCenter,
)
self.__timer: typing.Final[QtCore.QTimer] = QtCore.QTimer()
layout: typing.Final[QtWidgets.QVBoxLayout] = QtWidgets.QVBoxLayout()
layout.addWidget(self.error_label)
layout.addWidget(self.info_label)
self.info_label.sig_button_clicked.connect(self.hide_all)
self.setLayout(layout)
self.hide_all()
def show_info(self, limit: typing.Optional[int] = 5000) -> None:
"""Show info label for `limit` seconds"""
if not self.error_label.isVisible():
self.error_label.hide()
return
self.show()
self.error_label.hide()
self.info_label.show()
if limit:
self.__timer.singleShot(limit, self.hide_all)
def show_error(self) -> None:
"""Show error label"""
self.show()
self.error_label.show()
self.info_label.hide()
def hide_all(self) -> None:
"""Hide all labels."""
self.hide()
self.error_label.hide()
self.info_label.hide()
PASSWORD_HIDDEN: typing.Final[singletons.Singleton[QtGui.QIcon]] = singletons.SingleInstanceOf(PasswordHiddenIcon)
PASSWORD_VISIBLE: typing.Final[singletons.Singleton[QtGui.QIcon]] = singletons.SingleInstanceOf(PasswordVisibleIcon)
class LineEdit(QtWidgets.QLineEdit):
"""Custom QLineEdit with additional toggles for password entry."""
def __init__(self, *args: typing.Any, show_toggle: bool = False) -> None:
"""Initialize new :class:`~LineEdit` instance."""
super().__init__(*args)
toggle_action: typing.Optional[QtWidgets.QAction] = None
if show_toggle:
toggle_action = self.addAction(PASSWORD_HIDDEN.instance, QtWidgets.QLineEdit.TrailingPosition)
toggle_action.triggered.connect(lambda _: self.setEchoMode())
self._toggle_action: typing.Final[typing.Optional[QtWidgets.QAction]] = toggle_action
if show_toggle:
self.setEchoMode(QtWidgets.QLineEdit.Password)
def setEchoMode(self, mode: typing.Optional[int] = None) -> None: # pylint: disable=invalid-name
"""Set how text should be echoed to the user."""
if mode is None:
if self.echoMode() == QtWidgets.QLineEdit.Password:
mode = QtWidgets.QLineEdit.Normal
else:
mode = QtWidgets.QLineEdit.Password
super().setEchoMode(mode)
if self._toggle_action is None:
return
if mode == QtWidgets.QLineEdit.Password:
self._toggle_action.setIcon(PASSWORD_HIDDEN.instance)
else:
self._toggle_action.setIcon(PASSWORD_VISIBLE.instance)