Skip to content

Commit

Permalink
rework user auth ui, add menu, icon, & styling
Browse files Browse the repository at this point in the history
  • Loading branch information
Allie Crevier committed Mar 21, 2019
1 parent 28171fe commit d69a0c4
Show file tree
Hide file tree
Showing 7 changed files with 561 additions and 94 deletions.
13 changes: 6 additions & 7 deletions securedrop_client/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import logging
from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QDesktopWidget, \
QStatusBar
from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QDesktopWidget
from typing import List

from securedrop_client import __version__
from securedrop_client.db import Source
from securedrop_client.gui.widgets import ToolBar, MainView, LoginDialog, SourceConversationWrapper
from securedrop_client.gui.widgets import ToolBar, MainView, LoginDialog, StatusBar, \
SourceConversationWrapper
from securedrop_client.resources import load_icon

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,8 +63,7 @@ def __init__(self, sdc_home: str):
self.central_widget.setLayout(central_widget_layout)
self.setCentralWidget(self.central_widget)

self.status_bar = QStatusBar(self)
self.status_bar.setStyleSheet('background-color: #fff;')
self.status_bar = StatusBar()
central_widget_layout.addWidget(self.status_bar)

self.widget = QWidget()
Expand Down Expand Up @@ -98,7 +97,7 @@ def setup(self, controller):
"""
self.controller = controller # Reference the Client logic instance.
self.tool_bar.setup(self, controller)

self.status_bar.setup(controller)
self.set_status(_('Started SecureDrop Client. Please sign in.'), 20000)

self.login_dialog = LoginDialog(self)
Expand Down Expand Up @@ -201,4 +200,4 @@ def set_status(self, message, duration=0):
Display a status message to the user. Optionally, supply a duration
(in milliseconds), the default will continuously show the message.
"""
self.status_bar.showMessage(message, duration)
self.status_bar.set_message(message, duration)
194 changes: 140 additions & 54 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
import arrow
import html
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QIcon, QPalette, QBrush, QColor, QFont
from PyQt5.QtWidgets import QListWidget, QLabel, QWidget, QListWidgetItem, QHBoxLayout, \
QPushButton, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, QMessageBox, \
QToolButton, QSizePolicy, QTextEdit
QToolButton, QSizePolicy, QTextEdit, QStatusBar
from typing import List
from uuid import uuid4

Expand All @@ -36,48 +36,102 @@
logger = logging.getLogger(__name__)


class StatusBar(QStatusBar):
def __init__(self):
super().__init__()

self.setStyleSheet('''
QStatusBar { background-color: #fff; }
QStatusBar::item { border: none; }
QPushButton { border: none; }
''')

self.refresh = QPushButton()
self.refresh.clicked.connect(self.on_refresh_clicked)
self.refresh.setMaximumSize(30, 30)
refresh_pixmap = load_image('refresh.svg')
self.refresh.setIcon(QIcon(refresh_pixmap))
self.addPermanentWidget(self.refresh) # widget may not be obscured by temporary messages

def setup(self, controller):
"""
Assign a controller object (containing the application logic).
"""
self.controller = controller
self.controller.sync_events.connect(self._on_sync_event)

def on_refresh_clicked(self):
"""
Called when the refresh button is clicked.
"""
self.controller.sync_api()

def _on_sync_event(self, data):
"""
Called when the refresh call completes
"""
self.refresh.setEnabled(data != 'syncing')

def set_message(self, message, duration=0):
"""
Display a status message to the user. Optionally, supply a duration
(in milliseconds), the default will continuously show the message.
"""
self.showMessage(message, duration)


class ToolBar(QWidget):
"""
Represents the tool bar across the top of the user interface.
"""

def __init__(self, parent: QWidget):
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)

self.user_state = QLabel(_('Signed out.'))

self.login = QPushButton(_('Sign in'))
self.login.setMaximumSize(80, 30)
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 10, 20, 10)

self.setAutoFillBackground(True)
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(load_image('hexes.svg')))
self.setPalette(palette)
self.user_icon = QLabel()
self.user_icon.setFont(QFont("Helvetica [Cronyx]", 16, QFont.Bold))
self.user_icon.hide()
self.user_state = QLabel()
self.user_state.setFont(QFont("Helvetica [Cronyx]", 12, QFont.Bold))
self.user_state.hide()
self.user_menu = JournalistMenuButton(self)
self.user_menu.hide()

self.login = QPushButton(_('SIGN IN'))
self.login.setFont(QFont("Helvetica [Cronyx]", 10))
self.login.setMinimumSize(200, 40)
button_palette = self.login.palette()
button_palette.setColor(QPalette.Button, QColor('#eee'))
button_palette.setColor(QPalette.ButtonText, QColor('#000'))
self.login.setAutoFillBackground(True)
self.login.setPalette(button_palette)
self.login.update()
self.login.clicked.connect(self.on_login_clicked)

self.logout = QPushButton(_('Sign out'))
self.logout.clicked.connect(self.on_logout_clicked)
self.logout.setMaximumSize(80, 30)
self.logout.setVisible(False)

self.refresh = QPushButton()
self.refresh.clicked.connect(self.on_refresh_clicked)
self.refresh.setMaximumSize(30, 30)
refresh_pixmap = load_image('refresh.svg')

self.refresh.setIcon(QIcon(refresh_pixmap))
self.refresh.show()
spacer = QWidget()
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

self.logo = QLabel()
self.logo.setPixmap(load_image('icon.png'))
self.logo.setMinimumSize(200, 200)
logo = QLabel()
logo.setMinimumSize(200, 200)
logo.setPixmap(load_image('logo.png'))

journalist_layout = QHBoxLayout()
journalist_layout.addWidget(self.refresh, 1)
journalist_layout.addWidget(self.user_state, 5)
journalist_layout.addWidget(self.login, 5)
journalist_layout.addWidget(self.logout, 5)
journalist_layout.addStretch()
user_layout = QHBoxLayout()
user_layout.addWidget(self.user_icon, 5, Qt.AlignLeft)
user_layout.addWidget(self.user_state, 5, Qt.AlignLeft)
user_layout.addWidget(self.login, 5, Qt.AlignLeft)
user_layout.addWidget(self.user_menu, 5, Qt.AlignLeft)
user_layout.addStretch()

layout.addLayout(journalist_layout)
layout.addWidget(self.logo)
layout.addLayout(user_layout)
layout.addWidget(spacer, 5)
layout.addWidget(logo, 3, Qt.AlignCenter)
layout.addStretch()

def setup(self, window, controller):
Expand All @@ -91,25 +145,31 @@ def setup(self, window, controller):
self.window = window
self.controller = controller

self.controller.sync_events.connect(self._on_sync_event)

def set_logged_in_as(self, username):
"""
Update the UI to reflect that the user is logged in as "username".
"""
self.user_state.setText(html.escape(username))
self.login.setVisible(False)
self.logout.setVisible(True)
self.refresh.setVisible(True)
self.login.hide()

self.user_icon.setText(_('jo'))
self.user_icon.setStyleSheet('''
QLabel { background-color: #045fb4; color: cyan; padding: 10; border: 1px solid gray; }
''')
self.user_icon.show()

self.user_state.setText(_('{}').format(html.escape(username)))
self.user_state.show()

self.user_menu.show()

def set_logged_out(self):
"""
Update the UI to a logged out state.
"""
self.user_state.setText(_('Signed out.'))
self.login.setVisible(True)
self.logout.setVisible(False)
self.refresh.setVisible(False)
self.login.show()
self.user_icon.hide()
self.user_state.hide()
self.user_menu.hide()

def on_login_clicked(self):
"""
Expand All @@ -123,18 +183,6 @@ def on_logout_clicked(self):
"""
self.controller.logout()

def on_refresh_clicked(self):
"""
Called when the refresh button is clicked.
"""
self.controller.sync_api()

def _on_sync_event(self, data):
"""
Called when the refresh call completes
"""
self.refresh.setEnabled(data != 'syncing')


class MainView(QWidget):
"""
Expand All @@ -159,6 +207,7 @@ def __init__(self, parent):
self.error_status = QLabel('')
self.error_status.setObjectName('error_label')
left_layout.addWidget(self.error_status)
self.error_status.hide()

self.layout.addWidget(left_column, 4)

Expand Down Expand Up @@ -895,6 +944,43 @@ def trigger(self):
self.messagebox.launch()


class JournalistMenu(QMenu):
"""A menu next to the journalist username.
A menu that provides login options.
"""

def __init__(self, parent):
super().__init__()
self.logout = QAction(_('SIGN OUT'))
self.logout.setFont(QFont("Helvetica [Cronyx]", 10))
self.addAction(self.logout)
self.logout.triggered.connect(parent.on_logout_clicked)


class JournalistMenuButton(QToolButton):
"""An menu button for the journalist menu
This button is responsible for launching the journalist menu on click.
"""

def __init__(self, parent):
super().__init__()

self.setStyleSheet('''
QToolButton::menu-indicator { image: none; }
QToolButton { border: none; }
''')
arrow = load_image("dropdown_arrow.svg")
self.setIcon(QIcon(arrow))
self.setMinimumSize(20, 20)

self.menu = JournalistMenu(parent)
self.setMenu(self.menu)

self.setPopupMode(QToolButton.InstantPopup)


class SourceMenu(QMenu):
"""Renders menu having various operations.
Expand Down Expand Up @@ -923,7 +1009,7 @@ def __init__(self, source, controller):
class SourceMenuButton(QToolButton):
"""An ellipse based source menu button.
This button is responsible for launching menu on click.
This button is responsible for launching the source menu on click.
"""

def __init__(self, source, controller):
Expand Down
11 changes: 11 additions & 0 deletions securedrop_client/resources/images/dropdown_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d69a0c4

Please sign in to comment.