Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui polish] Reply pane styling #580

Merged
merged 10 commits into from
Oct 25, 2019
72 changes: 55 additions & 17 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from PyQt5.QtGui import QIcon, QPalette, QBrush, QColor, QFont, QLinearGradient
from PyQt5.QtWidgets import QListWidget, QLabel, QWidget, QListWidgetItem, QHBoxLayout, \
QPushButton, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, QMessageBox, \
QToolButton, QSizePolicy, QTextEdit, QStatusBar, QGraphicsDropShadowEffect, QApplication
QToolButton, QSizePolicy, QPlainTextEdit, QStatusBar, QGraphicsDropShadowEffect, QApplication

from securedrop_client.db import Source, Message, File, Reply, User
from securedrop_client.storage import source_exists
Expand Down Expand Up @@ -2222,19 +2222,36 @@ class ReplyBoxWidget(QWidget):
"""

CSS = '''
#replybox {
#replybox_holder {
min-height: 173px;
max-height: 173px;
}
#replybox {
background-color: #ffffff;
}
#replybox::disabled {
background-color: #efefef;
}
QPlainTextEdit {
font-family: 'Montserrat';
font-weight: 400;
font-size: 18px;
color: #9c9dbb;
}
QTextEdit {
border: none;
margin-left: 32.6px;
margin-top: 19px;
margin-bottom: 18px;
margin-right: 30.2px;
}
QPushButton {
border: none;
margin-right: 27.3px;
margin-bottom: 18px;
}
QWidget#horizontal_line {
min-height: 2px;
max-height: 2px;
background-color: rgba(42, 49, 157, 0.15);
border: none;
}
'''

Expand All @@ -2247,32 +2264,51 @@ def __init__(self, source: Source, controller: Controller) -> None:
self.controller = controller

# Set css id
self.setObjectName('replybox')
self.setObjectName('replybox_holder')

# Set styles
self.setStyleSheet(self.CSS)

# Set layout
layout = QVBoxLayout()
self.setLayout(layout)
main_layout = QVBoxLayout()
self.setLayout(main_layout)

# Set margins
layout.setContentsMargins(0, 0, 0, 0)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)

# Create widgets
self.text_edit = QTextEdit()
# Create top horizontal line
horizontal_line = QWidget()
horizontal_line.setObjectName('horizontal_line')

# Create replybox
self.replybox = QWidget()
self.replybox.setObjectName('replybox')
replybox_layout = QHBoxLayout(self.replybox)
replybox_layout.setContentsMargins(0, 0, 0, 0)
replybox_layout.setSpacing(0)

# Create reply text box
self.text_edit = QPlainTextEdit()
self.text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.text_edit.setPlaceholderText("Compose a reply to %s" %
self.source.journalist_designation)

# Create reply send button (airplane)
self.send_button = QPushButton()
self.send_button.clicked.connect(self.send_reply)
self.send_button.setMaximumSize(40, 40)
button_pixmap = load_image('send.png')
button_pixmap = load_image('send.svg')
button_icon = QIcon(button_pixmap)
self.send_button.setIcon(button_icon)
self.send_button.setIconSize(button_pixmap.rect().size())
self.send_button.setIconSize(QSize(56.5, 47))

# Add widgets to replybox
replybox_layout.addWidget(self.text_edit)
replybox_layout.addWidget(self.send_button, alignment=Qt.AlignBottom)

# Add widgets
layout.addWidget(self.text_edit)
layout.addWidget(self.send_button, alignment=Qt.AlignRight)
main_layout.addWidget(horizontal_line)
main_layout.addWidget(self.replybox)

# Determine whether or not this widget should be enabled
if self.controller.is_authenticated:
Expand All @@ -2286,11 +2322,13 @@ def __init__(self, source: Source, controller: Controller) -> None:
def enable(self):
self.text_edit.clear()
self.text_edit.setEnabled(True)
self.replybox.setEnabled(True)
self.send_button.show()

def disable(self):
self.text_edit.setText(_('You need to log in to send replies.'))
self.text_edit.setPlainText(_('You need to log in to send replies.'))
self.text_edit.setEnabled(False)
self.replybox.setEnabled(False)
self.send_button.hide()

def send_reply(self) -> None:
Expand Down
Binary file removed securedrop_client/resources/images/send.png
Binary file not shown.
3 changes: 3 additions & 0 deletions securedrop_client/resources/images/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2153,6 +2153,39 @@ def test_ReplyBoxWidget_init_no_auth(mocker):
assert rb.send_button.isHidden()


def test_ReplyBoxWidget_placeholder_show_currently_selected_source(mocker):
"""
Ensure placeholder of replybox "Compose a reply to [source designation]" displays the
designation for the correct source. #sanity-check
"""
controller = mocker.MagicMock()
sl = SourceList()
sl.setup(controller)

source_1 = factory.Source()
source_1.journalist_designation = "source one"
source_2 = factory.Source()
source_2.journalist_designation = "source two"

# add sources to sources list
sl.update([source_1, source_2])

source_1_item = sl.item(0)
source_2_item = sl.item(1)

# select source 1
sl.setCurrentItem(source_1_item)
assert sl.currentItem() == source_1_item

# select source other source
sl.setCurrentItem(source_2_item)
assert sl.currentItem() == source_2_item

selected_source = sl.itemWidget(sl.currentItem()).source
rb = ReplyBoxWidget(selected_source, controller)
assert rb.text_edit.placeholderText().find(source_2.journalist_designation) != -1


def test_ReplyBoxWidget_send_reply(mocker):
"""
Ensure sending a reply from the reply box emits signal, clears text box, and sends the reply
Expand Down