Skip to content

Commit

Permalink
scale conversation widgets as window resizes
Browse files Browse the repository at this point in the history
  • Loading branch information
Allie Crevier committed Feb 2, 2021
1 parent 124812b commit 649cc95
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 370 deletions.
1 change: 1 addition & 0 deletions securedrop_client/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def __init__(self) -> None:

# Main Pane to display everything else
self.main_pane = QWidget()
self.setObjectName("MainWindow")
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
Expand Down
115 changes: 97 additions & 18 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,10 @@ class SpeechBubble(QWidget):
MESSAGE_CSS = load_css("speech_bubble_message.css")
STATUS_BAR_CSS = load_css("speech_bubble_status_bar.css")

WIDTH_TO_CONTAINER_WIDTH_RATIO = 5 / 9
MIN_WIDTH = 400
MIN_CONTAINER_WIDTH = 750

TOP_MARGIN = 28
BOTTOM_MARGIN = 10

Expand All @@ -1835,9 +1839,11 @@ def __init__(
update_signal,
download_error_signal,
index: int,
container_width: int,
failed_to_decrypt: bool = False,
) -> None:
super().__init__()

self.uuid = message_uuid
self.index = index
self.failed_to_decrypt = failed_to_decrypt
Expand Down Expand Up @@ -1866,7 +1872,6 @@ def __init__(

# Speech bubble
self.speech_bubble = QWidget()
self.speech_bubble.setObjectName("SpeechBubble_container")
speech_bubble_layout = QVBoxLayout()
self.speech_bubble.setLayout(speech_bubble_layout)
speech_bubble_layout.addWidget(self.message)
Expand All @@ -1893,12 +1898,24 @@ def __init__(
if self.failed_to_decrypt:
self.set_failed_to_decrypt_styles()

self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

# Connect signals to slots
update_signal.connect(self._update_text)
download_error_signal.connect(self._on_download_error)

self.adjust_width(container_width)

def adjust_width(self, container_width):
"""
This is a workaround to the workaround for https://bugreports.qt.io/browse/QTBUG-85498.
Since QLabels containing text with long strings that cannot be wrapped have to have a fixed
width in order to fit within the scrollarea widget, we have to override the normal resizing
logic.
"""
if container_width < self.MIN_CONTAINER_WIDTH:
self.speech_bubble.setFixedWidth(self.MIN_WIDTH)
else:
self.speech_bubble.setFixedWidth(container_width * self.WIDTH_TO_CONTAINER_WIDTH_RATIO)

@pyqtSlot(str, str, str)
def _update_text(self, source_uuid: str, uuid: str, text: str) -> None:
"""
Expand Down Expand Up @@ -1949,10 +1966,17 @@ def __init__(
update_signal,
download_error_signal,
index: int,
container_width: int,
failed_to_decrypt: bool = False,
) -> None:
super().__init__(
message_uuid, message, update_signal, download_error_signal, index, failed_to_decrypt
message_uuid,
message,
update_signal,
download_error_signal,
index,
container_width,
failed_to_decrypt,
)


Expand All @@ -1964,6 +1988,8 @@ class ReplyWidget(SpeechBubble):
MESSAGE_CSS = load_css("speech_bubble_message.css")
STATUS_BAR_CSS = load_css("speech_bubble_status_bar.css")

ERROR_BOTTOM_MARGIN = 20

def __init__(
self,
controller: Controller,
Expand All @@ -1975,12 +2001,19 @@ def __init__(
message_succeeded_signal,
message_failed_signal,
index: int,
container_width: int,
sender: User,
sender_is_current_user: bool,
failed_to_decrypt: bool = False,
) -> None:
super().__init__(
message_uuid, message, update_signal, download_error_signal, index, failed_to_decrypt
message_uuid,
message,
update_signal,
download_error_signal,
index,
container_width,
failed_to_decrypt,
)
self.controller = controller
self.status = reply_status
Expand All @@ -1991,7 +2024,7 @@ def __init__(

self.error = QWidget()
error_layout = QHBoxLayout()
error_layout.setContentsMargins(0, 0, 0, 0)
error_layout.setContentsMargins(0, 0, 0, self.ERROR_BOTTOM_MARGIN)
error_layout.setSpacing(4)
self.error.setLayout(error_layout)
error_message = SecureQLabel("Failed to send", wordwrap=False)
Expand All @@ -2000,9 +2033,12 @@ def __init__(
error_icon.setFixedWidth(12)
error_layout.addWidget(error_message)
error_layout.addWidget(error_icon)
retain_space = self.sizePolicy()
retain_space.setRetainSizeWhenHidden(True)
self.error.setSizePolicy(retain_space)
self.error.hide()

self.bubble_area_layout.addWidget(self.error)
self.bubble_area_layout.addWidget(self.error, alignment=Qt.AlignBottom)
self.sender_icon.show()

message_succeeded_signal.connect(self._on_reply_success)
Expand Down Expand Up @@ -2143,13 +2179,18 @@ class FileWidget(QWidget):
FILENAME_WIDTH_PX = 360
FILE_OPTIONS_LAYOUT_SPACING = 8

WIDTH_TO_CONTAINER_WIDTH_RATIO = 5 / 9
MIN_CONTAINER_WIDTH = 750
MIN_WIDTH = 400

def __init__(
self,
file_uuid: str,
controller: Controller,
file_ready_signal: pyqtBoundSignal,
file_missing: pyqtBoundSignal,
index: int,
container_width: int,
) -> None:
"""
Given some text and a reference to the controller, make something to display a file.
Expand All @@ -2162,6 +2203,8 @@ def __init__(
self.index = index
self.downloading = False

self.adjust_width(container_width)

self.setObjectName("FileWidget")
file_description_font = QFont()
file_description_font.setLetterSpacing(QFont.AbsoluteSpacing, self.FILE_FONT_SPACING)
Expand Down Expand Up @@ -2244,34 +2287,45 @@ def __init__(
layout.addWidget(self.file_options)
layout.addWidget(self.file_name)
layout.addWidget(self.no_file_name)
layout.addWidget(self.horizontal_line)
layout.addWidget(self.spacer)
layout.addWidget(self.horizontal_line)
layout.addWidget(self.file_size)

# Connect signals to slots
file_ready_signal.connect(self._on_file_downloaded, type=Qt.QueuedConnection)
file_missing.connect(self._on_file_missing, type=Qt.QueuedConnection)

def update_file_size(self):
try:
self.file_size.setText(humanize_filesize(self.file.size))
except Exception as e:
logger.error(f"Could not update file size on FileWidget: {e}")
self.file_size.setText("")
self.installEventFilter(self)

def adjust_width(self, container_width):
"""
This is a workaround to the workaround for https://bugreports.qt.io/browse/QTBUG-85498.
See comment in the adjust_width method for SpeechBubble.
"""
if container_width < self.MIN_CONTAINER_WIDTH:
self.setFixedWidth(self.MIN_WIDTH)
else:
self.setFixedWidth(container_width * self.WIDTH_TO_CONTAINER_WIDTH_RATIO)

def eventFilter(self, obj, event):
t = event.type()
if t == QEvent.MouseButtonPress:
if event.button() == Qt.LeftButton:
self._on_left_click()
# See https://github.com/freedomofpress/securedrop-client/issues/835
# for context on code below.
if t == QEvent.HoverEnter and not self.downloading:
elif t == QEvent.HoverEnter and not self.downloading:
self.download_button.setIcon(load_icon("download_file_hover.svg"))
elif t == QEvent.HoverLeave and not self.downloading:
self.download_button.setIcon(load_icon("download_file.svg"))

return QObject.event(obj, event)

def update_file_size(self):
try:
self.file_size.setText(humanize_filesize(self.file.size))
except Exception as e:
logger.error(f"Could not update file size on FileWidget: {e}")
self.file_size.setText("")

def _set_file_state(self):
if self.file.is_decrypted:
logger.debug("Changing file {} state to decrypted/downloaded".format(self.uuid))
Expand Down Expand Up @@ -2932,6 +2986,9 @@ def __init__(self):
# Create the scroll area's widget
conversation = QWidget()
conversation.setObjectName("ConversationScrollArea_conversation")
# The size policy for the scrollarea's widget needs a fixed height so that the speech
# bubbles are aligned at the top rather than spreading out to fill the height of the
# scrollarea.
conversation.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.conversation_layout = QVBoxLayout()
conversation.setLayout(self.conversation_layout)
Expand All @@ -2941,6 +2998,20 @@ def __init__(self):
# `conversation` is a child of this scroll area
self.setWidget(conversation)

def resizeEvent(self, event):
"""
This is a workaround to the workaround for https://bugreports.qt.io/browse/QTBUG-85498.
See comment in the adjust_width method for SpeechBubble.
"""
super().resizeEvent(event)
self.widget().setFixedWidth(event.size().width())

for widget in self.findChildren(FileWidget):
widget.adjust_width(self.widget().width())

for widget in self.findChildren(SpeechBubble):
widget.adjust_width(self.widget().width())

def add_widget_to_conversation(
self, index: int, widget: QWidget, alignment_flag: Qt.AlignmentFlag
) -> None:
Expand All @@ -2963,6 +3034,8 @@ class ConversationView(QWidget):

conversation_updated = pyqtSignal()

SCROLL_BAR_WIDTH = 15

def __init__(self, source_db_object: Source, controller: Controller):
super().__init__()

Expand All @@ -2973,6 +3046,8 @@ def __init__(self, source_db_object: Source, controller: Controller):
# To hold currently displayed messages.
self.current_messages = {} # type: Dict[str, QWidget]

self.setObjectName("ConversationView")

# Set layout
main_layout = QVBoxLayout()
self.setLayout(main_layout)
Expand Down Expand Up @@ -3077,6 +3152,7 @@ def add_file(self, file: File, index):
self.controller.file_ready,
self.controller.file_missing,
index,
self.scroll.widget().width(),
)
self.scroll.add_widget_to_conversation(index, conversation_item, Qt.AlignLeft)
self.current_messages[file.uuid] = conversation_item
Expand All @@ -3101,6 +3177,7 @@ def add_message(self, message: Message, index) -> None:
self.controller.message_ready,
self.controller.message_download_failed,
index,
self.scroll.widget().width(),
message.download_error is not None,
)
self.scroll.add_widget_to_conversation(index, conversation_item, Qt.AlignLeft)
Expand Down Expand Up @@ -3134,6 +3211,7 @@ def add_reply(self, reply: Union[DraftReply, Reply], sender: User, index: int) -
self.controller.reply_succeeded,
self.controller.reply_failed,
index,
self.scroll.widget().width(),
sender,
sender_is_current_user,
failed_to_decrypt=getattr(reply, "download_error", None) is not None,
Expand Down Expand Up @@ -3161,8 +3239,9 @@ def add_reply_from_reply_box(self, uuid: str, content: str) -> None:
self.controller.reply_succeeded,
self.controller.reply_failed,
index,
self.scroll.widget().width(),
self.controller.authenticated_user,
sender_is_current_user=True,
True,
)
self.scroll.add_widget_to_conversation(index, conversation_item, Qt.AlignRight)
self.current_messages[uuid] = conversation_item
Expand Down
2 changes: 1 addition & 1 deletion securedrop_client/resources/css/modal_dialog_button.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
height: 40px;
padding-left: 20px;
padding-right: 20px;
}
}
32 changes: 13 additions & 19 deletions securedrop_client/resources/css/sdclient.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* The main stylesheet for the SecureDrop Client application
*/

#MainWindow {
min-width: 1279px;
min-height: 600px;
}

#LoginDialog_form QLabel {
color: #fff;
font-family: 'Montserrat';
Expand Down Expand Up @@ -148,11 +153,14 @@
}

#MainView_view_holder {
min-width: 500;
border: none;
background-color: #f9f9ff;
}

#EmptyConversationView {
min-width: 640px;
}

#EmptyConversationView QLabel {
font-family: Montserrat;
font-weight: 400;
Expand All @@ -170,14 +178,6 @@
font-weight: 600;
}

#EmptyConversationView_no_sources QLabel#EmptyConversationView_instructions {
max-width: 600px;
}

#EmptyConversationView_no_source_selected QLabel#EmptyConversationView_instructions {
max-width: 520px;
}

QListView#SourceList {
border: none;
show-decoration-selected: 0;
Expand Down Expand Up @@ -323,11 +323,6 @@ QToolButton#SourceMenuButton::menu-indicator {
background: #f9f9ff;
}

#FileWidget {
min-width: 540px;
max-width: 540px;
}

#FileWidget_file_options {
min-width: 137px;
}
Expand Down Expand Up @@ -372,17 +367,16 @@ QLabel#FileWidget_file_size {
}

QWidget#FileWidget_horizontal_line {
min-width: 0px;
min-height: 2px;
max-height: 2px;
background-color: rgba(211, 216, 234, 0.45);
margin: 0px 8px 0px 8px;
}

#SpeechBubble_container {
min-width: 540px;
max-width: 540px;
min-height: 64px;
background-color: #fff;
#ConversationView {
min-width: 640px;
background-color: #00ff00;
}

#ReplyWidget_failed_to_send_text {
Expand Down
2 changes: 1 addition & 1 deletion securedrop_client/resources/css/sender_icon.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@
font-weight: 500;
font-size: 23px;
color: #fff;
}
}

0 comments on commit 649cc95

Please sign in to comment.