diff --git a/soitool/help_actions.py b/soitool/help_actions.py index 5c822603179f67b4ef443cd6ebf391be5792f0d2..0d99369a6082c279ce8a5f4caa29ecef07c48739 100644 --- a/soitool/help_actions.py +++ b/soitool/help_actions.py @@ -41,9 +41,7 @@ class ShortcutsHelpDialog(QDialog): self.layout_label.addRow( QLabel("Åpne SOI fra database: "), QLabel("Ctrl + D") ) - self.layout_label.addRow( - QLabel("Forhåndsvis SOI: "), QLabel("Ctrl + P") - ) + self.layout_label.addRow(QLabel("Eksporter PDF: "), QLabel("Ctrl + P")) self.layout_label.addRow( QLabel("Lagre i database: "), QLabel("Ctrl + S") ) diff --git a/soitool/inline_editable_soi_view.py b/soitool/inline_editable_soi_view.py index 5bde3a8ba14af685065fa8200d96e2148e36651a..8690ad30f8010d51768593fcbe512b337781f493 100644 --- a/soitool/inline_editable_soi_view.py +++ b/soitool/inline_editable_soi_view.py @@ -12,7 +12,6 @@ from PySide2.QtWidgets import ( QGraphicsProxyWidget, ) from PySide2.QtGui import ( - QFont, QPixmap, QBrush, QPalette, @@ -22,6 +21,7 @@ from PySide2.QtPrintSupport import QPrinter from soitool.soi import ModuleLargerThanBinError from soitool.dialog_wrappers import exec_warning_dialog from soitool.serialize_export_import_soi import generate_soi_filename +from soitool.modules.module_base import qfont_with_pixel_size # How attachment pages should be numbered. The first page should be numbered # by the value of ATTACHMENT_NUMBERING_SCHEME[0], the second page @@ -419,7 +419,7 @@ class InlineEditableSOIView(QScrollArea): label_title = QLabel(self.soi.title) label_title.move(x, y + self.soi.HEADER_HEIGHT) label_title.setStyleSheet("background-color: rgba(0,0,0,0%)") - label_title.setFont(QFont("Times New Roman", 35)) + label_title.setFont(qfont_with_pixel_size("Times New Roman", 60)) proxy = self.scene.addWidget(label_title) proxy.setRotation(-90) @@ -427,7 +427,7 @@ class InlineEditableSOIView(QScrollArea): label_description = QLabel(self.soi.description) label_description.move(x + 57, y + self.soi.HEADER_HEIGHT) label_description.setStyleSheet("background-color: rgba(0,0,0,0%)") - label_description.setFont(QFont("Times New Roman", 15)) + label_description.setFont(qfont_with_pixel_size("Times New Roman", 25)) proxy = self.scene.addWidget(label_description) proxy.setRotation(-90) @@ -435,7 +435,9 @@ class InlineEditableSOIView(QScrollArea): creation_date = soi_date_string_to_user_friendly_string(self.soi.date) label_creation_date = QLabel("Opprettet: {}".format(creation_date)) label_creation_date.setStyleSheet("background-color: rgba(0,0,0,0%)") - label_creation_date.setFont(QFont("Times New Roman", 15)) + label_creation_date.setFont( + qfont_with_pixel_size("Times New Roman", 25) + ) # Source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet label_width = ( @@ -470,7 +472,7 @@ class InlineEditableSOIView(QScrollArea): "1 av N", f"{self.copy_current} av {self.copy_total}" ) proxy.label.setStyleSheet("background-color: rgba(0,0,0,0%)") - proxy.label.setFont(QFont("Times New Roman", 40)) + proxy.label.setFont(qfont_with_pixel_size("Times New Roman", 70)) # Need to call this when label of proxy changes. See function docstring proxy.update_bounding_rect() @@ -493,7 +495,9 @@ class InlineEditableSOIView(QScrollArea): label_copy_number_header.setStyleSheet( "background-color: rgba(0,0,0,0%)" ) - label_copy_number_header.setFont(QFont("Times New Roman", 15)) + label_copy_number_header.setFont( + qfont_with_pixel_size("Times New Roman", 25) + ) # Source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet label_width = ( @@ -519,7 +523,7 @@ class InlineEditableSOIView(QScrollArea): ) ) page_number.setStyleSheet("background-color: rgba(0,0,0,0%)") - page_number.setFont(QFont("Times New Roman", 15)) + page_number.setFont(qfont_with_pixel_size("Times New Roman", 25)) # Source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet label_width = ( @@ -534,7 +538,7 @@ class InlineEditableSOIView(QScrollArea): classification.setStyleSheet( "background-color: rgba(0,0,0,0%); " "color: red" ) - classification.setFont(QFont("Times New Roman", 35)) + classification.setFont(qfont_with_pixel_size("Times New Roman", 60)) # Source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet label_width = ( @@ -552,7 +556,7 @@ class InlineEditableSOIView(QScrollArea): ) label_valid_from = QLabel("Gyldig fra: {}".format(valid_from)) label_valid_from.setStyleSheet("background-color: rgba(0,0,0,0%)") - label_valid_from.setFont(QFont("Times New Roman", 15)) + label_valid_from.setFont(qfont_with_pixel_size("Times New Roman", 25)) # Source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet label_width = ( @@ -568,7 +572,7 @@ class InlineEditableSOIView(QScrollArea): valid_to = soi_date_string_to_user_friendly_string(self.soi.valid_to) label_valid_to = QLabel("Gyldig til: {}".format(valid_to)) label_valid_to.setStyleSheet("background-color: rgba(0,0,0,0%)") - label_valid_to.setFont(QFont("Times New Roman", 15)) + label_valid_to.setFont(qfont_with_pixel_size("Times New Roman", 25)) # Source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet label_width = ( diff --git a/soitool/main_window.py b/soitool/main_window.py index a4571cdf2b3e19a9a05f921c1b42bdc740fc5a4c..5dc81632c957243ee2a1fc6e6560a7ca1d049e92 100644 --- a/soitool/main_window.py +++ b/soitool/main_window.py @@ -110,14 +110,14 @@ class MainWindow(QMainWindow): new_soi.triggered.connect(self.open_soi_workspace_tab) # Open file - open_file = QAction("Åpne", self) + open_file = QAction("Åpne fra fil", self) open_file.setShortcut("Ctrl+o") open_file.setStatusTip("Åpne en SOI fra fil") open_file.triggered.connect(self.import_soi) file_menu.addAction(open_file) # Open file from DB - open_file_db = QAction("Åpne fra DB", self) + open_file_db = QAction("Åpne fra database", self) open_file_db.setShortcut("Ctrl+d") open_file_db.setStatusTip("Åpne en SOI fra databasen") open_file_db.triggered.connect(self.show_soi_db) @@ -130,7 +130,7 @@ class MainWindow(QMainWindow): file_menu.addAction(preview_soi) # Save to DB - save_soi = QAction("Lagre i DB", self) + save_soi = QAction("Lagre i database", self) save_soi.setShortcut("Ctrl+s") save_soi.setStatusTip("Lagre SOI i databasen") save_soi.triggered.connect(self.save_soi_db) @@ -154,6 +154,7 @@ class MainWindow(QMainWindow): # SOI PDF export_pdf = QAction("PDF", self) export_pdf.setStatusTip("Eksporter til PDF klar for utskrift") + export_pdf.setShortcut("Ctrl+p") export_pdf.triggered.connect( lambda: self.try_export_soi(medium=ExportMedium.PDF) ) @@ -169,7 +170,7 @@ class MainWindow(QMainWindow): codebook_menu.addAction(codebook) # Regenerate codebook-codes: - regenerate_codes = QAction("Nye koder i db", self) + regenerate_codes = QAction("Nye koder i kodebok", self) regenerate_codes.setStatusTip("Nye koder lages tilfeldig") regenerate_codes.triggered.connect(self.regenerate_codes) codebook_menu.addAction(regenerate_codes) @@ -360,7 +361,7 @@ class MainWindow(QMainWindow): soi_db_view = None codebook_db_view = None for i in range(self.tabs.count()): - if self.tabs.tabText(i) == "SOI'er i DB": + if self.tabs.tabText(i) == "SOI-er i database": soi_db_view = self.tabs.widget(i).view soi_db_view.setModel(None) elif self.tabs.tabText(i) == "Kodebok": @@ -377,7 +378,7 @@ class MainWindow(QMainWindow): exec_info_dialog( "Valgt tab er ingen SOI-tab", "Den valgte taben inneholder ingen SOI.\n" - "Riktig tab må velges for å lagre en SOI i DB.", + "Riktig tab må velges for å lagre en SOI i database.", ) def show_soi_db(self): @@ -388,13 +389,13 @@ class MainWindow(QMainWindow): """ # Loop through tabs to look for existing SOI-db-tab: for i in range(self.tabs.count()): - if self.tabs.tabText(i) == "SOI'er i DB": + if self.tabs.tabText(i) == "SOI-er i database": self.tabs.setCurrentIndex(i) break # SOI-db-tab does not exist, create, add and select tab else: tab = SOIDbWidget(self.database, self.tabs) - self.tabs.addTab(tab, "SOI'er i DB") + self.tabs.addTab(tab, "SOI-er i database") self.tabs.setCurrentWidget(tab) def open_shortcut_help(self): diff --git a/soitool/modules/code_table_base.py b/soitool/modules/code_table_base.py index 7a7200938b7cfa487fee33cda2b8821bf57147e8..478bc64449a55dc22a63336299d9ed83d5611253 100644 --- a/soitool/modules/code_table_base.py +++ b/soitool/modules/code_table_base.py @@ -5,11 +5,13 @@ Parent-class for AuthenticationBoardModule and SubtractorCodesModule. from PySide2.QtWidgets import QTableWidget, QTableWidgetItem from PySide2.QtCore import Qt from soitool.coder import get_code_set, get_code +from soitool.modules.code_table_settings import CodeTableSettings from soitool.modules.module_base import ( ModuleBase, get_table_size, resize_table, HEADLINE_FONT, + TABLE_CELL_DEFAULT_FONT, ) AUTHENTICATIONBOARD_MODULE = "AuthenticationBoardModule" @@ -28,14 +30,6 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): """ def __init__(self, size, data): - if self.type not in [ - AUTHENTICATIONBOARD_MODULE, - SUBTRACTORCODES_MODULE, - ]: - raise ValueError( - "Invalid value for class-variable type: " - "'{}'".format(self.type) - ) QTableWidget.__init__(self) ModuleBase.__init__(self) @@ -44,6 +38,7 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): self.verticalHeader().hide() self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setFont(TABLE_CELL_DEFAULT_FONT) # Resize table when headline changes self.cellChanged.connect( @@ -51,8 +46,33 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): self, rows=False, columns=False, has_headline=True, ) ) - # If parameters are None, generate new table + # If parameters are None, launch settings-dialog and generate new table if size is None and data is None: + # Disabling pylint-error + # 'Access to member before its definition line' + # because the variable is defined in subclass. + # pylint: disable=E0203 + if self.start_no_of_codes > self.maximum_no_of_codes: + raise ValueError( + "The value of module-constant 'START_NO_OF_CODES' " + "is larger than module-constant 'MAXIMUM_NO_OF_CODES': " + "{} > {}".format( + self.start_no_of_codes, self.maximum_no_of_codes + ) + ) + dialog = CodeTableSettings(self) + dialog.exec() + + self.start_headline = dialog.edit_headline.text() + self.start_no_of_codes = int( + dialog.combo_no_of_codes.currentText() + ) + self.code_length = int(dialog.combo_code_length.currentText()) + self.space_interval = int( + dialog.combo_space_interval.currentText() + ) + self.space_amount = int(dialog.combo_space_amount.currentText()) + self.generate_table() self.resizeColumnsToContents() self.resizeRowsToContents() @@ -77,10 +97,9 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): item = QTableWidgetItem( cells[i + 1][j] ) # +1 skip headline - if j == 2 and self.type == AUTHENTICATIONBOARD_MODULE: - item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) - else: + if j == 2: item.setTextAlignment(Qt.AlignCenter) + item.setFont(self.code_font) self.setItem(i, j, item) self.resizeColumnsToContents() @@ -195,7 +214,7 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta): # If at least two rows (+ headline-row) exists and a row other than # headline-row is selected row_index = self.currentRow() - if self.rowCount() > 2 and row_index != 0 and row_index != -1: + if self.rowCount() > 3 and row_index != 0 and row_index != -1: self.remove_row(row_index) else: super().keyPressEvent(event) diff --git a/soitool/modules/code_table_settings.py b/soitool/modules/code_table_settings.py new file mode 100644 index 0000000000000000000000000000000000000000..83ad828ae2ff239d4c4092e26073f76b6ac2b841 --- /dev/null +++ b/soitool/modules/code_table_settings.py @@ -0,0 +1,100 @@ +"""QDialog for setup of CodeTableBase.""" + +from PySide2.QtWidgets import ( + QDialog, + QVBoxLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QComboBox, + QFormLayout, + QPushButton, +) +from PySide2.QtCore import Qt + + +class CodeTableSettings(QDialog): + """Setup of CodeTableBase. + + Parameters + ---------- + code_table_base : soitool.modules.code_table_base.CodeTableBase + Is used to fetch the modules default values, so that these are + pre-selected. + """ + + def __init__(self, code_table_base): + super().__init__() + + # Hide help-button, disable close-button and set window width + self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) + self.setWindowFlag(Qt.WindowCloseButtonHint, False) + self.setFixedWidth(350) + + # Headline + self.label_headline = QLabel("Overskrift") + self.edit_headline = QLineEdit() + self.edit_headline.setText(code_table_base.start_headline) + + # Number of codes + self.label_no_of_codes = QLabel("Antall koder") + self.combo_no_of_codes = QComboBox() + for i in range(2, code_table_base.maximum_no_of_codes + 1): + self.combo_no_of_codes.addItem(str(i)) + self.combo_no_of_codes.setCurrentIndex( + code_table_base.start_no_of_codes - 2 + ) + + # Code length + self.label_code_length = QLabel("Kodelengde") + self.combo_code_length = QComboBox() + for i in range(2, 31): + self.combo_code_length.addItem(str(i)) + self.combo_code_length.setCurrentIndex(code_table_base.code_length - 2) + + # Space interval + self.label_space_interval = QLabel("Intervall for kodemellomrom") + self.combo_space_interval = QComboBox() + for i in range(11): + self.combo_space_interval.addItem(str(i)) + self.combo_space_interval.setCurrentIndex( + code_table_base.space_interval + ) + + # Space amount + self.label_space_amount = QLabel("Mellomrom per intervall") + self.combo_space_amount = QComboBox() + for i in range(6): + self.combo_space_amount.addItem(str(i)) + self.combo_space_amount.setCurrentIndex(code_table_base.space_amount) + + # Create-button + self.button_create = QPushButton("Opprett") + self.button_create.clicked.connect(self.accept) + + self.create_and_set_layout() + + def create_and_set_layout(self): + """Create layouts, add widgets and set layout.""" + # Layout for input-widgets + self.form_layout = QFormLayout() + self.form_layout.addRow(self.label_headline, self.edit_headline) + self.form_layout.addRow(self.label_no_of_codes, self.combo_no_of_codes) + self.form_layout.addRow(self.label_code_length, self.combo_code_length) + self.form_layout.addRow( + self.label_space_interval, self.combo_space_interval + ) + self.form_layout.addRow( + self.label_space_amount, self.combo_space_amount + ) + + # Layout for create-button + self.button_layout = QHBoxLayout() + self.button_layout.addWidget(self.button_create) + + # Main layout + self.main_layout = QVBoxLayout() + self.main_layout.addLayout(self.form_layout) + self.main_layout.addLayout(self.button_layout) + + self.setLayout(self.main_layout) diff --git a/soitool/modules/module_authentication_board.py b/soitool/modules/module_authentication_board.py index 5399ddd044a96a382011def70db29adad7acf48a..06ba84f67f0fee4b0a46b072c50bde45af22b0ea 100644 --- a/soitool/modules/module_authentication_board.py +++ b/soitool/modules/module_authentication_board.py @@ -4,10 +4,19 @@ from secrets import choice from PySide2.QtWidgets import QTableWidgetItem from PySide2 import QtGui from PySide2.QtCore import Qt -from soitool.modules.module_base import resize_table +from soitool.modules.module_base import ( + resize_table, + qfont_with_pixel_size, + TABLE_CELL_DEFAULT_FONT, +) from soitool.modules.code_table_base import CodeTableBase -# Maximum number of codes is len(ROW_IDENTIFIERS) +# Characters for first column, +# it's length determines maximum number of codes (rows). +ROW_IDENTIFIERS = string.ascii_uppercase + +# Maximum number of codes depends on ROW_IDENTIFIERS +MAXIMUM_NO_OF_CODES = len(ROW_IDENTIFIERS) START_NO_OF_CODES = 10 CODE_LENGTH = 25 @@ -16,11 +25,9 @@ CODE_LENGTH = 25 CODE_CHARACTER_TYPE = "ascii" # Font for authentication codes, should be a monospaced font -CODE_FONT = QtGui.QFont("Consolas", 10, QtGui.QFont.SansSerif) - -# Characters for first column, -# it's length determines maximum number of codes (rows). -ROW_IDENTIFIERS = string.ascii_uppercase +CODE_FONT = qfont_with_pixel_size( + "Consolas", TABLE_CELL_DEFAULT_FONT.pixelSize(), QtGui.QFont.SansSerif +) # Adds space between sets of characters, 0 => no spaces # If code is 123456 and interval is 2, code will be 12 34 56 @@ -33,7 +40,7 @@ HEADLINE_TEXT = "Autentiseringstavle" class AuthenticationBoardModule(CodeTableBase): """Modified QTableWidget representing SOI-module 'Autentiseringstavle'. - By default, the widget initializes with a headline, a row-count of + The default widget-initialization has a headline, a row-count of START_NO_OF_CODES and three columns. Row x in the first column contains the character ROW_IDENTIFIERS[x]. Row x in the second column contains the character x. @@ -70,6 +77,7 @@ class AuthenticationBoardModule(CodeTableBase): "'{}'".format(CODE_CHARACTER_TYPE) ) self.code_font = CODE_FONT + self.maximum_no_of_codes = MAXIMUM_NO_OF_CODES # Set default values for table to be generated if size is None and data is None: @@ -137,6 +145,7 @@ class AuthenticationBoardModule(CodeTableBase): # Insert authentication-code in third column item_third = QTableWidgetItem(code) item_third.setFont(self.code_font) + item_third.setTextAlignment(Qt.AlignCenter) item_third.setFlags(item_third.flags() ^ Qt.ItemIsEditable) self.setItem(selected_row_index + 1, 2, item_third) diff --git a/soitool/modules/module_base.py b/soitool/modules/module_base.py index 1d6f055d20dd0a418317e7a77d3e140540159b2d..91d97dbcbade3b85432fbaed1415a3a394663b1d 100644 --- a/soitool/modules/module_base.py +++ b/soitool/modules/module_base.py @@ -2,11 +2,34 @@ from abc import ABC from PySide2 import QtGui + +def qfont_with_pixel_size(font_family, pixel_size, weight=None): + """Provide a QFont with given family and pixel size. + + Created because QFont does not have a constructor with pixel size as a + parameter. + + Parameters + ---------- + font_family : str + Name of font family. Sent to https://doc.qt.io/qt-5/qfont.html#QFont-1 + pixel_size : int + Pixel size. Sent to https://doc.qt.io/qt-5/qfont.html#setPixelSize + weight : QFont.Weight + Weight of font. Sent to https://doc.qt.io/qt-5/qfont.html#QFont-1 + """ + if weight is not None: + font = QtGui.QFont(font_family, weight=weight) + else: + font = QtGui.QFont(font_family) + font.setPixelSize(pixel_size) + return font + + # Font for module headline -HEADLINE_FONT = QtGui.QFont() -HEADLINE_FONT.setFamily("Arial") -HEADLINE_FONT.setPointSize(12) -HEADLINE_FONT.setWeight(100) +HEADLINE_FONT = qfont_with_pixel_size("Arial", 24, 100) + +TABLE_CELL_DEFAULT_FONT = qfont_with_pixel_size("Arial", 16) class ModuleBase(ABC): diff --git a/soitool/modules/module_freetext.py b/soitool/modules/module_freetext.py index 95066bceea66e73d73013f3b930fecd92ef1484d..14ede836bcd46425bc8d10a1a198121b566759af 100644 --- a/soitool/modules/module_freetext.py +++ b/soitool/modules/module_freetext.py @@ -13,7 +13,11 @@ from PySide2.QtWidgets import ( ) from PySide2.QtCore import QSize, Qt from PySide2.QtGui import QIcon, QFontMetricsF -from soitool.modules.module_base import ModuleBase, HEADLINE_FONT +from soitool.modules.module_base import ( + ModuleBase, + HEADLINE_FONT, + qfont_with_pixel_size, +) class TextEditWithSizeOfContent(QTextEdit): @@ -113,6 +117,9 @@ class FreeTextModule(ModuleBase, QWidget, metaclass=Meta): self.line_edit_header = LineEditWithSizeOfContent() self.line_edit_header.setFont(HEADLINE_FONT) self.text_edit_body = TextEditWithSizeOfContent() + self.text_edit_body.setFont( + qfont_with_pixel_size("Arial", HEADLINE_FONT.pixelSize()) + ) # When the contents of these widgets change we need to manually trigger # adjust of size, even on self. Without adjust of size on self the diff --git a/soitool/modules/module_subtractorcodes.py b/soitool/modules/module_subtractorcodes.py index 8622f14b415973186a6e8a1e76eef606a0503b05..89d3f5c479e4f153489101b426450849a5a3db59 100644 --- a/soitool/modules/module_subtractorcodes.py +++ b/soitool/modules/module_subtractorcodes.py @@ -3,36 +3,44 @@ import string from PySide2.QtWidgets import QTableWidgetItem from PySide2 import QtGui from PySide2.QtCore import Qt -from soitool.modules.module_base import resize_table +from soitool.modules.module_base import ( + resize_table, + qfont_with_pixel_size, + TABLE_CELL_DEFAULT_FONT, +) from soitool.modules.code_table_base import CodeTableBase -# Maximum number of codes is len(ROW_IDENTIFIERS)/2 columns = 13 -START_NO_OF_CODES = 7 -CODE_LENGTH = 8 - -# Font for subtractorcodes -CODE_FONT = QtGui.QFont("Arial", 10, QtGui.QFont.SansSerif) - # Characters for first and second column ROW_IDENTIFIERS = string.ascii_uppercase -# Adds space between sets of characters, 0 => no spaces. -# If code is 12345678 and interval is 2, code will be 1234 5678 +# Maximum number of codes is: number of row identifiers / 2 columns +# = 13 if identifiers are A-Z +MAXIMUM_NO_OF_CODES = len(ROW_IDENTIFIERS) // 2 +START_NO_OF_CODES = 7 +CODE_LENGTH = 8 + +# Adds space between sets of characters, 0 => no spaces +# If code is 12345678 and interval is 4, code will be 1234 5678 SPACE_INTERVAL = 4 SPACE_AMOUNT = 3 +# Font for subtractorcodes +CODE_FONT = qfont_with_pixel_size( + "Arial", TABLE_CELL_DEFAULT_FONT.pixelSize(), QtGui.QFont.SansSerif +) + HEADLINE_TEXT = "Subtraktorkoder" class SubtractorcodesModule(CodeTableBase): """Modified QTablewidget representing SOI-module 'Subtraktorkoder'. - By default, the widget initializes with a headline, a row-count of + The default widget-initialization has a headline, a row-count of START_NO_OF_CODES and three columns. - If there are 2 rows and ROW_IDENTIFIERS is the alphabet, the first - column will contain A and B, and the second column will contain C and D. The third column contains subtractorcodes of length CODE_LENGTH, spaced out for readability if SPACE_INTERVAL and SPACE_AMOUNT larger than 0. + If there are 2 rows and ROW_IDENTIFIERS is the alphabet, the first + column will contain A and B, and the second column will contain C and D. If parameters are given, the widget initializes accordingly: 'size' is a dict: {"width": int, "height": int}, @@ -48,18 +56,17 @@ 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_headline = HEADLINE_TEXT - self.code_length = CODE_LENGTH - self.start_no_of_codes = START_NO_OF_CODES - self.space_interval = SPACE_INTERVAL - self.space_amount = SPACE_AMOUNT self.code_character_type = "digits" self.code_font = CODE_FONT + self.maximum_no_of_codes = MAXIMUM_NO_OF_CODES + + # Set default values for table to be generated + if size is None and data is None: + self.start_headline = HEADLINE_TEXT + self.start_no_of_codes = START_NO_OF_CODES + self.code_length = CODE_LENGTH + self.space_interval = SPACE_INTERVAL + self.space_amount = SPACE_AMOUNT CodeTableBase.__init__(self, size, data) diff --git a/soitool/modules/module_table.py b/soitool/modules/module_table.py index 43bef7fb8d9e3ca625877c9ae7dfae4705b3dd93..fff51d7e058351e3df4dde972e1db077481b8316 100644 --- a/soitool/modules/module_table.py +++ b/soitool/modules/module_table.py @@ -6,6 +6,7 @@ from soitool.modules.module_base import ( resize_table, get_table_size, HEADLINE_FONT, + TABLE_CELL_DEFAULT_FONT, ) START_ROWS = 2 @@ -42,6 +43,7 @@ class TableModule(ModuleBase, QTableWidget, metaclass=Meta): self.verticalHeader().hide() self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setFont(TABLE_CELL_DEFAULT_FONT) # If parameters are None, start as empty table. if size is None and data is None: diff --git a/test/test_module_authentication_and_subtractor.py b/test/test_module_authentication_and_subtractor.py index b7add13a9c313317cc4167f8cdcdef7abf5e34c6..3eb4195928b69794204c40f25c56212bfa0be4fc 100644 --- a/test/test_module_authentication_and_subtractor.py +++ b/test/test_module_authentication_and_subtractor.py @@ -3,7 +3,7 @@ import unittest import string from PySide2 import QtGui from PySide2.QtWidgets import QApplication -from PySide2.QtCore import Qt +from PySide2.QtCore import Qt, QTimer from PySide2.QtTest import QTest from soitool.modules.module_authentication_board import ( AuthenticationBoardModule, @@ -39,7 +39,15 @@ class TestDefaultAuthenticationBoardAndSubtractorcodesModule( def setUp(self): """Create new AuthenticationBoardModule.""" + + def press_enter_on_active_dialog(): + active_widget = app.activeModalWidget() + QTest.keyClick(active_widget, Qt.Key_Enter) + + QTimer.singleShot(0, press_enter_on_active_dialog) self.authentication = AuthenticationBoardModule() + + QTimer.singleShot(0, press_enter_on_active_dialog) self.subtractor = SubtractorcodesModule() def test_default_module(self): @@ -201,16 +209,16 @@ class TestDefaultAuthenticationBoardAndSubtractorcodesModule( self.authentication.item(1, 1).text(), value_to_delete ) - # Remove rows until only headline-row and a single code-row exist - for _ in range(1, self.authentication.rowCount() - 1): + # Remove rows until only headline-row and two code-rows exist + for _ in range(1, self.authentication.rowCount()): QTest.keyClick(self.authentication, Qt.Key_Down) QTest.keyClicks(self.authentication, "_", Qt.ControlModifier) - self.assertTrue(self.authentication.rowCount() == 2) + self.assertTrue(self.authentication.rowCount() == 3) # Try to remove final code-row, should not work QTest.keyClick(self.authentication, Qt.Key_Down) QTest.keyClicks(self.authentication, "_", Qt.ControlModifier) # Assert row was not removed - self.assertTrue(self.authentication.rowCount() == 2) + self.assertTrue(self.authentication.rowCount() == 3) def test_add_to_soi_smoke_test(self): """Test that module can be added to SOI.""" diff --git a/test/test_resolution_smoke_test.py b/test/test_resolution_smoke_test.py new file mode 100644 index 0000000000000000000000000000000000000000..479fcf8578c404fb4a69774e8bd7b56096d46bed --- /dev/null +++ b/test/test_resolution_smoke_test.py @@ -0,0 +1,152 @@ +"""Test module that looks for differences in SOI modules across resolutions. + +This was written as a regression test for #125. Ideally it should also test the +header of the SOI, but this is much more complicated to test for.. + +## Important + +This test can not test across different resolutions by itself, this is up to +the user. Our pipeline will test across a couple of different resolutions. +""" +import unittest +from PySide2.QtWidgets import QApplication +from PySide2 import QtGui +from PySide2.QtCore import QTimer, Qt, QSysInfo +from PySide2.QtTest import QTest +from PySide2.QtGui import QGuiApplication +from soitool.soi import SOI +from soitool.modules.module_authentication_board import ( + AuthenticationBoardModule, +) +from soitool.modules.module_subtractorcodes import SubtractorcodesModule +from soitool.modules.module_predefined_codes import PredefinedCodesModule +from soitool.new_module_dialog import MODULE_CHOICES +from soitool.soi_workspace_widget import DATABASE_MODULES +from soitool.database import Database + +# The error being ignored here is pylint telling us that 'test' is a standard +# module, so the import should be placed further up. In our case we have an +# actual custom module called 'test', so pylint is confused. +# pylint: disable=C0411 +from test.test_database import TESTDBPATH + + +if isinstance(QtGui.qApp, type(None)): + app = QApplication([]) +else: + app = QtGui.qApp + +# Modules with a popup as part of their __init__ +POPUP_MODULES = [ + AuthenticationBoardModule, + SubtractorcodesModule, + PredefinedCodesModule, +] + + +def screen_information(): + """Get string with information about the screen. + + Returns + ------- + str + String with screen information. + """ + screen_string = "" + screen = QGuiApplication.primaryScreen() + screen_string += "screen.size() -> " + str(screen.size()) + "\n" + screen_string += ( + "screen.physicalDotsPerInchX() -> " + + str(screen.physicalDotsPerInchX()) + + "\n" + ) + screen_string += ( + "screen.physicalDotsPerInchY() -> " + + str(screen.physicalDotsPerInchY()) + + "\n" + ) + screen_string += ( + "screen.logicalDotsPerInchX() -> " + + str(screen.logicalDotsPerInchX()) + + "\n" + ) + screen_string += ( + "screen.logicalDotsPerInchY() -> " + + str(screen.logicalDotsPerInchY()) + + "\n" + ) + screen_string += ( + "screen.devicePixelRatio() -> " + str(screen.devicePixelRatio()) + "\n" + ) + return screen_string + + +class TestModulesAcrossResolutions(unittest.TestCase): + """TestCase for modules across resolutions.""" + + @unittest.skipUnless( + QSysInfo.productType() == "windows", + "Test currently only able to target Windows", + ) + def test_add_all_modules(self): + """Add all modules, reorganize, and assert result. + + Expected result was gotten by simply running reorganize and noting down + the results. We should get the same result across different + resolutions. + + Modules added to SOI must be the same every time the program is ran. + + Test only works on Windows, as the expected result is based on the + Windows look-and-feel. + + NOTE: This test needs to be updated when a new module is added, + deleted, or changed. It will fail until it is updated. + """ + expected_result = [ + {"x": 0, "y": 0, "page": 1, "name": "PredefinedCodesModule"}, + {"x": 640, "y": 0, "page": 1, "name": "AuthenticationBoardModule"}, + {"x": 1047.5, "y": 0, "page": 1, "name": "SubtractorcodesModule"}, + {"x": 1273.0, "y": 0, "page": 1, "name": "FreeTextModule"}, + {"x": 1373.0, "y": 0, "page": 1, "name": "TableModule"}, + ] + + # For use with modules that require a database + database = Database(db_path=TESTDBPATH) + + def press_enter(): + active_widget = app.activeModalWidget() + + # AuthenticationBoardModule needs special treatment because the + # title can contain random info + if isinstance(active_widget, AuthenticationBoardModule): + # triple click to select existing text for overwrite + QTest.mouseDClick(active_widget.edit_headline, Qt.LeftButton) + QTest.mouseClick(active_widget.edit_headline, Qt.LeftButton) + # need to overwrite text because title otherwise contains + # unpredictable text + QTest.keyClicks(active_widget.edit_headline, "TestTitle") + QTest.keyClick(active_widget, Qt.Key_Enter) + + soi = SOI() + + for module in MODULE_CHOICES: + # If we're adding one of the modules with a popup as part of it's + # constructor we need to singleShot pressing enter to close it + if module in POPUP_MODULES: + QTimer.singleShot(0, press_enter) + + if module in DATABASE_MODULES: + soi.add_module(module.__name__, module(database=database)) + else: + soi.add_module(module.__name__, module()) + + soi.reorganize() + + self.assertEqual( + expected_result, + [module["meta"] for module in soi.modules], + "All modules added to SOI and calling reorganize should result in " + "expected 'meta' information. Screen is: \n" + + screen_information(), + )