Skip to content

Commit

Permalink
wip styling
Browse files Browse the repository at this point in the history
  • Loading branch information
rocodes committed Feb 9, 2024
1 parent 3fd105a commit b24cb10
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 84 deletions.
29 changes: 0 additions & 29 deletions client/securedrop_client/gui/conversation/export/dialog_button.css

This file was deleted.

57 changes: 38 additions & 19 deletions client/securedrop_client/gui/conversation/export/export_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt, pyqtSlot
from PyQt5.QtGui import QIcon, QKeyEvent
from PyQt5.QtGui import QFont, QIcon, QKeyEvent
from PyQt5.QtWidgets import QApplication, QWizard, QWizardPage

from securedrop_client.export import Export
Expand All @@ -31,7 +31,9 @@ class ExportWizard(QWizard):
PASSPHRASE_LABEL_SPACING = 0.5
NO_MARGIN = 0
FILENAME_WIDTH_PX = 260
BUTTON_CSS = resource_string(__name__, "dialog_button.css").decode("utf-8")
FILE_OPTIONS_FONT_SPACING = 1.6
BUTTON_CSS = resource_string(__name__, "wizard_button.css").decode("utf-8")
WIZARD_CSS = resource_string(__name__, "wizard.css").decode("utf-8")

# If the drive is unlocked, we don't need a passphrase; if we do need one,
# it's populated later.
Expand All @@ -56,6 +58,7 @@ def __init__(self, export: Export, summary_text: str, filepaths: List[str]) -> N
self._set_layout()
self._set_pages()
self._style_buttons()
self.adjustSize()

def keyPressEvent(self, event: QKeyEvent) -> None:
if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
Expand All @@ -71,38 +74,54 @@ def text(self) -> str:
return self.body.text()

def _style_buttons(self) -> None:
button_font = QFont()
button_font.setLetterSpacing(QFont.AbsoluteSpacing, self.FILE_OPTIONS_FONT_SPACING)

self.next_button = self.button(QWizard.WizardButton.NextButton)
self.next_button.clicked.connect(self.request_export)
self.next_button.setObjectName("QWizardButton_PrimaryButton")
self.next_button.setStyleSheet(self.BUTTON_CSS)
self.next_button.setMinimumSize(QSize(130, 24))
self.next_button.clicked.connect(self.request_export)

self.cancel_button = self.button(QWizard.WizardButton.CancelButton)
self.cancel_button.setObjectName("QWizardButton_GenericButton")
self.cancel_button.setStyleSheet(self.BUTTON_CSS)
self.cancel_button.setMinimumSize(QSize(130, 24))

self.back_button = self.button(QWizard.WizardButton.BackButton)
self.back_button.setObjectName("QWizardButton_GenericButton")
self.back_button.setStyleSheet(self.BUTTON_CSS)
self.back_button.setMinimumSize(QSize(130, 24))

self.finish_button = self.button(QWizard.WizardButton.FinishButton)
self.finish_button.setObjectName("QWizardButton_GenericButton")
self.finish_button.setMinimumSize(QSize(135, 25))
self.finish_button.setStyleSheet(self.BUTTON_CSS)

# Activestate animation
self.button_animation = load_movie("activestate-wide.gif")
self.button_animation.setScaledSize(QSize(32, 32))
self.button_animation.frameChanged.connect(self.animate_activestate)

# It's not button styling. I wish I could tell you why.
self.setButtonText(self.next_button, _("CONTINUE"))
self.setButtonText(self.cancel_button, _("CANCEL"))
self.setButtonText(self.finish_button, _("DONE"))
self.setButtonText(self.back_button, _("BACK")) # TODO

def animate_activestate(self) -> None:
self.next_button.setIcon(QIcon(self.button_animation.currentPixmap()))

def start_animate_activestate(self) -> None:
self.button_animation.start()
self.next_button.setMinimumSize(QSize(142, 43))
# Reset widget stylesheets
self.next_button.setStyleSheet("")
self.next_button.setObjectName("ModalDialog_primary_button_active")
self.next_button.setStyleSheet(self.BUTTON_CSS)

def stop_animate_activestate(self) -> None:
self.next_button.setIcon(QIcon())
self.button_animation.stop()
# Reset widget stylesheets
self.next_button.setStyleSheet("")
self.next_button.setObjectName("ModalDialog_primary_button")
self.next_button.setStyleSheet(self.BUTTON_CSS)

def _set_layout(self) -> None:
self.setWindowTitle(f"Export {self.summary_text}")
self.setWindowTitle(f"Export {self.summary_text}") # TODO (il8n)
self.setStyleSheet(self.WIZARD_CSS)
self.setModal(False)
self.setOptions(
QWizard.NoBackButtonOnLastPage
Expand All @@ -120,10 +139,6 @@ def _set_pages(self) -> None:
]:
self.setPage(id, page)

# Nice to have, but steals the focus from the password field after 1 character is typed.
# Probably another way to have it be based on validating the status
# page.completeChanged.connect(lambda: self._set_focus(QWizard.WizardButton.NextButton))

@pyqtSlot(int)
def _set_focus(self, which: QWizard.WizardButton) -> None:
self.button(which).setFocus()
Expand All @@ -134,7 +149,9 @@ def request_export(self) -> None:
# This prevents the dialog from briefly flashing one page and then
# advancing to a subsequent page (for example, flashing the "Insert a USB"
# page before detecting the USB and advancing to the "Unlock USB" page)
self.currentPage().set_complete(False)
page = self.currentPage()
if page:
page.set_complete(False)
self.start_animate_activestate()

# Registered fields let us access the passphrase field
Expand Down Expand Up @@ -162,7 +179,9 @@ def on_status_received(self, status: ExportStatus) -> None:
logger.debug(f"Wizard received {status.value}. Current page is {type(self.currentPage())}")

# Release the page (page was held during "next" button click event)
self.currentPage().set_complete(True)
page = self.currentPage()
if page:
page.set_complete(True)
self.stop_animate_activestate()

# Unrecoverable - end the wizard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class ExportWizardPage(QWizardPage):
* Directional buttons (continue/done, cancel)
"""

DIALOG_CSS = resource_string(__name__, "dialog.css").decode("utf-8")
ERROR_DETAILS_CSS = resource_string(__name__, "dialog_message.css").decode("utf-8")
WIZARD_CSS = resource_string(__name__, "wizard.css").decode("utf-8")
ERROR_DETAILS_CSS = resource_string(__name__, "wizard_message.css").decode("utf-8")

MARGIN = 40
PASSPHRASE_LABEL_SPACING = 0.5
Expand All @@ -72,7 +72,8 @@ def __init__(self, export: Export, header: str, body: str) -> None:

def set_complete(self, is_complete: bool) -> None:
"""
Flag a page as being incomplete. (Disables Next button)
Flag a page as being incomplete. (Disables Next button and prevents
user from advancing to next page)
"""
self._is_complete = is_complete

Expand All @@ -83,7 +84,7 @@ def _build_layout(self) -> QVBoxLayout:
"""
Create parent layout, draw elements, return parent layout
"""
self.setStyleSheet(self.DIALOG_CSS)
self.setStyleSheet(self.WIZARD_CSS)
parent_layout = QVBoxLayout()
parent_layout.setContentsMargins(self.MARGIN, self.MARGIN, self.MARGIN, self.MARGIN)

Expand All @@ -92,25 +93,25 @@ def _build_layout(self) -> QVBoxLayout:
header_container_layout = QHBoxLayout()
header_container.setLayout(header_container_layout)
self.header_icon = SvgLabel("blank.svg", svg_size=QSize(64, 64))
self.header_icon.setObjectName("ModalDialog_header_icon")
self.header_icon.setObjectName("QWizard_header_icon")
self.header_spinner = QPixmap()
self.header_spinner_label = QLabel()
self.header_spinner_label.setObjectName("ModalDialog_header_spinner")
self.header_spinner_label.setObjectName("QWizard_header_spinner")
self.header_spinner_label.setMinimumSize(64, 64)
self.header_spinner_label.setVisible(False)
self.header_spinner_label.setPixmap(self.header_spinner)
self.header = QLabel()
self.header.setObjectName("ModalDialog_header")
self.header.setObjectName("QWizard_header")
header_container_layout.addWidget(self.header, alignment=Qt.AlignCenter)
header_container_layout.addWidget(self.header_icon)
header_container_layout.addWidget(self.header_spinner_label)
header_container_layout.addWidget(self.header, alignment=Qt.AlignLeft) # Prev: AlignCenter
header_container_layout.addStretch()
self.header_line = QWidget()
self.header_line.setObjectName("ModalDialog_header_line")
self.header_line.setObjectName("QWizard_header_line")

# Body to display instructions and forms
self.body = QLabel()
self.body.setObjectName("ModalDialog_body")
self.body.setObjectName("QWizard_body")
self.body.setWordWrap(True)
self.body.setScaledContents(True)

Expand All @@ -121,15 +122,14 @@ def _build_layout(self) -> QVBoxLayout:
)
body_container.setLayout(self.body_layout)
self.body_layout.addWidget(self.body)
self.body_layout.setSizeConstraint(QLayout.SetMinimumSize)

# TODO: it's either like this, or in the parent layout elements
self.body_layout.setSizeConstraint(QLayout.SetMinimumSize)

# Widget for displaying error messages (hidden by default)
self.error_details = QLabel()
self.error_details.setObjectName("ModalDialog_error_details")
self.error_details.setObjectName("QWizard_error_details")
self.error_details.setStyleSheet(self.ERROR_DETAILS_CSS)
self.error_details.setContentsMargins(
self.NO_MARGIN, self.NO_MARGIN, self.NO_MARGIN, self.NO_MARGIN
)
self.error_details.setWordWrap(True)
self.error_details.hide()

Expand All @@ -147,7 +147,10 @@ def _build_layout(self) -> QVBoxLayout:
parent_layout.addWidget(self.header_line)
parent_layout.addWidget(body_container)
parent_layout.addWidget(self.error_details)
# parent_layout.setSizeConstraint(QLayout.SetFixedSize)
parent_layout.addStretch()

# Try it
parent_layout.setSizeConstraint(QLayout.SetFixedSize)

return parent_layout

Expand All @@ -159,7 +162,7 @@ def animate_activestate(self) -> None:

def start_animate_activestate(self) -> None:
self.error_details.setStyleSheet("")
self.error_details.setObjectName("ModalDialog_error_details_active")
self.error_details.setObjectName("QWizard_error_details_active")
self.error_details.setStyleSheet(self.ERROR_DETAILS_CSS)

def start_animate_header(self) -> None:
Expand All @@ -169,7 +172,7 @@ def start_animate_header(self) -> None:

def stop_animate_activestate(self) -> None:
self.error_details.setStyleSheet("")
self.error_details.setObjectName("ModalDialog_error_details")
self.error_details.setObjectName("QWizard_error_details")
self.error_details.setStyleSheet(self.ERROR_DETAILS_CSS)

def stop_animate_header(self) -> None:
Expand Down Expand Up @@ -390,7 +393,7 @@ def _build_layout(self) -> QVBoxLayout:

# Passphrase Form
self.passphrase_form = QWidget()
self.passphrase_form.setObjectName("ModalDialog_passphrase_form")
self.passphrase_form.setObjectName("QWizard_passphrase_form")
passphrase_form_layout = QVBoxLayout()
passphrase_form_layout.setContentsMargins(
self.NO_MARGIN, self.NO_MARGIN, self.NO_MARGIN, self.NO_MARGIN
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#ModalDialog {
#QWizard {
min-width: 800px;
max-width: 800px;
min-height: 300px;
max-height: 800px;
background-color: #fff;
}

#ModalDialog_header_icon, #ModalDialog_header_spinner {
#QWizard_header_icon, #QWizard_header_spinner {
min-width: 80px;
max-width: 80px;
min-height: 64px;
max-height: 64px;
margin: 0px 0px 0px 30px;
}

#ModalDialog_header {
#QWizard_header {
min-height: 68px;
max-height: 68px;
margin: 0;
Expand All @@ -24,39 +24,32 @@
color: #2a319d;
}

#ModalDialog_header_line {
#QWizard_header_line {
margin: 0;
min-height: 2px;
max-height: 2px;
background-color: rgba(42, 49, 157, 0.15);
border: none;
}

#ModalDialog_body {
#QWizard_body {
font-family: 'Montserrat';
font-size: 16px;
color: #302aa3;
margin: 0;
padding: 0;
}

#ModalDialogConfirmation {
#QWizardConfirmation {
font-family: 'Montserrat';
font-size: 16px;
font-weight: 600;
color: #302aa3;
margin: 0;
}

#ModalDialog.dangerous #ModalDialogConfirmation {
color: #ff3366;
}

#ModalDialog_button_box {
border: 1px solid #ff0000;
}

#ModalDialog_button_box QPushButton {
#QWizard_button_box QWizardButton {
margin: 0px 0px 0px 12px;
height: 40px;
margin: 0;
Expand All @@ -69,7 +62,7 @@
color: #2a319d;
}

#ModalDialog_button_box QPushButton::disabled {
#QWizard_button_box QWizardButton::disabled {
border: 2px solid rgba(42, 49, 157, 0.4);
color: rgba(42, 49, 157, 0.4);
}
Expand Down
Loading

0 comments on commit b24cb10

Please sign in to comment.