From 443a15f22fcd64c2a5e9d34fe55af74af948288d Mon Sep 17 00:00:00 2001 From: Allie Crevier Date: Tue, 1 Dec 2020 18:43:01 -0800 Subject: [PATCH] resizable preview --- securedrop_client/gui/__init__.py | 7 ++ securedrop_client/gui/widgets.py | 81 +++++++++++++++----- securedrop_client/resources/css/sdclient.css | 4 +- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/securedrop_client/gui/__init__.py b/securedrop_client/gui/__init__.py index 3fd4c9d4e4..7c89685766 100644 --- a/securedrop_client/gui/__init__.py +++ b/securedrop_client/gui/__init__.py @@ -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 = "", @@ -168,6 +171,7 @@ 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: @@ -175,6 +179,9 @@ def setText(self, text: str) -> None: 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 diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py index 51aca64a52..d6b491f254 100644 --- a/securedrop_client/gui/widgets.py +++ b/securedrop_client/gui/widgets.py @@ -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. @@ -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__() @@ -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) @@ -876,7 +885,10 @@ 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()) @@ -905,6 +917,7 @@ 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. @@ -912,7 +925,12 @@ def schedule_source_management(slice_size=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) @@ -975,6 +993,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. @@ -983,9 +1023,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 @@ -995,7 +1032,13 @@ 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 @@ -1003,15 +1046,14 @@ def __init__(self, controller: Controller, source: Source, source_selected_signa 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() @@ -1021,14 +1063,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") @@ -1056,9 +1094,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) @@ -1076,6 +1114,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. @@ -1117,6 +1160,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: @@ -2886,7 +2930,6 @@ class ConversationScrollArea(QScrollArea): def __init__(self): super().__init__() - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setWidgetResizable(True) self.setObjectName("ConversationScrollArea") diff --git a/securedrop_client/resources/css/sdclient.css b/securedrop_client/resources/css/sdclient.css index 8bb7ec9c4c..6678663767 100644 --- a/securedrop_client/resources/css/sdclient.css +++ b/securedrop_client/resources/css/sdclient.css @@ -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; } @@ -191,7 +191,7 @@ QListView#SourceList::item:selected { } QListView#SourceList::item:hover { - border: 500px solid #f9f9ff; + background-color: #f9f9ff; } #SourceWidget_container {