diff --git a/soitool/coder.py b/soitool/coder.py index e900cc3e8042f41c0a168c49d41fdd28b4e1fcf9..3439f53eadb9d14e077f1455812e99d89776f345 100644 --- a/soitool/coder.py +++ b/soitool/coder.py @@ -6,7 +6,7 @@ import string import secrets -def get_code(code_length, mode="ascii", space_interval=0): +def get_code(code_length, mode="ascii", space_interval=0, space_amount=1): """ Generate a single random code. @@ -19,6 +19,8 @@ def get_code(code_length, mode="ascii", space_interval=0): combination of letters and digits, by default 'ascii'. space_interval : int Spaces will be inserted to code each interval if not 0, by default 0. + space_amount : int + Amount of spaces per interval, by default 1. Return ------ @@ -52,12 +54,14 @@ def get_code(code_length, mode="ascii", space_interval=0): # Add spaces to code if interval is given if space_interval > 0: - code = insert_spaces(code, space_interval) + code = insert_spaces(code, space_interval, space_amount) return code -def get_code_set(count, code_length, mode="ascii", space_interval=0): +def get_code_set( + count, code_length, mode="ascii", space_interval=0, space_amount=1 +): """ Generate a set of unique, random codes. @@ -72,6 +76,8 @@ def get_code_set(count, code_length, mode="ascii", space_interval=0): for combination of letters and digits. space_interval : int Spaces will be inserted to code each interval if not 0, by default 0. + space_amount : int + Amount of spaces per interval, by default 1. Return ------ @@ -81,7 +87,7 @@ def get_code_set(count, code_length, mode="ascii", space_interval=0): codes = set() while len(codes) < count: - code = get_code(code_length, mode, space_interval) + code = get_code(code_length, mode, space_interval, space_amount) codes.add(code) return codes @@ -109,7 +115,7 @@ def get_code_length_needed(number_of_entries): return code_length -def insert_spaces(code, interval): +def insert_spaces(code, interval, space_amount=1): """Insert space after every x'th character, x = interval. Parameters @@ -118,6 +124,8 @@ def insert_spaces(code, interval): String to add spaces to. interval : int Interval for inserting spaces. + space_amount : int + Amount of spaces per interval, by default 1. Returns ------- @@ -126,7 +134,7 @@ def insert_spaces(code, interval): """ # Convert to list to insert spaces between characters code = list(code) - for i in range(interval - 1, len(code), interval): - code[i] += " " + for i in range(interval - 1, len(code) - interval, interval): + code[i] += " " * space_amount return "".join(code) diff --git a/soitool/module_list.py b/soitool/module_list.py index 9c4b9ba9795ce4dca060f7b839e5236cbb7b6a75..64c62e84200bf56fad6bb145357433b1bf463fff 100644 --- a/soitool/module_list.py +++ b/soitool/module_list.py @@ -171,10 +171,10 @@ class ModuleList(QListWidget): # Update module/attachment priority (order in list): if ModuleType(self.type) == ModuleType.MAIN_MODULE: - moving_module = self.parent.soi.modules.pop(origin) + moving_module = self.soi.modules.pop(origin) self.soi.modules.insert(destination, moving_module) elif ModuleType(self.type) == ModuleType.ATTACHMENT_MODULE: - moving_module = self.parent.soi.attachments.pop(origin) + moving_module = self.soi.attachments.pop(origin) self.soi.attachments.insert(destination, moving_module) self.soi.reorganize() diff --git a/soitool/modules/code_table_base.py b/soitool/modules/code_table_base.py index 7f3e2b9ec47cec3e23689b4d67a2f70847128a36..95ed2d1dc3c4cc832913422728e8e0b6c926870c 100644 --- a/soitool/modules/code_table_base.py +++ b/soitool/modules/code_table_base.py @@ -43,7 +43,7 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): if size is None and data is None: self.generate_table() self.resizeColumnsToContents() - self.insert_headline() + self.insert_headline(self.headline) # Resize height of rows and set size of window resize_table(self, resize_column=False) @@ -58,17 +58,23 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): self.setItem(i, j, item) self.resizeColumnsToContents() + self.insert_headline(data[0]) + resize_table(self, resize_column=False) self.setFixedWidth(size["width"]) self.setFixedHeight(size["height"]) - self.insert_headline(data[0]) + def test(self): + """...""" + print("JADDA") + resize_table(self, resize_column=False) def generate_table(self): """Insert row identifiers and authentication codes...........""" # Set number of rows and columns self.setRowCount(self.start_no_of_codes) self.setColumnCount(3) + self.insert_row_identifiers() # Generate codes codes = list( @@ -77,32 +83,26 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): self.code_length, self.code_character_type, self.space_interval, + self.space_amount, ) ) - - # Insert table data + # Insert codes for i in range(self.start_no_of_codes): - if self.type == AUTHENTICATIONBOARDMODULE: - # Insert non-editable row identifier in first column - item_first = QTableWidgetItem(self.row_identifiers[i]) - item_first.setFlags(item_first.flags() ^ Qt.ItemIsEditable) - self.setItem(i, 0, item_first) - - # Insert non-editable row identifier (int) in second column - item_second = QTableWidgetItem(str(i)) - item_second.setFlags(item_second.flags() ^ Qt.ItemIsEditable) - self.setItem(i, 1, item_second) - - # Insert non-editable code in third column - item_third = QTableWidgetItem(codes[i]) - item_third.setFlags(item_third.flags() ^ Qt.ItemIsEditable) - self.setItem(i, 2, item_third) - elif self.type == SUBTRACTORCODESMODULE: - print("NOPE") - - def insert_headline(self): - """Insert headline text.""" - item_headline = QTableWidgetItem(self.headline) + # Insert non-editable code in third column + item_third = QTableWidgetItem(codes[i]) + item_third.setFlags(item_third.flags() ^ Qt.ItemIsEditable) + self.setItem(i, 2, item_third) + + def insert_headline(self, text=None): + """Insert headline text. + + Parameters + ---------- + text : [type], optional + [description], by default None + """ + headline = self.headline if text is not None else text + item_headline = QTableWidgetItem(headline) item_headline.setTextAlignment(Qt.AlignHCenter) item_headline.setFont(HEADLINE_FONT) @@ -147,6 +147,21 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): return codes + def keyPressEvent(self, event): + """Add or remove row when 'Ctrl + +' and 'Ctrl + -' are pressed.""" + if ( + event.modifiers() == Qt.ControlModifier + and event.key() == Qt.Key_Plus + ): + self.add_row() + elif ( + event.modifiers() == Qt.ControlModifier + and event.key() == Qt.Key_Underscore + ): + self.remove_row() + else: + super().keyPressEvent(event) + def get_size(self): """Return size of widget.""" return get_table_size(self) diff --git a/soitool/modules/module_authentication_board.py b/soitool/modules/module_authentication_board.py index 6dc6912475e5c7a1315a0df3e904513b982fdf69..31329915f5c2689e72dc0abefb7e72f464c313e8 100644 --- a/soitool/modules/module_authentication_board.py +++ b/soitool/modules/module_authentication_board.py @@ -1,4 +1,4 @@ -"""Module containing SOI-module 'Autentifiseringstavle'.""" +"""Module containing SOI-module 'Autentiseringstavle'.""" import string from PySide2.QtWidgets import QTableWidgetItem from PySide2 import QtGui @@ -14,12 +14,13 @@ ROW_IDENTIFIERS = string.ascii_uppercase # Characters for first column, # it's length determines maximum number of codes (rows). SPACE_INTERVAL = 5 # Adds space between sets of characters, 0 => no spaces # If code is 123456 and interval is 2, code will be 12 34 56 +SPACE_AMOUNT = 2 HEADLINE_TEXT = "Autentiseringstavle" class AuthenticationBoardModule(CodeTableBase): - """Modified QTableWidget representing a 'Autentifiseringstavle'. + """Modified QTableWidget representing a 'Autentiseringstavle'. By default, the widget initializes with a headline, a row-count of START_NO_OF_AUTHENTICATION_CODES and three columns. @@ -42,11 +43,6 @@ class AuthenticationBoardModule(CodeTableBase): def __init__(self, size=None, data=None): self.type = "AuthenticationBoardModule" - self.start_no_of_codes = START_NO_OF_CODES - self.code_length = CODE_LENGTH - self.space_interval = SPACE_INTERVAL - self.row_identifiers = ROW_IDENTIFIERS - if CODE_CHARACTER_TYPE == "ascii": self.code_characters = string.ascii_uppercase elif CODE_CHARACTER_TYPE == "digits": @@ -58,25 +54,31 @@ 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 CodeTableBase.__init__(self, size, data) - - def keyPressEvent(self, event): - """Add or remove row when 'Ctrl + +' and 'Ctrl + -' are pressed.""" - if ( - event.modifiers() == Qt.ControlModifier - and event.key() == Qt.Key_Plus - ): - self.add_row() - elif ( - event.modifiers() == Qt.ControlModifier - and event.key() == Qt.Key_Underscore - ): - self.remove_row() - else: - super(AuthenticationBoardModule, self).keyPressEvent(event) + # If headline changes + self.cellChanged.connect( + lambda: resize_table(self, resize_column=False) + ) + + def insert_row_identifiers(self): + """Insert values in column one and two.""" + for i in range(self.rowCount()): + # Insert non-editable row identifier in first column + item_first = QTableWidgetItem(ROW_IDENTIFIERS[i]) + item_first.setFlags(item_first.flags() ^ Qt.ItemIsEditable) + self.setItem(i, 0, item_first) + + # Insert non-editable row identifier (int) in second column + item_second = QTableWidgetItem(str(i)) + item_second.setFlags(item_second.flags() ^ Qt.ItemIsEditable) + self.setItem(i, 1, item_second) def add_row(self): """Insert row below the selected row and add data.""" diff --git a/soitool/modules/module_base.py b/soitool/modules/module_base.py index c608a17177d43a16371b675d8f5a815f82a87e27..8300256e464779c4449045742238c636ea8d5488 100644 --- a/soitool/modules/module_base.py +++ b/soitool/modules/module_base.py @@ -54,8 +54,20 @@ def resize_table(widget, resize_row=True, resize_column=True): if resize_column: widget.resizeColumnsToContents() + widget.resizeColumnToContents(2) width, height = get_table_size(widget) + headline = widget.item(0, 0).text() + fm = QtGui.QFontMetrics(HEADLINE_FONT) + headline_width = fm.width(headline) + 10 # + 10 offset + + # If headline is wider than widget + if width < headline_width: + difference = headline_width - width + width += difference + old_width = widget.columnWidth(2) + widget.setColumnWidth(2, old_width + difference) + widget.setFixedWidth(width) widget.setFixedHeight(height) diff --git a/soitool/modules/module_subtractorcodes.py b/soitool/modules/module_subtractorcodes.py index 3233b65ed8a476c624a638b7392bdf4931e972f6..c048c8ebdcb7f11678bae129ca91f83ff8f18493 100644 --- a/soitool/modules/module_subtractorcodes.py +++ b/soitool/modules/module_subtractorcodes.py @@ -6,9 +6,11 @@ from PySide2.QtCore import Qt from soitool.modules.module_base import resize_table from soitool.modules.code_table_base import CodeTableBase -START_NO_OF_CODES = 7 +START_NO_OF_CODES = 7 # Maximum value is len(ALPHABET)/2 columns = 13 CODE_LENGTH = 8 SPACE_INTERVAL = 4 +SPACE_AMOUNT = 2 +ALPHABET = string.ascii_uppercase # Characters for first and second column HEADLINE_TEXT = "Subtraktorkoder" @@ -19,21 +21,95 @@ class SubtractorcodesModule(CodeTableBase): def __init__(self, size=None, data=None): self.type = "SubtractorcodesModule" + if START_NO_OF_CODES > 13: + raise ValueError( + "Invalid value for CONSTANT 'START_NO_OF_CODES': " + "'{}'".format(START_NO_OF_CODES) + ) + self.start_no_of_codes = START_NO_OF_CODES self.code_length = CODE_LENGTH self.space_interval = SPACE_INTERVAL - # Characters for first and second column, - # it's length/2 it the maximum number of codes (rows). - self.row_identifiers = string.ascii_uppercase + self.space_amount = SPACE_AMOUNT self.code_character_type = "digits" self.headline = HEADLINE_TEXT CodeTableBase.__init__(self, size, data) + # If headline changes + self.cellChanged.connect( + lambda: resize_table(self, resize_column=False) + ) + + def insert_row_identifiers(self): + """Insert table data. + + Parameters + ---------- + data : List + List of codes to insert. + """ + split_one = ALPHABET[0 : self.rowCount()] + split_two = ALPHABET[self.rowCount() : self.rowCount() * 2] + + for i in range(2): + for j in range(self.rowCount()): + text = split_one[j] if i == 0 else split_two[j] + item = QTableWidgetItem(text) + item.setFlags(item.flags() ^ Qt.ItemIsEditable) + self.setItem(j, i, item) + + def add_row(self): + """...""" + row_index = self.currentRow() + # If maximum amount of rows not reached and a row is selected + # (+ 1 to skip row containing headline) + if self.rowCount() < len(ALPHABET) / 2 + 1 and row_index != -1: + # Generate unique code and insert row + code = self.generate_unique_authentication_code() + self.insertRow(row_index + 1) + + # Insert values in first and second column + split_one = ALPHABET[0 : self.rowCount() - 1] + split_two = ALPHABET[self.rowCount() - 1 : self.rowCount() * 2 - 2] + + for i in range(2): + for j in range(1, self.rowCount()): # From 1 to skip headline + text = split_one[j - 1] if i == 0 else split_two[j - 1] + item = QTableWidgetItem(text) + item.setFlags(item.flags() ^ Qt.ItemIsEditable) + self.setItem(j, i, item) + + # Insert code in third column + item_third = QTableWidgetItem(code) + item_third.setFlags(item_third.flags() ^ Qt.ItemIsEditable) + self.setItem(row_index + 1, 2, item_third) + + # Resize code-column in case it got wider + # Example: 'BGD' is wider than 'III' (depending on font) + self.resizeColumnToContents(2) + resize_table(self, resize_column=False) + + def remove_row(self): + """Remove selected row.""" + row_index = self.currentRow() + # If at least one row (+ headline-row) exists and a row other than + # headline-row is selected + if self.rowCount() > 2 and row_index != 0 and row_index != -1: + # Remove row + self.removeRow(row_index) + + split_one = ALPHABET[0 : self.rowCount() - 1] + split_two = ALPHABET[self.rowCount() - 1 : self.rowCount() * 2 - 2] + for i in range(2): + for j in range(1, self.rowCount()): # From 1 to skip headline + # Insert values in first and second column + text = split_one[j - 1] if i == 0 else split_two[j - 1] + self.item(j, i).setText(text) @staticmethod def get_user_friendly_name(): """Get user-friendly name of module.""" - return "Autentiseringstavle" + return "Subtraktorkoder" @staticmethod def get_icon(): diff --git a/soitool/serialize_export_import_soi.py b/soitool/serialize_export_import_soi.py index d7856c1e2f6bd48e840f3486f093918864075abd..081e4f4b80a82f331d240353869c08b10dfc6b25 100644 --- a/soitool/serialize_export_import_soi.py +++ b/soitool/serialize_export_import_soi.py @@ -8,6 +8,7 @@ from soitool.modules.module_table import TableModule from soitool.modules.module_authentication_board import ( AuthenticationBoardModule, ) +from soitool.modules.module_subtractorcodes import SubtractorcodesModule from soitool.modules.module_freetext import FreeTextModule # Valid schema for serialized SOI @@ -231,6 +232,15 @@ def import_soi(file_path): "meta": module["meta"], } ) + elif module_type == "SubtractorcodesModule": + size = module["size"] + data = module["data"] + modules.append( + { + "widget": SubtractorcodesModule(size, data), + "meta": module["meta"], + } + ) elif module_type == "FreeTextModule": size = module["size"] data = module["data"]