diff --git a/soitool/main_window.py b/soitool/main_window.py index 7ce14b1bf0b89e6ec5258e4944687042a4d5595c..38759e20a72f31092e4f857794a4705783c5045e 100644 --- a/soitool/main_window.py +++ b/soitool/main_window.py @@ -317,7 +317,7 @@ class MainWindow(QMainWindow): soi = import_soi(file_path) # Create and select tab - tab = SOIWorkspaceWidget(soi) + tab = SOIWorkspaceWidget(self.database, soi) self.tabs.addTab(tab, soi.title) self.tabs.setCurrentWidget(tab) diff --git a/soitool/modules/module_predefined_codes.py b/soitool/modules/module_predefined_codes.py index cd509db2e4be803dd3d9ef47801375dc6c147e4a..3d27b0fabd37da2b578fda75dfa46e0121a5c6b3 100644 --- a/soitool/modules/module_predefined_codes.py +++ b/soitool/modules/module_predefined_codes.py @@ -1,5 +1,6 @@ -"""...""" +"""SOI-module 'Forhåndsavtalte koder'.""" import string +from random import sample from PySide2.QtWidgets import ( QWidget, QTableWidget, @@ -8,12 +9,21 @@ from PySide2.QtWidgets import ( QVBoxLayout, QLabel, QLineEdit, + QDialog, + QListWidget, + QListWidgetItem, + QAbstractItemView, + QFormLayout, + QPushButton, + QSpinBox, ) from PySide2 import QtGui from PySide2.QtCore import Qt from soitool.modules.module_base import ModuleBase, HEADLINE_FONT, resize_table +from soitool.dialog_wrappers import exec_critical_dialog ALPHABET = string.ascii_uppercase +DEFAULT_HEADLINE = "FORHÅNDSAVTALTE KODER" class Meta(type(ModuleBase), type(QWidget)): @@ -21,106 +31,178 @@ class Meta(type(ModuleBase), type(QWidget)): class PredefinedCodesModule(ModuleBase, QWidget, metaclass=Meta): - """...""" + """QWidget representing SOI-module 'Forhåndsavtalte koder'. - def __init__(self, size=None, data=None, database=None): - self.type = "PredefinedCodesModule" - QWidget.__init__(self) + This widget has a headline and a layout with x amount of + PredefinedCodesTable-objects. - if size is None and data is None: - # module_headline = QLabel("FORHÅNDSAVTALTE KODER") - self.module_headline = QLineEdit("FORHÅNDSAVTALTE KODER") - data = self.get_data_from_database(database) - self.create_tables(data) - self.create_and_set_layout() - else: - self.generate_from_data(data) - self.setFixedSize(size["width"], size["height"]) + It is initialized using only one of it's + parameters: - self.resize_module_headline() - self.module_headline.setFont(HEADLINE_FONT) - self.module_headline.setAlignment(Qt.AlignCenter) - self.module_headline.textChanged.connect(self.resize_module_headline) + If only parameter 'database' is given, the widget reads from + database-table 'Codebook' all expressions where column 'Type'='Liten'. + It then creates one PredefinedCodesTable per category and inserts all + expressions in the category. - def get_data_from_database(self, database): - """...""" - stmt = "SELECT Category FROM Codebook WHERE Type='Liten' GROUP BY Category" + If only parameter 'data' is given, the widget creates + PredefinedCodesTables based on 'data'. + 'data' is a dict: {"module_headline": string, "tables": list}, where + "tables": [{"table_headline": string, "expressions": list of strings}] - queried = database.conn.execute(stmt).fetchall() - categories = [row["Category"] for row in queried] + The widget does not use more room than needed, and resizes dynamically. - data = [] - for category in categories: + Raises + ------ + ValueError + If none or both parameters are given. + """ - stmt = ( - "SELECT Word FROM Codebook WHERE Category=? AND Type='Liten'" - ) - queried = database.conn.execute(stmt, (category,)).fetchall() - expressions = [row["Word"] for row in queried] + def __init__(self, database=None, data=None): + self.type = "PredefinedCodesModule" + QWidget.__init__(self) + ModuleBase.__init__(self) + + if database is not None and data is None: + warning_word = get_warning_word(database) - data.append( - {"Category": category, "Expressions": expressions,} + categories_unsorted = get_categories(database) + dialog = PredefinedCodesSettings( + DEFAULT_HEADLINE, warning_word, categories_unsorted ) + dialog.exec_() - return data + self.module_headline = QLabel(dialog.edit_headline.text()) + self.warning_word = QLabel(dialog.edit_warning_word.text()) + self.maximum_height = dialog.edit_module_height.value() - def create_tables(self, data): - """...""" + categories = [] + for i in range(dialog.list_category_order.count()): + categories.append(dialog.list_category_order.item(i).text()) + + expressions = [] + for category in categories: + expressions_in_category = get_expressions_in_category( + database, category + ) + expressions.append( + sample( + expressions_in_category, len(expressions_in_category) + ) + ) + + self.create_tables(categories, expressions) + + elif data is not None and database is None: + self.module_headline = QLabel(data["module_headline"]) + self.warning_word = QLabel(data["warning_word"]) + self.create_tables_from_data(data) + else: + raise ValueError( + "Exactly one parameter needs to be given: " + + "('database' or 'data')." + ) + + self.create_and_set_layout() + self.module_headline.setFont(HEADLINE_FONT) + self.module_headline.setAlignment(Qt.AlignCenter) + self.warning_word.setFont(HEADLINE_FONT) + self.warning_word.setAlignment(Qt.AlignCenter) + # resize_text_container(self.module_headline) + # resize_text_container(self.warning_word) + # self.module_headline.textChanged.connect( + # lambda: resize_text_container(self.module_headline) + # ) + # self.warning_word.textChanged.connect( + # lambda: resize_text_container(self.warning_word) + # ) + + def create_tables(self, categories, expressions): + """Create PredefinedCodesTable-objects.""" self.tables = [] - for i, element in enumerate(data): - category = element["Category"] - expressions = element["Expressions"] - headline = ALPHABET[i] + ": " + category - table = PredefinedCodesTable(headline, expressions) + for i, category in enumerate(categories): + headline = " " + ALPHABET[i] + " " + category + table = PredefinedCodesTable(headline, expressions[i]) self.tables.append(table) + def create_tables_from_data(self, data): + """Create PredefinedCodesTable-objects from data.""" + self.tables = [] + + for table_data in data["tables"]: + table_headline = table_data["table_headline"] + expressions = table_data["expressions"] + table = PredefinedCodesTable(table_headline, expressions) + self.tables.append(table) + def create_and_set_layout(self): - """...""" - table_layout = QHBoxLayout() + """Create, fill and set layout.""" + vbox_layouts = [] + i = 0 + filled_height = 0 + for table in self.tables: - table_layout.addWidget(table) - table_layout.setAlignment(table, Qt.AlignTop) + if table.height() > self.maximum_height: + exec_critical_dialog( + "Forhåndsavtalte koder kunne ikke lages.", + "Én eller flere tabeller er høyere enn valgt " + + "maksimalhøyde.", + ) + raise ValueError( + "One or more tables are taller than the chosen " + "maximum height: " + "{} > {}".format(table.height(), self.maximum_height) + ) + + while i < len(self.tables): + vbox_layout = QVBoxLayout() + while ( + i < len(self.tables) + and filled_height + self.tables[i].height() + <= self.maximum_height + ): + vbox_layout.addWidget(self.tables[i]) + vbox_layout.setAlignment(self.tables[i], Qt.AlignTop) + filled_height += self.tables[i].height() + i += 1 + vbox_layouts.append(vbox_layout) + filled_height = 0 + + table_layout = QHBoxLayout() + for vbox_layout in vbox_layouts: + table_layout.addLayout(vbox_layout) main_layout = QVBoxLayout() main_layout.addWidget(self.module_headline) main_layout.setAlignment(self.module_headline, Qt.AlignHCenter) - main_layout.addLayout(table_layout) - - self.setLayout(main_layout) - def resize_module_headline(self): - """...""" - headline = self.module_headline.text() - - headline_width = ( - QtGui.QFontMetricsF(HEADLINE_FONT).horizontalAdvance(headline) + 5 - ) - self.module_headline.setFixedWidth(headline_width) + warning_word_layout = QHBoxLayout() + warning_word_label = QLabel("Varslingsord: ") + warning_word_layout.addWidget(warning_word_label) + warning_word_layout.addWidget(self.warning_word) - def generate_from_data(self, data): - """...""" - self.module_headline = QLineEdit(data["module_headline"]) - self.tables = [] - - for table_data in data["tables"]: - table_headline = table_data["table_headline"] - expressions = table_data["expressions"] - table = PredefinedCodesTable(table_headline, expressions) - self.tables.append(table) + main_layout.addLayout(warning_word_layout) + main_layout.setAlignment(warning_word_layout, Qt.AlignHCenter) + main_layout.addLayout(table_layout) - self.create_and_set_layout() + self.setLayout(main_layout) def get_size(self): """Return size of table.""" return self.width(), self.height() def get_data(self): - """...""" + """Return dict containing all module-data. + + Returns + ------- + Dict + With keys 'table_headline': str and "expressions": list of str. + """ tables = [] + for table in self.tables: - # rows = [] item_headline = table.item(0, 0) if item_headline is not None: @@ -130,10 +212,6 @@ class PredefinedCodesModule(ModuleBase, QWidget, metaclass=Meta): expressions = [] for i in range(1, table.rowCount()): - # row = [] - # for j in range(table.columnCount()): - # row.append(table.item(i, 1).text()) - # rows.append(row) expressions.append(table.item(i, 1).text()) table = { @@ -144,6 +222,7 @@ class PredefinedCodesModule(ModuleBase, QWidget, metaclass=Meta): data = { "module_headline": self.module_headline.text(), + "warning_word": self.warning_word.text(), "tables": tables, } @@ -258,3 +337,136 @@ class PredefinedCodesTable(QTableWidget): self.set_codes(selected_row_index) resize_table(self, columns=False, rows=False, has_headline=True) + + +class PredefinedCodesSettings(QDialog): + """...""" + + def __init__(self, headline, warning_word, categories): + 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(headline) + + # Warning-word + self.label_warning_word = QLabel("Varslingsord") + self.edit_warning_word = QLineEdit() + self.edit_warning_word.setText(warning_word) + + # Module-height + self.label_module_height = QLabel("Maksimal modulhøyde") + self.edit_module_height = QSpinBox() + self.edit_module_height.setRange(50, 1000) + self.edit_module_height.setSingleStep(50) + self.edit_module_height.setValue(200) + + # Category-order + self.label_category_order = QLabel("Kategori-rekkefølge") + self.list_category_order = QListWidget() + # Enable drag-and-drop + self.list_category_order.setDragEnabled(True) + self.list_category_order.viewport().setAcceptDrops(True) + self.list_category_order.setDragDropMode( + QAbstractItemView.InternalMove + ) + # Remove horizontal scrollbar + self.list_category_order.setHorizontalScrollBarPolicy( + Qt.ScrollBarAlwaysOff + ) + for i, category in enumerate(categories): + item = QListWidgetItem(category) + item.setFlags(item.flags() ^ Qt.ItemIsEditable) + self.list_category_order.insertItem(i, item) + + # 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_warning_word, self.edit_warning_word + ) + self.form_layout.addRow( + self.label_module_height, self.edit_module_height + ) + self.form_layout.addRow( + self.label_category_order, self.list_category_order + ) + + # 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) + + +def get_warning_word(database): + """...""" + stmt = "SELECT Word FROM CategoryWords ORDER BY RANDOM() LIMIT 1" + + warning_word = database.conn.execute(stmt).fetchone()[0] + + return warning_word + + +def get_categories(database): + """...""" + stmt = ( + "SELECT Category FROM Codebook " "WHERE Type='Liten' GROUP BY Category" + ) + queried = database.conn.execute(stmt).fetchall() + + categories = [row["Category"] for row in queried] + + return categories + + +def get_expressions_in_category(database, category): + """Read categories and expressions from database. + + Categories and expressions are from small codebook ('Type'='Liten'). + + Parameters + ---------- + database : soitool.database.Database + Connection to database. + + Returns + ------- + list + Containing dicts + with keys 'Category': string and 'Expressions': list of strings. + """ + stmt = "SELECT Word FROM Codebook WHERE Category=? AND Type='Liten'" + queried = database.conn.execute(stmt, (category,)).fetchall() + expressions = [row["Word"] for row in queried] + + return expressions + + +# def resize_text_container(container): +# """Adjust width of a text-widget to fit it's text."""" +# headline = container.text() +# +# width = ( +# QtGui.QFontMetricsF(HEADLINE_FONT).horizontalAdvance(headline) + 5 +# ) +# container.setFixedWidth(width) diff --git a/soitool/serialize_export_import_soi.py b/soitool/serialize_export_import_soi.py index e1ad84d4cd084775ec407cf808630f9808b92402..3bbb8fb8914db30c18c6a2671cc2e858f38a8284 100644 --- a/soitool/serialize_export_import_soi.py +++ b/soitool/serialize_export_import_soi.py @@ -312,7 +312,7 @@ def construct_modules_from_serialized(serialized_modules): elif module_type == "PredefinedCodesModule": modules.append( { - "widget": PredefinedCodesModule(size, data), + "widget": PredefinedCodesModule(data=data), "meta": module["meta"], } )