diff --git a/soitool/modules/code_table_base.py b/soitool/modules/code_table_base.py index f00585f7f2fdd66083af9aaf3a2d3b8bdc1ccd87..4516e2c2c673a7bc2f4bd1deb8e0030e4e97839a 100644 --- a/soitool/modules/code_table_base.py +++ b/soitool/modules/code_table_base.py @@ -48,35 +48,48 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): # Resize table when headline changes self.cellChanged.connect( lambda: resize_table( - self, - resize_rows=False, - resize_columns=False, - has_headline=True, + self, rows=False, columns=False, has_headline=True, ) ) # If parameters are None, generate new table if size is None and data is None: self.generate_table() self.resizeColumnsToContents() - self.insert_headline(self.headline) + self.resizeRowsToContents() + self.insert_headline(self.start_headline) - # Resize height of rows and set size of window - resize_table(self, resize_columns=False, has_headline=True) + resize_table( + self, columns=False, rows=False, has_headline=True, + ) else: - self.setColumnCount(len(data[1])) - self.setRowCount(len(data) - 1) # - 1 to skip headline + self.code_length = data["code_length"] + self.space_interval = data["space_interval"] + self.space_amount = data["space_amount"] + self.code_character_type = data["code_character_type"] + + cells = data["cells"] + self.setColumnCount(len(cells[1])) + self.setRowCount(len(cells) - 1) # - 1 to skip headline # Set cell-items for i in range(self.rowCount()): for j in range(self.columnCount()): - item = QTableWidgetItem(data[i + 1][j]) # +1 skip headline - item.setTextAlignment(Qt.AlignCenter) + item = QTableWidgetItem( + cells[i + 1][j] + ) # +1 skip headline + if j == 2 and self.type == AUTHENTICATIONBOARD_MODULE: + item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) + else: + item.setTextAlignment(Qt.AlignCenter) self.setItem(i, j, item) self.resizeColumnsToContents() - self.insert_headline(data[0]) + self.resizeRowsToContents() + self.insert_headline(cells[0]) - resize_table(self, resize_columns=False, has_headline=True) + resize_table( + self, columns=False, rows=False, has_headline=True, + ) self.setFixedWidth(size["width"]) self.setFixedHeight(size["height"]) @@ -112,13 +125,13 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): Parameters ---------- text : string, optional - The headline text, self.headline is used if None, + The headline text, self.start_headline is used if None, by default None. """ - headline = self.headline if text is None else text + headline = self.start_headline if text is None else text item_headline = QTableWidgetItem(headline) - item_headline.setTextAlignment(Qt.AlignHCenter) + item_headline.setTextAlignment(Qt.AlignCenter) item_headline.setFont(HEADLINE_FONT) self.insertRow(0) @@ -200,16 +213,26 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): List[0] contains headline, list[x][y] represents value of row x, column y. """ - data = [] + cells = [] item_headline = self.item(0, 0) if item_headline is not None: - data.append(item_headline.text()) + cells.append(item_headline.text()) + else: + cells.append("") for i in range(1, self.rowCount()): row = [] for j in range(self.columnCount()): row.append(self.item(i, j).text()) - data.append(row) + cells.append(row) + + data = { + "cells": cells, + "code_length": self.code_length, + "space_interval": self.space_interval, + "space_amount": self.space_amount, + "code_character_type": self.code_character_type, + } return data diff --git a/soitool/modules/module_authentication_board.py b/soitool/modules/module_authentication_board.py index e1dc99965b92e0c85d5c973da40d8a504b96c4e3..6c6e76cb694b5d66df874443c751ff84789089c5 100644 --- a/soitool/modules/module_authentication_board.py +++ b/soitool/modules/module_authentication_board.py @@ -1,5 +1,6 @@ """Module containing SOI-module 'Autentiseringstavle'.""" import string +from secrets import choice from PySide2.QtWidgets import QTableWidgetItem from PySide2 import QtGui from PySide2.QtCore import Qt @@ -39,14 +40,16 @@ class AuthenticationBoardModule(CodeTableBase): If parameters are given, the widget initializes accordingly: 'size' is a dict: {"width": int, "height": int}, - 'data' is a 2D list where data[0] is the headline, - and data[x][y] represents the value in row x, column y. + 'data' is a dict with keys "cells", "code_length", "space_interval", + "space_amount" and "code_character_type". "cells" is a 2D list where + cells[0] is the headline and cells[x][y] represents the value in row x, + column y. The other keys contain an integer. The widget does not use more room than needed, and resizes dynamically. It has shortcuts for adding and removing rows. - Codes are not horizontally centered for readability because 'BGD' is wider - than 'III' (example) in certain fonts. + Codes are not horizontally centered for readability concerns because 'BGD' + is wider than 'III' (example) in certain fonts. """ def __init__(self, size=None, data=None): @@ -63,12 +66,23 @@ class AuthenticationBoardModule(CodeTableBase): "Invalid value for CONSTANT 'CODE_CHARACTER_TYPE': " "'{}'".format(CODE_CHARACTER_TYPE) ) - self.start_no_of_codes = START_NO_OF_CODES - self.code_length = CODE_LENGTH - self.space_interval = SPACE_INTERVAL - self.space_amount = SPACE_AMOUNT - self.code_character_type = CODE_CHARACTER_TYPE - self.headline = HEADLINE_TEXT + # Set default values for table to be generated + if size is None and data is None: + self.start_no_of_codes = START_NO_OF_CODES + self.code_length = CODE_LENGTH + self.space_interval = SPACE_INTERVAL + self.space_amount = SPACE_AMOUNT + self.code_character_type = CODE_CHARACTER_TYPE + + numbers = self.generate_authentication_numbers() + self.start_headline = ( + HEADLINE_TEXT + + " (" + + str(numbers[0]) + + " & " + + str(numbers[1]) + + ")" + ) CodeTableBase.__init__(self, size, data) @@ -123,7 +137,9 @@ class AuthenticationBoardModule(CodeTableBase): # Resize code-column in case it got wider # Example: 'BGD' is wider than 'III' (depending on font) self.resizeColumnToContents(2) - resize_table(self, resize_columns=False, has_headline=True) + self.resizeRowToContents(selected_row_index + 1) + + resize_table(self, columns=False, rows=False, has_headline=True) def remove_row(self, row_index): """Remove selected row. @@ -140,7 +156,28 @@ class AuthenticationBoardModule(CodeTableBase): for i in range(row_index, self.rowCount()): self.item(i, 0).setText(self.code_characters[i - 1]) self.item(i, 1).setText(str(i - 1)) - resize_table(self, resize_columns=False, has_headline=True) + resize_table(self, columns=False, rows=False, has_headline=True) + + def generate_authentication_numbers(self): + """Generate two non-equal numbers between 1 and self.code_length. + + Returns + ------- + list + Containing two integers, sorted in ascending order. + """ + available_numbers = list(range(1, self.code_length + 1)) + + numbers = [] + numbers.append(choice(available_numbers)) + numbers.append(choice(available_numbers)) + + while numbers[0] == numbers[1]: + numbers[1] = choice(available_numbers) + + numbers.sort() + + return numbers @staticmethod def get_user_friendly_name(): diff --git a/soitool/modules/module_base.py b/soitool/modules/module_base.py index 7b87d96b67669e18bd34a5315ea358b38a4f9ae7..1d6f055d20dd0a418317e7a77d3e140540159b2d 100644 --- a/soitool/modules/module_base.py +++ b/soitool/modules/module_base.py @@ -38,26 +38,24 @@ class ModuleBase(ABC): raise NotImplementedError -def resize_table( - table, resize_rows=True, resize_columns=True, has_headline=False -): +def resize_table(table, rows=True, columns=True, has_headline=False): """Resize a given QTableWidget. Parameters ---------- table : QTableWidget QTablewidget-instance to resize. - resize_rows : bool + rows : bool Resizes rows to contents if True, by default True. - resize_columns : bool + columns : bool Resizes columns to contents if True, by default True. has_headline : bool True if the table has a headline, by default False. Last column is widened if headline is wider than table. """ - if resize_columns: + if columns: table.resizeColumnsToContents() - if resize_rows: + if rows: table.resizeRowsToContents() # If table has a headline, make sure table is wide enough to fit it. diff --git a/soitool/modules/module_subtractorcodes.py b/soitool/modules/module_subtractorcodes.py index 3b04ab28ef7d67fcb7944d731c9ed37e989d4988..5ef03ddbde0ef4e611a1356f0faeb497ae5ae8ac 100644 --- a/soitool/modules/module_subtractorcodes.py +++ b/soitool/modules/module_subtractorcodes.py @@ -33,8 +33,10 @@ class SubtractorcodesModule(CodeTableBase): If parameters are given, the widget initializes accordingly: 'size' is a dict: {"width": int, "height": int}, - 'data' is a 2D list where data[0] is the headline, - and data[x][y] represents the value in row x, column y. + 'data' is a dict with keys "cells", "code_length", "space_interval", + "space_amount" and "code_character_type". "cells" is a 2D list where + cells[0] is the headline and cells[x][y] represents the value in row x, + column y. The other keys contain an integer. The widget does not use more room than needed, and resizes dynamically. It has shortcuts for adding and removing rows. @@ -48,7 +50,7 @@ class SubtractorcodesModule(CodeTableBase): "Invalid value for CONSTANT 'START_NO_OF_CODES': " "'{}'".format(START_NO_OF_CODES) ) - self.headline = HEADLINE_TEXT + self.start_headline = HEADLINE_TEXT self.code_length = CODE_LENGTH self.start_no_of_codes = START_NO_OF_CODES self.space_interval = SPACE_INTERVAL @@ -115,7 +117,8 @@ class SubtractorcodesModule(CodeTableBase): item_code.setFlags(item_code.flags() ^ Qt.ItemIsEditable) self.setItem(selected_row_index + 1, 2, item_code) - resize_table(self, resize_columns=False, has_headline=True) + self.resizeRowToContents(selected_row_index + 1) + resize_table(self, columns=False, rows=False, has_headline=True) def remove_row(self, row_index): """Remove the selected row. @@ -128,7 +131,7 @@ class SubtractorcodesModule(CodeTableBase): self.removeRow(row_index) self.insert_row_identifiers(has_headline=True) - resize_table(self, resize_columns=False, has_headline=True) + resize_table(self, columns=False, rows=False, has_headline=True) @staticmethod def get_user_friendly_name(): diff --git a/soitool/modules/module_table.py b/soitool/modules/module_table.py index a4b917ab9911bd4d9d9961d35f92f9f24a4323f1..43bef7fb8d9e3ca625877c9ae7dfae4705b3dd93 100644 --- a/soitool/modules/module_table.py +++ b/soitool/modules/module_table.py @@ -50,7 +50,7 @@ class TableModule(ModuleBase, QTableWidget, metaclass=Meta): self.setRowCount(START_ROWS) # Resize width and height of rows, columns and window - resize_table(self, resize_rows=True, resize_columns=True) + resize_table(self) # Set header-items for i in range(self.columnCount()): @@ -74,9 +74,7 @@ class TableModule(ModuleBase, QTableWidget, metaclass=Meta): self.setFixedWidth(size["width"]) self.setFixedHeight(size["height"]) - self.cellChanged.connect( - lambda: resize_table(self, resize_rows=True, resize_columns=True) - ) + self.cellChanged.connect(lambda: resize_table(self)) def keyPressEvent(self, event): """Launch actions when specific combinations of keys are pressed. @@ -131,24 +129,24 @@ class TableModule(ModuleBase, QTableWidget, metaclass=Meta): """Add column to the right of selected column.""" self.insertColumn(self.currentColumn() + 1) self.set_header_item(self.currentColumn() + 1, "") - resize_table(self, resize_rows=True, resize_columns=True) + resize_table(self) def remove_column(self): """Remove selected column if two or more columns exist.""" if self.columnCount() > 1: self.removeColumn(self.currentColumn()) - resize_table(self, resize_rows=True, resize_columns=True) + resize_table(self) def add_row(self): """Add row below selected row.""" self.insertRow(self.currentRow() + 1) - resize_table(self, resize_rows=True, resize_columns=True) + resize_table(self) def remove_row(self): """Remove selected row if two or more rows exist (including header).""" if self.rowCount() > 2 and self.currentRow() != 0: self.removeRow(self.currentRow()) - resize_table(self, resize_rows=True, resize_columns=True) + resize_table(self) def get_size(self): """Return size of widget.""" diff --git a/test/test_module_authentication_and_subtractor.py b/test/test_module_authentication_and_subtractor.py index 412bab95d15d7ca830fc5d445e2bd3bbc9ba936d..b7add13a9c313317cc4167f8cdcdef7abf5e34c6 100644 --- a/test/test_module_authentication_and_subtractor.py +++ b/test/test_module_authentication_and_subtractor.py @@ -45,8 +45,9 @@ class TestDefaultAuthenticationBoardAndSubtractorcodesModule( def test_default_module(self): """Test that module is initialized properly.""" # Assert correct headline - self.assertEqual( - self.authentication.item(0, 0).text(), HEADLINE_TEXT_AUTHENTICATION + self.assertTrue( + HEADLINE_TEXT_AUTHENTICATION + in self.authentication.item(0, 0).text() ) self.assertEqual( self.subtractor.item(0, 0).text(), HEADLINE_TEXT_SUBTRACTOR @@ -224,11 +225,17 @@ class TestAuthenticationBoardModuleFromData(unittest.TestCase): def test_create_from_data(self): """Test creating AuthenticationBoardModule from data.""" - test_data = [ - "Headline text", - ["A", "0", "TEST CODE ONE"], - ["B", "1", "TEST CODE TWO"], - ] + test_data = { + "cells": [ + "Headline text", + ["A", "0", "TEST CODE ONE"], + ["B", "1", "TEST CODE TWO"], + ], + "code_length": 11, + "space_interval": 4, + "space_amount": 1, + "code_character_type": "ascii", + } test_size = {"width": 100, "height": 100} module = AuthenticationBoardModule(size=test_size, data=test_data)