diff --git a/securedrop_client/gui/main.py b/securedrop_client/gui/main.py
index 877da095c8..1a9f56ff59 100644
--- a/securedrop_client/gui/main.py
+++ b/securedrop_client/gui/main.py
@@ -20,7 +20,8 @@
along with this program. If not, see .
"""
import logging
-from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QDesktopWidget, QStatusBar
+from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, \
+ QDesktopWidget, QStatusBar
from typing import List
from securedrop_client import __version__
@@ -57,18 +58,29 @@ def __init__(self, sdc_home: str):
self.setWindowTitle(_("SecureDrop Client {}").format(__version__))
self.setWindowIcon(load_icon(self.icon))
+ self.central_widget = QWidget()
+ central_widget_layout = QVBoxLayout()
+ central_widget_layout.setContentsMargins(0, 0, 0, 0)
+ self.central_widget.setLayout(central_widget_layout)
+ self.setCentralWidget(self.central_widget)
+
+ self.status_bar = QStatusBar(self)
+ self.status_bar.setStyleSheet('background-color: #fff;')
+ central_widget_layout.addWidget(self.status_bar)
+
self.widget = QWidget()
- widget_layout = QVBoxLayout()
+ widget_layout = QHBoxLayout()
+ widget_layout.setContentsMargins(0, 0, 0, 0)
self.widget.setLayout(widget_layout)
- self.setCentralWidget(self.widget)
self.tool_bar = ToolBar(self.widget)
-
self.main_view = MainView(self.widget)
self.main_view.source_list.itemSelectionChanged.connect(self.on_source_changed)
widget_layout.addWidget(self.tool_bar, 1)
- widget_layout.addWidget(self.main_view, 6)
+ widget_layout.addWidget(self.main_view, 8)
+
+ central_widget_layout.addWidget(self.widget)
# Cache a dict of source.uuid -> SourceConversationWrapper
# We do this to not create/destroy widgets constantly (because it causes UI "flicker")
@@ -88,8 +100,6 @@ def setup(self, controller):
self.controller = controller # Reference the Client logic instance.
self.tool_bar.setup(self, controller)
- self.status_bar = QStatusBar(self)
- self.setStatusBar(self.status_bar)
self.set_status('Started SecureDrop Client. Please sign in.', 20000)
self.login_dialog = LoginDialog(self)
@@ -144,10 +154,9 @@ def show_sync(self, updated_on):
Display a message indicating the data-sync state.
"""
if updated_on:
- self.main_view.status.setText('Last refresh: ' +
- updated_on.humanize())
+ self.set_status('Last refresh: ' + updated_on.humanize())
else:
- self.main_view.status.setText(_('Waiting to refresh...'))
+ self.set_status('Waiting to refresh...', 5000)
def set_logged_in_as(self, username):
"""
@@ -188,9 +197,9 @@ def show_conversation_for(self, source: Source, is_authenticated: bool):
self.main_view.set_conversation(conversation_container)
- def set_status(self, message, duration=5000):
+ def set_status(self, message, duration=0):
"""
Display a status message to the user. Optionally, supply a duration
- (in milliseconds), the default value being a duration of 5 seconds.
+ (in milliseconds), the default will continuously show the message.
"""
self.status_bar.showMessage(message, duration)
diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py
index 56833dbb4c..d4e20179d7 100644
--- a/securedrop_client/gui/widgets.py
+++ b/securedrop_client/gui/widgets.py
@@ -43,29 +43,44 @@ class ToolBar(QWidget):
def __init__(self, parent: QWidget):
super().__init__(parent)
- layout = QHBoxLayout(self)
- self.logo = QLabel()
- self.logo.setPixmap(load_image('header_logo.png'))
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
- self.user_state = QLabel(_("Signed out."))
+ self.user_state = QLabel(_('Signed out.'))
self.login = QPushButton(_('Sign in'))
+ self.login.setMaximumSize(80, 30)
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(_('Refresh'))
+ self.refresh = QPushButton()
self.refresh.clicked.connect(self.on_refresh_clicked)
+ self.refresh.setMaximumSize(30, 30)
+ refresh_pixmap = load_image('refresh.png')
+ refresh_icon = QIcon(refresh_pixmap)
+ self.refresh.setIcon(refresh_icon)
+ self.refresh.setIconSize(refresh_pixmap.rect().size())
self.refresh.setVisible(False)
+ self.logo = QLabel()
+ self.logo.setStyleSheet('background-color: #CCC;')
+ self.logo.setPixmap(load_image('branding.png'))
+ self.logo.setMinimumSize(200, 200)
+
+ journalist_layout = QHBoxLayout(self)
+ 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()
+
+ layout.addLayout(journalist_layout)
layout.addWidget(self.logo)
layout.addStretch()
- layout.addWidget(self.user_state)
- layout.addWidget(self.login)
- layout.addWidget(self.logout)
- layout.addWidget(self.refresh)
def setup(self, window, controller):
"""
@@ -84,7 +99,7 @@ def set_logged_in_as(self, username):
"""
Update the UI to reflect that the user is logged in as "username".
"""
- self.user_state.setText(_('Signed in as: ' + html.escape(username)))
+ self.user_state.setText(_(html.escape(username)))
self.login.setVisible(False)
self.logout.setVisible(True)
self.refresh.setVisible(True)
@@ -132,27 +147,29 @@ class MainView(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.layout = QHBoxLayout(self)
+ self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
left_column = QWidget(parent=self)
left_layout = QVBoxLayout()
+ left_layout.setContentsMargins(0, 0, 0, 0)
left_column.setLayout(left_layout)
- self.status = QLabel(_('Waiting to refresh...'))
self.error_status = QLabel('')
self.error_status.setObjectName('error_label')
-
- left_layout.addWidget(self.status)
- left_layout.addWidget(self.error_status)
+ # TODO: move this to the main window QStatusBar
+ # left_layout.addWidget(self.error_status)
self.source_list = SourceList(left_column)
left_layout.addWidget(self.source_list)
- self.layout.addWidget(left_column, 2)
+ self.layout.addWidget(left_column, 4)
- self.view_holder = QWidget()
self.view_layout = QVBoxLayout()
+ self.view_layout.setContentsMargins(0, 0, 0, 0)
+ self.view_holder = QWidget()
self.view_holder.setLayout(self.view_layout)
+
self.layout.addWidget(self.view_holder, 6)
def setup(self, controller):
@@ -174,8 +191,8 @@ def set_conversation(self, widget):
if old_widget:
old_widget.widget().setVisible(False)
- self.view_layout.addWidget(widget)
widget.setVisible(True)
+ self.view_layout.addWidget(widget)
class SourceList(QListWidget):
@@ -372,11 +389,13 @@ def setup(self, controller):
self.setWindowTitle(_('Sign in to SecureDrop'))
main_layout = QHBoxLayout()
+ main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addStretch()
self.setLayout(main_layout)
form = QWidget()
layout = QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
form.setLayout(layout)
main_layout.addWidget(form)
@@ -528,12 +547,14 @@ def __init__(self,
"""
super().__init__()
layout = QHBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+
label = SpeechBubble(message_id, message, update_signal)
if align != "left":
# Float right...
layout.addStretch(5)
- label.setStyleSheet(label.css + 'border-bottom-right-radius: 0px;')
+ label.setStyleSheet(label.css)
layout.addWidget(label, 6)
@@ -542,10 +563,7 @@ def __init__(self,
layout.addStretch(5)
label.setStyleSheet(label.css + 'border-bottom-left-radius: 0px;')
- layout.setContentsMargins(0, 0, 0, 0)
-
self.setLayout(layout)
- self.setContentsMargins(0, 0, 0, 0)
class MessageWidget(ConversationWidget):
@@ -678,6 +696,7 @@ def __init__(self, source_db_object: Source, sdc_home: str, controller: Client,
self.container = QWidget()
self.conversation_layout = QVBoxLayout()
+ self.conversation_layout.setContentsMargins(0, 0, 0, 0)
self.container.setLayout(self.conversation_layout)
self.container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -693,9 +712,9 @@ def __init__(self, source_db_object: Source, sdc_home: str, controller: Client,
sb.rangeChanged.connect(self.update_conversation_position)
main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(self.scroll)
self.setLayout(main_layout)
-
self.update_conversation(self.source.collection)
def clear_conversation(self):
@@ -788,14 +807,15 @@ def __init__(
self.sdc_home = sdc_home
self.layout = QVBoxLayout()
+ self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.conversation = ConversationView(self.source, self.sdc_home, self.controller,
parent=self)
self.source_profile = SourceProfileShortWidget(self.source, self.controller)
- self.layout.addWidget(self.source_profile)
- self.layout.addWidget(self.conversation)
+ self.layout.addWidget(self.source_profile, 1)
+ self.layout.addWidget(self.conversation, 9)
self.controller.authentication_state.connect(self._show_or_hide_replybox)
self._show_or_hide_replybox(is_authenticated)
@@ -816,7 +836,7 @@ def _show_or_hide_replybox(self, show: bool) -> None:
old_widget.widget().deleteLater()
self.reply_box = new_widget
- self.layout.addWidget(new_widget)
+ self.layout.addWidget(new_widget, 3)
class ReplyBoxWidget(QWidget):
@@ -828,14 +848,22 @@ def __init__(self, conversation: SourceConversationWrapper) -> None:
super().__init__()
self.conversation = conversation
- self.text_edit = QTextEdit()
+ self.text_edit = QTextEdit('Compose a reply')
- self.send_button = QPushButton('Send')
+ self.send_button = QPushButton()
self.send_button.clicked.connect(self.send_reply)
+ self.send_button.setMaximumSize(40, 40)
- layout = QHBoxLayout()
+ button_pixmap = load_image('send.png')
+ button_icon = QIcon(button_pixmap)
+ self.send_button.setIcon(button_icon)
+ self.send_button.setIconSize(button_pixmap.rect().size())
+
+ layout = QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.text_edit)
- layout.addWidget(self.send_button)
+
+ layout.addWidget(self.send_button, 0, Qt.AlignRight)
self.setLayout(layout)
def send_reply(self) -> None:
@@ -918,9 +946,9 @@ class TitleLabel(QLabel):
"""Centered aligned, HTML heading level 3 label."""
def __init__(self, text):
- html_text = "
%s
" % (text,)
+ html_text = "%s
" % (text,)
super().__init__(_(html_text))
- self.setAlignment(Qt.AlignCenter)
+ self.setAlignment(Qt.AlignLeft)
class SourceProfileShortWidget(QWidget):
diff --git a/securedrop_client/resources/images/branding.png b/securedrop_client/resources/images/branding.png
new file mode 100644
index 0000000000..5fa7ce482a
Binary files /dev/null and b/securedrop_client/resources/images/branding.png differ
diff --git a/securedrop_client/resources/images/delete.png b/securedrop_client/resources/images/delete.png
new file mode 100644
index 0000000000..6c9656c98a
Binary files /dev/null and b/securedrop_client/resources/images/delete.png differ
diff --git a/securedrop_client/resources/images/refresh.png b/securedrop_client/resources/images/refresh.png
new file mode 100644
index 0000000000..65a66adeea
Binary files /dev/null and b/securedrop_client/resources/images/refresh.png differ
diff --git a/securedrop_client/resources/images/send.png b/securedrop_client/resources/images/send.png
new file mode 100644
index 0000000000..1fe8922a1e
Binary files /dev/null and b/securedrop_client/resources/images/send.png differ
diff --git a/securedrop_client/resources/images/trash.png b/securedrop_client/resources/images/trash.png
new file mode 100644
index 0000000000..3bd54f6e22
Binary files /dev/null and b/securedrop_client/resources/images/trash.png differ