Skip to content

Commit

Permalink
resizable preview
Browse files Browse the repository at this point in the history
  • Loading branch information
Allie Crevier committed Feb 1, 2021
1 parent 562bfaa commit dba9711
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 58 deletions.
7 changes: 7 additions & 0 deletions securedrop_client/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ def update_image(self, filename: str, svg_size: str = None) -> None:


class SecureQLabel(QLabel):

MAX_PREVIEW_LENGTH = 200

def __init__(
self,
text: str = "",
Expand All @@ -168,13 +171,17 @@ def __init__(
def setText(self, text: str) -> None:
text = text.strip()
self.setTextFormat(Qt.PlainText)
self.preview_text = text[: self.MAX_PREVIEW_LENGTH]
elided_text = self.get_elided_text(text)
self.elided = True if elided_text != text else False
if self.elided and self.with_tooltip:
tooltip_label = SecureQLabel(text)
self.setToolTip(tooltip_label.text())
super().setText(elided_text)

def refresh_preview_text(self):
self.setText(self.preview_text)

def get_elided_text(self, full_text: str) -> str:
if not self.max_length:
return full_text
Expand Down
79 changes: 58 additions & 21 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
from typing import Dict, List, Optional, Union # noqa: F401
from uuid import uuid4

import arrow
import sqlalchemy.orm.exc
from PyQt5.QtCore import QEvent, QObject, QSize, Qt, QTimer, pyqtBoundSignal, pyqtSignal, pyqtSlot
from PyQt5.QtGui import (
QBrush,
Expand Down Expand Up @@ -63,6 +61,8 @@
QWidget,
)

import arrow
import sqlalchemy.orm.exc
from securedrop_client import __version__ as sd_version
from securedrop_client.db import (
DraftReply,
Expand Down Expand Up @@ -596,8 +596,8 @@ def __init__(self, parent: QObject):
self.view_layout.addWidget(self.empty_conversation_view)

# Add widgets to layout
self.layout.addWidget(self.source_list)
self.layout.addWidget(self.view_holder)
self.layout.addWidget(self.source_list, stretch=1)
self.layout.addWidget(self.view_holder, stretch=2)

# Note: We should not delete SourceConversationWrapper when its source is unselected. This
# is a temporary solution to keep copies of our objects since we do delete them.
Expand Down Expand Up @@ -802,8 +802,10 @@ class SourceList(QListWidget):
"""

NUM_SOURCES_TO_ADD_AT_A_TIME = 32
INITIAL_UPDATE_SCROLLBAR_WIDTH = 20

source_selected = pyqtSignal(str)
adjust_preview = pyqtSignal(int)

def __init__(self):
super().__init__()
Expand All @@ -815,12 +817,19 @@ def __init__(self):
layout = QVBoxLayout(self)
self.setLayout(layout)

# Disable horizontal scrollbar for SourceList widget
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

# Enable ordering.
self.setSortingEnabled(True)

# To hold references to SourceListWidgetItem instances indexed by source UUID.
self.source_items = {}

def resizeEvent(self, event):
self.adjust_preview.emit(event.size().width())
super().resizeEvent(event)

def setup(self, controller):
self.controller = controller
self.controller.reply_succeeded.connect(self.set_snippet)
Expand Down Expand Up @@ -876,7 +885,7 @@ def update(self, sources: List[Source]) -> List[str]:
# Add widgets for new sources
for uuid in sources_to_add:
source_widget = SourceWidget(
self.controller, sources_to_add[uuid], self.source_selected
self.controller, sources_to_add[uuid], self.source_selected, self.adjust_preview
)
source_item = SourceListWidgetItem(self)
source_item.setSizeHint(source_widget.sizeHint())
Expand Down Expand Up @@ -905,14 +914,17 @@ def add_source(self, sources, slice_size=1):

def schedule_source_management(slice_size=slice_size):
if not sources:
self.adjust_preview.emit(self.width() - self.INITIAL_UPDATE_SCROLLBAR_WIDTH)
return

# Process the remaining "slice_size" number of sources.
sources_slice = sources[:slice_size]
for source in sources_slice:
try:
source_uuid = source.uuid
source_widget = SourceWidget(self.controller, source, self.source_selected)
source_widget = SourceWidget(
self.controller, source, self.source_selected, self.adjust_preview
)
source_item = SourceListWidgetItem(self)
source_item.setSizeHint(source_widget.sizeHint())
self.insertItem(0, source_item)
Expand Down Expand Up @@ -975,6 +987,28 @@ def set_snippet(self, source_uuid: str, collection_item_uuid: str, content: str)
source_widget.set_snippet(source_uuid, collection_item_uuid, content)


class SourcePreview(SecureQLabel):
PREVIEW_WIDTH_DIFFERENCE = 140

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

def adjust_preview(self, 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 scroll list widget, we have to override the normal resizing
logic.
"""
new_width = width - self.PREVIEW_WIDTH_DIFFERENCE
if self.width() == new_width:
return

self.setFixedWidth(new_width)
self.max_length = self.width()
self.refresh_preview_text()


class SourceWidget(QWidget):
"""
Used to display summary information about a source in the list view.
Expand All @@ -983,9 +1017,6 @@ class SourceWidget(QWidget):
TOP_MARGIN = 11
BOTTOM_MARGIN = 7
SIDE_MARGIN = 10
PREVIEW_WIDTH = 360
PREVIEW_WIDGET_WIDTH = 380
PREVIEW_WIDGET_HEIGHT = 22
SPACER = 14
BOTTOM_SPACER = 11
STAR_WIDTH = 20
Expand All @@ -995,23 +1026,28 @@ class SourceWidget(QWidget):
SOURCE_PREVIEW_CSS = load_css("source_preview.css")
SOURCE_TIMESTAMP_CSS = load_css("source_timestamp.css")

def __init__(self, controller: Controller, source: Source, source_selected_signal: pyqtSignal):
def __init__(
self,
controller: Controller,
source: Source,
source_selected_signal: pyqtSignal,
adjust_preview: pyqtSignal,
):
super().__init__()

self.controller = controller
self.controller.source_deleted.connect(self._on_source_deleted)
self.controller.source_deletion_failed.connect(self._on_source_deletion_failed)
self.controller.authentication_state.connect(self._on_authentication_changed)
source_selected_signal.connect(self._on_source_selected)
adjust_preview.connect(self._on_adjust_preview)

# Store source
self.source = source
self.seen = self.source.seen
self.source_uuid = self.source.uuid
self.last_updated = self.source.last_updated
self.selected = False

# Set cursor.
self.setCursor(QCursor(Qt.PointingHandCursor))

retain_space = self.sizePolicy()
Expand All @@ -1021,14 +1057,10 @@ def __init__(self, controller: Controller, source: Source, source_selected_signa
self.star.setFixedWidth(self.STAR_WIDTH)
self.name = QLabel()
self.name.setObjectName("SourceWidget_name")
self.preview = SecureQLabel(max_length=self.PREVIEW_WIDTH)
self.preview = SourcePreview()
self.preview.setObjectName("SourceWidget_preview")
self.preview.setFixedSize(QSize(self.PREVIEW_WIDGET_WIDTH, self.PREVIEW_WIDGET_HEIGHT))
self.waiting_delete_confirmation = QLabel("Deletion in progress")
self.waiting_delete_confirmation.setObjectName("SourceWidget_source_deleted")
self.waiting_delete_confirmation.setFixedSize(
QSize(self.PREVIEW_WIDGET_WIDTH, self.PREVIEW_WIDGET_HEIGHT)
)
self.waiting_delete_confirmation.hide()
self.paperclip = SvgLabel("paperclip.svg", QSize(11, 17)) # Set to size provided in the svg
self.paperclip.setObjectName("SourceWidget_paperclip")
Expand Down Expand Up @@ -1056,9 +1088,9 @@ def __init__(self, controller: Controller, source: Source, source_selected_signa
source_widget_layout.setSpacing(0)
source_widget_layout.setContentsMargins(0, self.TOP_MARGIN, 0, self.BOTTOM_MARGIN)
source_widget_layout.addWidget(self.star, 0, 0, 1, 1)
self.spacer_widget = QWidget()
self.spacer_widget.setFixedWidth(self.SPACER)
source_widget_layout.addWidget(self.spacer_widget, 0, 1, 1, 1)
self.spacer = QWidget()
self.spacer.setFixedWidth(self.SPACER)
source_widget_layout.addWidget(self.spacer, 0, 1, 1, 1)
source_widget_layout.addWidget(self.name, 0, 2, 1, 1)
source_widget_layout.addWidget(self.paperclip, 0, 3, 1, 1)
source_widget_layout.addWidget(self.preview, 1, 2, 1, 1, alignment=Qt.AlignLeft)
Expand All @@ -1076,6 +1108,11 @@ def __init__(self, controller: Controller, source: Source, source_selected_signa

self.update()

@pyqtSlot(int)
def _on_adjust_preview(self, width):
self.setFixedWidth(width)
self.preview.adjust_preview(width)

def update(self):
"""
Updates the displayed values with the current values from self.source.
Expand Down Expand Up @@ -1117,6 +1154,7 @@ def set_snippet(self, source_uuid: str, collection_uuid: str = None, content: st
content = str(last_activity)

self.preview.setText(content)
self.preview.adjust_preview(self.width())

def delete_source(self, event):
if self.controller.api is None:
Expand Down Expand Up @@ -2886,7 +2924,6 @@ class ConversationScrollArea(QScrollArea):
def __init__(self):
super().__init__()

self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setWidgetResizable(True)

self.setObjectName("ConversationScrollArea")
Expand Down
4 changes: 2 additions & 2 deletions securedrop_client/resources/css/sdclient.css
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ QListView#SourceList {
border: none;
show-decoration-selected: 0;
border-right: 3px solid #f3f5f9;
min-width: 500px;
min-width: 400px;
max-width: 540px;
}

Expand All @@ -191,7 +191,7 @@ QListView#SourceList::item:selected {
}

QListView#SourceList::item:hover {
border: 500px solid #f9f9ff;
background-color: #f9f9ff;
}

#SourceWidget_container {
Expand Down

0 comments on commit dba9711

Please sign in to comment.