Skip to content
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
39 changes: 39 additions & 0 deletions qtapputils/testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © QtAppUtils Project Contributors
# https://github.com/geo-stack/qtapputils
#
# This file is part of QtAppUtils.
# Licensed under the terms of the MIT License.
# -----------------------------------------------------------------------------

"""
Helper functions for testing.
"""

from qtpy.QtGui import QIcon
from qtpy.QtCore import QSize


def icons_are_equal(icon1: QIcon, icon2: QIcon, size: QSize = QSize(16, 16)):
"""
Return True if two QIcon objects have identical image data at
the given size.

Parameters
----------
icon1 : QIcon
The first icon to compare.
icon2 : QIcon
The second icon to compare.
size : QSize, optional
The size at which to compare the icons (default is 16x16).

Returns
-------
bool
True if the icons look the same at the specified size, False otherwise.
"""
pm1 = icon1.pixmap(size)
pm2 = icon2.pixmap(size)
return pm1.toImage() == pm2.toImage()
74 changes: 74 additions & 0 deletions qtapputils/widgets/buttons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © QtAppUtils Project Contributors
# https://github.com/geo-stack/qtapputils
#
# This file is part of QtAppUtils.
# Licensed under the terms of the MIT License.
# -----------------------------------------------------------------------------


# ---- Third party imports
from qtpy.QtCore import Signal
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QToolButton, QWidget


class MultiStateToolButton(QToolButton):
"""
A QToolButton that cycles through a list of icons each time it is clicked.

Parameters
----------
icons : list of QIcon
The list of icons to cycle through.
parent : QWidget, optional
The parent widget.
index : int, optional
The index of the starting icon (default is 0).

Signals
-------
sig_index_changed : Signal(int)
Signal emitted with the new index whenever the current state changes.
"""

sig_index_changed = Signal(int)

def __init__(
self, icons: list[QIcon],
parent: QWidget = None,
index: int = 0
):
super().__init__(parent)

self.setAutoRaise(True)
self.setCheckable(False)

self._icons = icons
self._current_index = index

self.clicked.connect(self._handle_clicked)
self._update_icon()

def current_index(self):
return self._current_index

def set_current_index(self, index: int):
if index >= len(self._icons):
index = 0
elif index < 0:
index = len(self._icons) - 1

if index == self._current_index:
return

self._current_index = index
self._update_icon()
self.sig_index_changed.emit(index)

def _update_icon(self):
self.setIcon(self._icons[self._current_index])

def _handle_clicked(self):
self.set_current_index(self._current_index + 1)
2 changes: 1 addition & 1 deletion qtapputils/widgets/path.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © QtAppUtils Project Contributors
# https://github.com/jnsebgosselin/qtapputils
# https://github.com/geo-stack/qtapputils
#
# This file is part of QtAppUtils.
# Licensed under the terms of the MIT License.
Expand Down
96 changes: 96 additions & 0 deletions qtapputils/widgets/tests/test_buttons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © QtAppUtils Project Contributors
# https://github.com/geo-stack/qtapputils
#
# This file is part of QtAppUtils.
# Licensed under the terms of the MIT License.
# -----------------------------------------------------------------------------

import pytest
from qtpy.QtGui import QIcon, QPixmap
from qtpy.QtCore import Qt
from qtpy.QtTest import QSignalSpy

from qtapputils.widgets.buttons import MultiStateToolButton
from qtapputils.testing import icons_are_equal


@pytest.fixture
def icons():
# Create dummy QIcons for testing
pixmap1 = QPixmap(16, 16)
pixmap1.fill(Qt.red)
pixmap2 = QPixmap(16, 16)
pixmap2.fill(Qt.green)
pixmap3 = QPixmap(16, 16)
pixmap3.fill(Qt.blue)
return [QIcon(pixmap1), QIcon(pixmap2), QIcon(pixmap3)]


@pytest.fixture
def button(icons, qtbot):

btn = MultiStateToolButton(icons)
qtbot.addWidget(btn)
btn.show()
qtbot.waitUntil(btn.isVisible)

assert btn.current_index() == 0
assert icons_are_equal(btn.icon(), icons[0])

return btn


def test_cycle_icons(button, qtbot, icons):
"""Test icon cycles forward with clicks and wraps around."""
signal_spy = QSignalSpy(button.sig_index_changed)

qtbot.mouseClick(button, Qt.LeftButton)

assert len(signal_spy) == 1
assert signal_spy[-1] == [1]
assert icons_are_equal(button.icon(), icons[1])

qtbot.mouseClick(button, Qt.LeftButton)

assert len(signal_spy) == 2
assert signal_spy[-1] == [2]
assert icons_are_equal(button.icon(), icons[2])

# Should wrap back to 0.
qtbot.mouseClick(button, Qt.LeftButton)

assert len(signal_spy) == 3
assert signal_spy[-1] == [0]
assert icons_are_equal(button.icon(), icons[0])


def test_set_index(button, qtbot, icons):
"""Test icon is set as expected when index is set programattically."""
signal_spy = QSignalSpy(button.sig_index_changed)

# Should wrap back to len(icons) - 1.
button.set_current_index(-1)

assert len(signal_spy) == 1
assert signal_spy[-1] == [2]
assert icons_are_equal(button.icon(), icons[2])

# Should wrap back to 0.
button.set_current_index(100)

assert len(signal_spy) == 2
assert signal_spy[-1] == [0]
assert icons_are_equal(button.icon(), icons[0])

# Should do nothing.
button.set_current_index(100)

assert len(signal_spy) == 2
assert signal_spy[-1] == [0]
assert icons_are_equal(button.icon(), icons[0])


if __name__ == '__main__':
pytest.main(['-x', __file__, '-vv', '-rw'])