diff --git a/soitool/soi.py b/soitool/soi.py index 5a177c63d81263db229c57a4e43f83aa79170910..5f5585817db26df29453cf05f21dfa8cae584647 100644 --- a/soitool/soi.py +++ b/soitool/soi.py @@ -11,7 +11,6 @@ from rectpack import ( skyline, guillotine, ) -from soitool.modules.module_table import TableModule # functions to sort modules by different criteria @@ -158,6 +157,10 @@ class SOI: Currently the following are implemented: 'none', 'area', 'width', 'height'. Please refer to the STRING_TO_ALGORITHM_INITIAL_SORT variable. + modules : list of modules + initial modules of SOI + attachments : list of attachment modules + initial attachment modules of SOI """ @@ -207,6 +210,8 @@ class SOI: algorithm_bin="BFF", algorithm_pack="MaxRectsBl", algorithm_sort="area", + modules=[], + attachments=[], ): # populate date-related arguments if they are not supplied by the user @@ -230,6 +235,8 @@ class SOI: self.classification = classification self.orientation = orientation self.placement_strategy = placement_strategy + self.modules = modules + self.attachments = attachments # the following propertiese are relevant when self.placement_strategy # is "auto". They are used by rectpack @@ -240,34 +247,6 @@ class SOI: # functions to call after reorganization self.reorganization_listeners = [] - # NOTE - # * test modules, just to have something show up on screen - # * not valid until reorganize has been run, as the positions must be - # updated - self.modules = [ - { - "widget": TableModule(), - "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell1"}, - }, - { - "widget": TableModule(), - "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell2"}, - }, - { - "widget": TableModule(), - "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell3"}, - }, - ] - - # NOTE - # test attachments, just to have something show up on screen - self.attachments = [ - { - "widget": TableModule(), - "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell1"}, - } - ] - self.reorganize() def add_reorganization_listener(self, function): diff --git a/soitool/soi_workspace_widget.py b/soitool/soi_workspace_widget.py index 76b5531b35690ce19984de8cdd938edd232b8bb6..4c00e20aaaa6232bf75b7364505741b7bbdb8714 100644 --- a/soitool/soi_workspace_widget.py +++ b/soitool/soi_workspace_widget.py @@ -3,12 +3,18 @@ Meant for use inside of tabs in our program. """ -from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, \ - QLabel +from PySide2.QtWidgets import ( + QWidget, + QHBoxLayout, + QVBoxLayout, + QPushButton, + QLabel, +) from soitool.soi import SOI, ModuleType from soitool.module_list import ModuleList from soitool.inline_editable_soi_view import InlineEditableSOIView from soitool.setup_settings import Setup +from soitool.modules.module_table import TableModule class SOIWorkspaceWidget(QWidget): @@ -21,7 +27,39 @@ class SOIWorkspaceWidget(QWidget): def __init__(self): super().__init__() - self.soi = SOI() + # NOTE + # * test modules, just to have something show up on screen + # * not valid until reorganize has been run, as the positions must be + # updated + initial_modules = [ + { + "widget": TableModule(), + "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell1"}, + }, + { + "widget": TableModule(), + "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell2"}, + }, + { + "widget": TableModule(), + "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell3"}, + }, + ] + + # NOTE + # test attachments, just to have something show up on screen + initial_attachments = [ + { + "widget": TableModule(), + "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell1"}, + } + ] + + self.soi = SOI( + modules=initial_modules, attachments=initial_attachments + ) + + self.soi.reorganize() self.layout_wrapper = QHBoxLayout() self.layout_sidebar = QVBoxLayout() diff --git a/test/test_soi.py b/test/test_soi.py new file mode 100644 index 0000000000000000000000000000000000000000..c065e595d82ba3831839a2d823be66b31a314e15 --- /dev/null +++ b/test/test_soi.py @@ -0,0 +1,145 @@ +"""Test the soi module. + +To view the SOI during manual testing the following snippet is useful: + +```python +self.soi.reorganize() +view = InlineEditableSOIView(self.soi) +view.show() +app.exec_() +``` +""" + +import unittest +from PySide2.QtWidgets import QApplication, QWidget +from PySide2 import QtGui +from PySide2.QtGui import QPalette, QColor +from soitool.soi import SOI +from soitool.modules.module_base import ModuleBase + +if isinstance(QtGui.qApp, type(None)): + app = QApplication([]) +else: + app = QtGui.qApp + + +class Meta(type(ModuleBase), type(QWidget)): + """Used as a metaclass to enable multiple inheritance.""" + + +class TestModule(ModuleBase, QWidget, metaclass=Meta): + """A simple module of given width, height and color for testing.""" + + def __init__(self, color, width, height, *args, **kwargs): + super(TestModule, self).__init__(*args, **kwargs) + self.setAutoFillBackground(True) + palette = self.palette() + palette.setColor(QPalette.Window, QColor(color)) + self.setPalette(palette) + self.setGeometry(0, 0, width, height) + + def get_size(self): + """Override.""" + size = self.size() + return size.width(), size.height() + + def set_pos(self, pos): + """Override.""" + self.move(pos) + + def render_onto_pdf(self): + """Not used.""" + raise NotImplementedError() + + +# The modules below have sizes that make the ideal for testing. +# Sorting them by width should yield +# 1. wide_module +# 2. big_module +# 3. tall_module +# Sorting them by height should yield +# 1. tall_module +# 2. big_module +# 3. wide_module +# And sorting them by area should yield +# 1. big_module +# 2. tall_module +# 3. wide_module +# Note that when sorting by area the two modules 'tall_module' and +# 'wide_module' have the same area, but 'tall_module' should come first +# because sorting should be stable +# https://en.wikipedia.org/wiki/Category:Stable_sorts +# FIXME the only meta information necessary here is "name", if we assume +# automatic sorting.. +TEST_MODULES = [ + { + "widget": TestModule("red", 100, 400), + "meta": {"x": 0, "y": 0, "page": 1, "name": "tall_module"}, + }, + { + "widget": TestModule("blue", 400, 100), + "meta": {"x": 0, "y": 0, "page": 1, "name": "wide_module"}, + }, + { + "widget": TestModule("orange", 300, 300), + "meta": {"x": 0, "y": 0, "page": 1, "name": "big_module"}, + }, +] + + +class TestSOI(unittest.TestCase): + """TestCase for the SOI class.""" + + def setUp(self): + """Prepare SOI to test on.""" + self.soi = SOI( + modules=TEST_MODULES, + attachments=TEST_MODULES, + algorithm_sort="none", + ) + + def sort_modules_and_assert_order(self, sorting_algorithm, expected_order): + """Sort the SOI modules using algorithm and expect order. + + This function was created to reduce code-duplication only. + + Parameters + ---------- + sorting_algorithm : str + name of sorting algorithm to pass to the SOI + expected_order : list of string names + names of modules in the expected order + """ + self.soi.algorithm_sort = sorting_algorithm + self.soi.sort_modules() + + self.assertEqual( + expected_order, + [module["meta"]["name"] for module in self.soi.modules], + f"Modules should have expected order when sorting algorithm is " + "set to '{sorting_algorithm}'", + ) + + def test_sort_modules_none(self): + """Test sort algorithm 'none'.""" + self.sort_modules_and_assert_order( + "none", ["tall_module", "wide_module", "big_module"] + ) + + def test_sort_modules_width(self): + """Test sort algorithm 'width'.""" + self.sort_modules_and_assert_order( + "width", ["wide_module", "big_module", "tall_module"] + ) + + def test_sort_modules_height(self): + """Test sort algorithm 'height'.""" + self.sort_modules_and_assert_order( + "height", ["tall_module", "big_module", "wide_module"] + ) + + def test_sort_modules_area(self): + """Test sort algorithm 'area'.""" + self.sort_modules_and_assert_order( + "area", ["big_module", "tall_module", "wide_module"] + )