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

scale source list and preview as window resizes #1211

Merged
merged 1 commit into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) -> None:
self.setText(self.preview_text)

def get_elided_text(self, full_text: str) -> str:
if not self.max_length:
return full_text
Expand Down
76 changes: 57 additions & 19 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
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,13 +885,14 @@ 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())
self.insertItem(0, source_item)
self.setItemWidget(source_item, source_widget)
self.source_items[uuid] = source_item
self.adjust_preview.emit(self.width() - self.INITIAL_UPDATE_SCROLLBAR_WIDTH)

# Re-sort SourceList to make sure the most recently-updated sources appear at the top
self.sortItems(Qt.DescendingOrder)
Expand All @@ -905,14 +915,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 +988,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 +1018,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 +1027,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 +1058,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 +1089,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 +1109,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 +1155,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 +2925,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
Loading