Skip to content

Commit

Permalink
Merge pull request #680 from freedomofpress/disable_refresh
Browse files Browse the repository at this point in the history
Disable refresh
  • Loading branch information
rmol authored Jan 10, 2020
2 parents 3ef6f68 + cec10f1 commit f1facac
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 46 deletions.
13 changes: 9 additions & 4 deletions securedrop_client/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from PyQt5.QtWidgets import QLabel, QHBoxLayout, QPushButton, QWidget
from PyQt5.QtCore import QSize, Qt

from securedrop_client.resources import load_svg, load_icon, load_toggle_icon
from securedrop_client.resources import load_svg, load_icon


class SvgToggleButton(QPushButton):
Expand Down Expand Up @@ -54,7 +54,7 @@ def __init__(self, on: str, off: str, svg_size: str = None):
layout.setSpacing(0)

# Add SVG icon and set its size
self.icon = load_toggle_icon(on=on, off=off)
self.icon = load_icon(normal=on, normal_off=off)
self.setIcon(self.icon)
self.setIconSize(svg_size) if svg_size else self.setIconSize(QSize())

Expand All @@ -68,7 +68,7 @@ def disable(self) -> None:
self.setEnabled(False)

def set_icon(self, on: str, off: str) -> None:
self.icon = load_toggle_icon(on=on, off=off)
self.icon = load_icon(normal=on, normal_off=off)
self.setIcon(self.icon)


Expand Down Expand Up @@ -110,7 +110,12 @@ def __init__(
layout.setSpacing(0)

# Add SVG icon and set its size
self.icon = load_icon(normal=normal, disabled=disabled, active=active, selected=selected)
self.icon = load_icon(
normal=normal,
disabled=disabled,
active=active,
selected=selected,
disabled_off=disabled)
self.setIcon(self.icon)
self.setIconSize(svg_size) if svg_size else self.setIconSize(QSize())

Expand Down
16 changes: 11 additions & 5 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ def __init__(self):
selected='refresh.svg',
svg_size=QSize(16, 16))

self.active = False

# Set css id
self.setObjectName('refresh_button')

Expand All @@ -231,14 +233,17 @@ def setup(self, controller):
self.controller.sync_events.connect(self._on_refresh_complete)

def _on_clicked(self):
if self.active:
return

self.controller.sync_api(manual_refresh=True)

# This is a temporary solution for showing the icon as active for the entire duration of a
# refresh, rather than for just the duration of a click. The icon image will be replaced
# when the controller tells us the refresh has finished. A cleaner solution would be to
# store and update our own icon mode so we don't have to reload any images.
self.setIcon(load_icon(
normal='refresh_active.svg',
disabled='refresh_offline.svg'))
self.setIcon(load_icon(normal='refresh_active.svg', disabled='refresh_offline.svg'))
self.active = True

def _on_refresh_complete(self, data):
if (data == 'synced'):
Expand All @@ -247,9 +252,11 @@ def _on_refresh_complete(self, data):
disabled='refresh_offline.svg',
active='refresh_active.svg',
selected='refresh.svg'))
self.active = False

def enable(self):
self.setEnabled(True)
self.active is False

def disable(self):
self.setEnabled(False)
Expand Down Expand Up @@ -2669,8 +2676,7 @@ def focusOutEvent(self, e):
def set_logged_in(self):
self.setEnabled(True)
source_name = "<strong><font color=\"#24276d\">{}</font></strong>".format(
self.source.journalist_designation
)
self.source.journalist_designation)
placeholder = _("Compose a reply to ") + source_name
self.placeholder.setText(placeholder)
self.placeholder.adjustSize()
Expand Down
65 changes: 34 additions & 31 deletions securedrop_client/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,37 +45,20 @@ def load_font(font_folder_name: str) -> None:
QFontDatabase.addApplicationFont(directory + '/' + filename)


def load_toggle_icon(on: str, off: str) -> QIcon:
def load_icon(
normal: str,
disabled: str = None,
active: str = None,
selected: str = None,
normal_off: str = None,
disabled_off: str = None,
active_off: str = None,
selected_off: str = None,
) -> QIcon:
"""
Add the contents of Scalable Vector Graphics (SVG) files provided for associated icon states,
see https://doc.qt.io/qt-5/qicon.html#State-enum.
Parameters
----------
on: str
file name to the on-state image
off: str
file name to the on-state image
Returns
-------
QIcon
The icon that displays the contents of the SVG files.
"""

icon = QIcon()

icon.addFile(path(on), state=QIcon.On)
icon.addFile(path(off), state=QIcon.Off)

return icon


def load_icon(normal: str, disabled: str = None, active: str = None, selected: str = None) -> QIcon:
"""
Add the contents of Scalable Vector Graphics (SVG) files provided for associated icon modes,
see https://doc.qt.io/qt-5/qicon.html#Mode-enum.
Add the contents of Scalable Vector Graphics (SVG) files provided for associated icon modes and
states, see https://doc.qt.io/qt-5/qicon.html#Mode-enum. If the widget containing this icon is
set to checkable, then the *_off states will be displayed.
Parameters
----------
Expand All @@ -87,6 +70,14 @@ def load_icon(normal: str, disabled: str = None, active: str = None, selected: s
The name of the SVG file to add to the icon for QIcon.Active mode.
selected: str, optional
The name of the SVG file to add to the icon for QIcon.Selected mode.
normal_off: str
The name of the SVG file to add to the icon for QIcon.Normal mode.
disabled_off: str or None, optional
The name of the SVG file to add to the icon for QIcon.Disabled mode.
active_off: str, optional
The name of the SVG file to add to the icon for QIcon.Active mode.
selected_off: str, optional
The name of the SVG file to add to the icon for QIcon.Selected mode.
Returns
-------
Expand All @@ -100,14 +91,26 @@ def load_icon(normal: str, disabled: str = None, active: str = None, selected: s
icon.addFile(path(normal), mode=QIcon.Normal, state=QIcon.On)

if disabled:
icon.addFile(path(disabled), mode=QIcon.Disabled, state=QIcon.Off)
icon.addFile(path(disabled), mode=QIcon.Disabled, state=QIcon.On)

if active:
icon.addFile(path(active), mode=QIcon.Active, state=QIcon.On)

if selected:
icon.addFile(path(selected), mode=QIcon.Selected, state=QIcon.On)

if normal_off:
icon.addFile(path(normal_off), mode=QIcon.Normal, state=QIcon.Off)

if disabled_off:
icon.addFile(path(disabled_off), mode=QIcon.Disabled, state=QIcon.Off)

if active_off:
icon.addFile(path(active_off), mode=QIcon.Active, state=QIcon.Off)

if selected_off:
icon.addFile(path(selected_off), mode=QIcon.Selected, state=QIcon.Off)

return icon


Expand Down
10 changes: 5 additions & 5 deletions tests/gui/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ def test_SvgToggleButton_init(mocker):
"""
svg_size = QSize(1, 1)
icon = mocker.MagicMock()
load_toggle_icon_fn = mocker.patch('securedrop_client.gui.load_toggle_icon', return_value=icon)
load_icon_fn = mocker.patch('securedrop_client.gui.load_icon', return_value=icon)
setIcon_fn = mocker.patch('securedrop_client.gui.SvgToggleButton.setIcon')
setIconSize_fn = mocker.patch('securedrop_client.gui.SvgToggleButton.setIconSize')

stb = SvgToggleButton(on='mock_on', off='mock_off', svg_size=svg_size)

assert stb.isCheckable() is True
load_toggle_icon_fn.assert_called_once_with(on='mock_on', off='mock_off')
load_icon_fn.assert_called_once_with(normal='mock_on', normal_off='mock_off')
setIcon_fn.assert_called_once_with(icon)
setIconSize_fn.assert_called_once_with(svg_size)

Expand Down Expand Up @@ -68,12 +68,12 @@ def test_SvgToggleButton_set_icon(mocker):
"""
setIcon_fn = mocker.patch('securedrop_client.gui.SvgToggleButton.setIcon')
icon = mocker.MagicMock()
load_toggle_icon_fn = mocker.patch('securedrop_client.gui.load_toggle_icon', return_value=icon)
load_icon_fn = mocker.patch('securedrop_client.gui.load_icon', return_value=icon)
stb = SvgToggleButton(on='mock_on', off='mock_off')

stb.set_icon(on='mock_on', off='mock_off')

load_toggle_icon_fn.assert_called_with(on='mock_on', off='mock_off')
load_icon_fn.assert_called_with(normal='mock_on', normal_off='mock_off')
setIcon_fn.assert_called_with(icon)
assert stb.icon == icon

Expand All @@ -93,7 +93,7 @@ def test_SvgPushButton_init(mocker):

assert spb.isCheckable() is False
load_icon_fn.assert_called_once_with(
normal='mock1', disabled='mock2', active='mock3', selected='mock4')
normal='mock1', disabled='mock2', active='mock3', selected='mock4', disabled_off='mock2')
setIcon_fn.assert_called_once_with(icon)
setIconSize_fn.assert_called_once_with(svg_size)

Expand Down
24 changes: 24 additions & 0 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,25 +178,49 @@ def test_RefreshButton_on_clicked(mocker):
rb.controller.sync_api.assert_called_once_with(manual_refresh=True)


def test_RefreshButton_on_clicked_while_active(mocker):
"""
When refresh button is clicked while active, sync_api should not be called.
"""
rb = RefreshButton()
rb.active = True
rb.controller = mocker.MagicMock()

rb._on_clicked()

rb.controller.sync_api.assert_not_called()


def test_RefreshButton_on_refresh_complete(mocker):
"""
Make sure we are enabled after a refresh completes.
"""
rb = RefreshButton()
rb._on_refresh_complete('synced')
assert rb.isEnabled()
assert rb.active is False


def test_RefreshButton_enable(mocker):
rb = RefreshButton()
rb.enable()
assert rb.isEnabled()
assert rb.active is False


def test_RefreshButton_disable(mocker):
rb = RefreshButton()
rb.disable()
assert not rb.isEnabled()
assert rb.active is False


def test_RefreshButton_disable_while_active(mocker):
rb = RefreshButton()
rb.active = True
rb.disable()
assert not rb.isEnabled()
assert rb.active is True


def test_ErrorStatusBar_clear_error_status(mocker):
Expand Down
10 changes: 9 additions & 1 deletion tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ def test_load_icon():
"""
Check the load_icon function returns the expected QIcon object.
"""
result = securedrop_client.resources.load_icon('icon')
result = securedrop_client.resources.load_icon(
'normal_mock',
'disabled_mock',
'active_mock',
'selected_mock',
'normal_off_mock',
'disabled_off_mock',
'active_off_mock',
'selected_off_mock')
assert isinstance(result, QIcon)


Expand Down

0 comments on commit f1facac

Please sign in to comment.