Module soitool.main_window

Inclues the main window of our application.

Built up by widgets implemented in other modules.

Expand source code
"""Inclues the main window of our application.

Built up by widgets implemented in other modules.
"""
import sys
import os
from enum import Enum
from PySide2.QtWidgets import (
    QTabWidget,
    QMainWindow,
    QApplication,
    QAction,
    QFileDialog,
    QDialog,
)
from PySide2.QtGui import QIcon
from PySide2.QtCore import QTimer
from soitool.soi import SOI
from soitool.soi_workspace_widget import SOIWorkspaceWidget
from soitool.codebook_to_pdf import generate_codebook_pdf
from soitool.dialog_wrappers import exec_info_dialog
from soitool.codebook_widget import CodebookWidget
from soitool.codebook_model_view import CodebookTableModel
from soitool.soi_db_widget import SOIDbWidget
from soitool.soi_model_view import SOITableModel
from soitool.database import Database, DBPATH
from soitool.help_actions import ShortcutsHelpDialog, BasicUsageHelpDialog
from soitool.pdf_export_options_dialog import PdfExportOptionsDialog
from soitool.serialize_export_import_soi import (
    export_soi,
    import_soi,
)


class ExportMedium(Enum):
    """Enumerate with mediums possible for export."""

    COMPRESSED = 0
    UNCOMPRESSED = 1
    PDF = 2


class MainWindow(QMainWindow):
    """MainWindow, shell of the entire application.

    Parameter 'db_path' is the path to an existing or future database-file.
    """

    def __init__(self, db_path=DBPATH):  # pylint: disable = R0914, R0915
        super().__init__()
        self.setGeometry(100, 100, 800, 800)
        self.setWindowTitle("SOI-tool")
        self.statusBar()

        self.popup_shortcut_help = ShortcutsHelpDialog()
        self.popup_basic_use_help = BasicUsageHelpDialog()
        # Database instance
        self.database = Database(db_path)
        # Timer for automatic update of codes in CodeBook
        self.timer = QTimer()
        # Interval i set to msec since last 24h update
        self.timer.setInterval(
            self.database.seconds_to_next_update(60 * 60 * 24) * 1000
        )
        self.timer.timeout.connect(lambda: self.regenerate_codes(auto=True))
        self.timer.start()

        self.setup_menubar()

        # Add tab-widget with closeable tabs
        self.tabs = QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.tabCloseRequested.connect(
            lambda index: self.close_tab(index)  # pylint: disable=W0108
        )
        self.setCentralWidget(self.tabs)

        # Add HV logo
        filename = "media/HVlogo.PNG"
        dirname = os.path.dirname(__file__)
        filepath = os.path.join(dirname, filename)
        self.setWindowIcon(QIcon(filepath))

        self.action_shortcut_help.triggered.connect(self.open_shortcut_help)
        self.action_basic_use_help.triggered.connect(self.open_basic_use_help)

        # Add SOIWorkspaceWidget-tab
        self.open_soi_workspace_tab()

    # pylint: disable=R0914, R0915
    # Ignoring "Too many local variables" and "Too many statements"
    def setup_menubar(self):
        """Set up menubar with submenus and actions."""
        menu = self.menuBar()
        file_menu = menu.addMenu("SOI")
        codebook_menu = menu.addMenu("Kodebok")
        help_menu = menu.addMenu("Hjelp")

        # New SOI
        new_soi = QAction("Ny", self)
        new_soi.setShortcut("Ctrl+n")
        new_soi.setStatusTip("Opprett en ny SOI")
        file_menu.addAction(new_soi)
        new_soi.triggered.connect(self.open_soi_workspace_tab)

        # Open file
        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 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)
        file_menu.addAction(open_file_db)

        # Preview SOI
        preview_soi = QAction("Forhåndsvis")
        preview_soi.setShortcut("Ctrl+p")
        preview_soi.setStatusTip("Forhåndsvis SOI som PDF")
        file_menu.addAction(preview_soi)

        # Save to DB
        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)
        file_menu.addAction(save_soi)

        # Export SOI
        export_serialized_soi = file_menu.addMenu("Eksporter")
        # Compressed SOI
        export_compressed = QAction("Komprimert", self)
        export_compressed.setShortcut("Ctrl+e")
        export_compressed.setStatusTip("Eksporter komprimert SOI")
        export_compressed.triggered.connect(
            lambda: self.try_export_soi(medium=ExportMedium.COMPRESSED)
        )
        # Uncompressed SOI
        export_uncompressed = QAction("Ukomprimert", self)
        export_uncompressed.setStatusTip("Eksporter ukomprimert SOI")
        export_uncompressed.triggered.connect(
            lambda: self.try_export_soi(medium=ExportMedium.UNCOMPRESSED)
        )
        # 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)
        )
        export_serialized_soi.addAction(export_compressed)
        export_serialized_soi.addAction(export_uncompressed)
        export_serialized_soi.addAction(export_pdf)
        file_menu.addMenu(export_serialized_soi)

        # View/edit Codebook
        codebook = QAction("Se/rediger kodebok", self)
        codebook.setStatusTip("Se/rediger kodebok")
        codebook.triggered.connect(self.open_codebook_tab)
        codebook_menu.addAction(codebook)

        # Regenerate codebook-codes:
        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)

        # Export codebook as PDF
        export_codebook = codebook_menu.addMenu("Eksporter")

        # Export full codebook
        export_codebook_full = QAction("Stor kodebok", self)
        export_codebook_full.setStatusTip("Eksporter stor kodebok som PDF")
        export_codebook_full.triggered.connect(
            lambda: generate_codebook_pdf(database=self.database)
        )
        export_codebook.addAction(export_codebook_full)

        # Export small codebook
        export_codebook_small = QAction("Liten kodebok", self)
        export_codebook_small.setStatusTip("Eksporter liten kodebok som PDF")
        export_codebook_small.triggered.connect(
            lambda: generate_codebook_pdf(database=self.database, small=True)
        )
        export_codebook.addAction(export_codebook_small)

        # Hot keys
        self.action_shortcut_help = QAction("Hurtigtaster", self)
        self.action_shortcut_help.setStatusTip(
            "Vis oversikt over hurtigtaster"
        )
        help_menu.addAction(self.action_shortcut_help)

        # Easy use
        self.action_basic_use_help = QAction("Enkel bruk", self)
        self.action_basic_use_help.setStatusTip(
            "Vis enkel bruk av programvaren"
        )
        help_menu.addAction(self.action_basic_use_help)

    def open_codebook_tab(self):
        """Open tab containing CodebookWidget.

        Select codebook-tab if it is already open,
        create and select codebook-tab if not open.
        """
        # Loop through tabs to look for existing codebook-tab:
        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == "Kodebok":
                self.tabs.setCurrentIndex(i)
                break
        # Codebook-tab does not exist, create, add and select tab
        else:
            tab = CodebookWidget(self.database)
            self.tabs.addTab(tab, "Kodebok")
            self.tabs.setCurrentWidget(tab)

    def open_soi_workspace_tab(self):
        """Open and select tab containing a SOIWorkspaceWidget.

        Tab will have newly created SOI.
        """
        soi = SOI()
        self.add_soi_tab(soi)

    def close_tab(self, index):
        """Close tab at given index.

        Parameters
        ----------
        index : int
            Index of the tab to close.
        """
        widget_in_tab = self.tabs.widget(index)

        # Close db-connection if tab is a CodebookWidget or SOIDbWidget
        if isinstance(widget_in_tab, (CodebookWidget, SOIDbWidget)):
            widget_in_tab.view.close_db_connection()

        self.tabs.removeTab(index)

    def try_export_soi(self, medium=ExportMedium.COMPRESSED):
        """Export the SOI in the current tab.

        Feedback is given through a dialog if the current tab does not contain
        an SOI (tab is not a SOIWorkspaceWidget).

        Parameters
        ----------
        medium : ExportMedium
            Which medium to export SOI to. Must be one of the enums in
            ExportMedium.

        Raises
        ------
        ValueError
            If export medium is unknown.
        """
        tab_widget = self.tabs.currentWidget()

        # If tab contains an SOI
        if isinstance(tab_widget, SOIWorkspaceWidget):
            if medium == ExportMedium.COMPRESSED:
                export_soi(tab_widget.soi, True)
            elif medium == ExportMedium.UNCOMPRESSED:
                export_soi(tab_widget.soi, False)
            elif medium == ExportMedium.PDF:
                self.prompt_user_and_produce_pdf()
            else:
                raise ValueError(f"Unknown medium for export '{medium}'")
        else:
            exec_info_dialog(
                "Valgt tab er ingen SOI-tab",
                "Den valgte taben inneholder ingen SOI.\n"
                "For å eksportere en SOI må riktig tab velges.",
            )

    def prompt_user_and_produce_pdf(self):
        """Prompt user for PDF options and produce PDF."""
        dialog_pdf_options = PdfExportOptionsDialog()
        dialog_code = dialog_pdf_options.exec()
        if dialog_code == QDialog.DialogCode.Accepted:
            chosen_number_of_copies = (
                dialog_pdf_options.spinbox_number_of_copies.value()
            )
            chosen_resolution = dialog_pdf_options.spinbox_resolution.value()

            tab_widget = self.tabs.currentWidget()
            tab_widget.view.produce_pdf(
                chosen_number_of_copies, chosen_resolution
            )

    def import_soi(self):
        """Import serialized SOI.

        Launches a QFileDialog with a name-filter, where .txt and .json are
        accepted file extensions.
        A SOIWorkspaceWidget containing the SOI-object is created and opened
        in a new tab, which is selected.
        """
        # Get file-path from dialog
        file_path = QFileDialog().getOpenFileName(
            self,
            "Åpne SOI",
            os.getcwd(),
            "Text/JSON-filer (SOI_*.txt SOI_*.json)",
        )[0]

        if len(file_path) > 0:
            soi = import_soi(file_path, self.database)
            self.add_soi_tab(soi)

    def add_soi_tab(self, soi):
        """Add SOI tab with given SOI.

        Parameters
        ----------
        soi : soitool.soi.SOI
            SOI to populate tab with.
        """
        # Create and select tab
        tab = SOIWorkspaceWidget(self.database, soi)
        self.tabs.addTab(tab, soi.title)
        self.tabs.setCurrentWidget(tab)

        # Update tab-title when SOI-title changes
        soi.add_update_property_listener(
            lambda: self.tabs.setTabText(self.tabs.indexOf(tab), soi.title)
        )

    def regenerate_codes(self, auto=False):
        """Regenerate codebook-codes and update codebook-tab if open.

        Parameters
        ----------
        auto : bool, optional
            True if self.timer called this function, by default False.
        """
        if auto:

            def regenerate():
                self.database.update_codebook_auto(self.timer)

        else:

            def regenerate():
                self.database.update_codebook()

        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == "Kodebok":
                view = self.tabs.widget(i).view
                view.setModel(None)
                regenerate()
                view.setModel(CodebookTableModel())
                break
        else:
            regenerate()

    def save_soi_db(self):
        """Save the SOI of the current tab in the database."""
        tab_widget = self.tabs.currentWidget()

        # If tab contains an SOI
        if isinstance(tab_widget, SOIWorkspaceWidget):
            # Update tab showing SOI's in db if it is open,
            # and pause database-lock by codebook-tab if it is open
            soi_db_view = None
            codebook_db_view = None
            for i in range(self.tabs.count()):
                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":
                    codebook_db_view = self.tabs.widget(i).view
                    codebook_db_view.setModel(None)

            self.database.insert_or_update_soi(tab_widget.soi)

            if soi_db_view is not None:
                soi_db_view.setModel(SOITableModel())
            if codebook_db_view is not None:
                codebook_db_view.setModel(CodebookTableModel())
        else:
            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 database.",
            )

    def show_soi_db(self):
        """Open and select tab containing SOIDbWidget.

        Select tab if it is already open,
        create and select tab if not open.
        """
        # 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 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 database")
            self.tabs.setCurrentWidget(tab)

    def open_shortcut_help(self):
        """Open shortcut dialog."""
        self.popup_shortcut_help.setWindowTitle("Hurtigtaster")
        self.popup_shortcut_help.exec()

    def open_basic_use_help(self):
        """Open basic usage dialog."""
        self.popup_basic_use_help.setWindowTitle("Enkel bruk")
        self.popup_basic_use_help.exec()


if __name__ == "__main__":

    app = QApplication(sys.argv)
    WINDOW = MainWindow()
    WINDOW.showMaximized()
    app.exec_()

Classes

class ExportMedium (value, names=None, *, module=None, qualname=None, type=None, start=1)

Enumerate with mediums possible for export.

Expand source code
class ExportMedium(Enum):
    """Enumerate with mediums possible for export."""

    COMPRESSED = 0
    UNCOMPRESSED = 1
    PDF = 2

Ancestors

  • enum.Enum

Class variables

var COMPRESSED

Enumerate with mediums possible for export.

var PDF

Enumerate with mediums possible for export.

var UNCOMPRESSED

Enumerate with mediums possible for export.

class MainWindow (db_path='/builds/bachelor-paa-bittet/soitool/soitool/database')

MainWindow, shell of the entire application.

Parameter 'db_path' is the path to an existing or future database-file.

Expand source code
class MainWindow(QMainWindow):
    """MainWindow, shell of the entire application.

    Parameter 'db_path' is the path to an existing or future database-file.
    """

    def __init__(self, db_path=DBPATH):  # pylint: disable = R0914, R0915
        super().__init__()
        self.setGeometry(100, 100, 800, 800)
        self.setWindowTitle("SOI-tool")
        self.statusBar()

        self.popup_shortcut_help = ShortcutsHelpDialog()
        self.popup_basic_use_help = BasicUsageHelpDialog()
        # Database instance
        self.database = Database(db_path)
        # Timer for automatic update of codes in CodeBook
        self.timer = QTimer()
        # Interval i set to msec since last 24h update
        self.timer.setInterval(
            self.database.seconds_to_next_update(60 * 60 * 24) * 1000
        )
        self.timer.timeout.connect(lambda: self.regenerate_codes(auto=True))
        self.timer.start()

        self.setup_menubar()

        # Add tab-widget with closeable tabs
        self.tabs = QTabWidget()
        self.tabs.setTabsClosable(True)
        self.tabs.tabCloseRequested.connect(
            lambda index: self.close_tab(index)  # pylint: disable=W0108
        )
        self.setCentralWidget(self.tabs)

        # Add HV logo
        filename = "media/HVlogo.PNG"
        dirname = os.path.dirname(__file__)
        filepath = os.path.join(dirname, filename)
        self.setWindowIcon(QIcon(filepath))

        self.action_shortcut_help.triggered.connect(self.open_shortcut_help)
        self.action_basic_use_help.triggered.connect(self.open_basic_use_help)

        # Add SOIWorkspaceWidget-tab
        self.open_soi_workspace_tab()

    # pylint: disable=R0914, R0915
    # Ignoring "Too many local variables" and "Too many statements"
    def setup_menubar(self):
        """Set up menubar with submenus and actions."""
        menu = self.menuBar()
        file_menu = menu.addMenu("SOI")
        codebook_menu = menu.addMenu("Kodebok")
        help_menu = menu.addMenu("Hjelp")

        # New SOI
        new_soi = QAction("Ny", self)
        new_soi.setShortcut("Ctrl+n")
        new_soi.setStatusTip("Opprett en ny SOI")
        file_menu.addAction(new_soi)
        new_soi.triggered.connect(self.open_soi_workspace_tab)

        # Open file
        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 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)
        file_menu.addAction(open_file_db)

        # Preview SOI
        preview_soi = QAction("Forhåndsvis")
        preview_soi.setShortcut("Ctrl+p")
        preview_soi.setStatusTip("Forhåndsvis SOI som PDF")
        file_menu.addAction(preview_soi)

        # Save to DB
        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)
        file_menu.addAction(save_soi)

        # Export SOI
        export_serialized_soi = file_menu.addMenu("Eksporter")
        # Compressed SOI
        export_compressed = QAction("Komprimert", self)
        export_compressed.setShortcut("Ctrl+e")
        export_compressed.setStatusTip("Eksporter komprimert SOI")
        export_compressed.triggered.connect(
            lambda: self.try_export_soi(medium=ExportMedium.COMPRESSED)
        )
        # Uncompressed SOI
        export_uncompressed = QAction("Ukomprimert", self)
        export_uncompressed.setStatusTip("Eksporter ukomprimert SOI")
        export_uncompressed.triggered.connect(
            lambda: self.try_export_soi(medium=ExportMedium.UNCOMPRESSED)
        )
        # 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)
        )
        export_serialized_soi.addAction(export_compressed)
        export_serialized_soi.addAction(export_uncompressed)
        export_serialized_soi.addAction(export_pdf)
        file_menu.addMenu(export_serialized_soi)

        # View/edit Codebook
        codebook = QAction("Se/rediger kodebok", self)
        codebook.setStatusTip("Se/rediger kodebok")
        codebook.triggered.connect(self.open_codebook_tab)
        codebook_menu.addAction(codebook)

        # Regenerate codebook-codes:
        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)

        # Export codebook as PDF
        export_codebook = codebook_menu.addMenu("Eksporter")

        # Export full codebook
        export_codebook_full = QAction("Stor kodebok", self)
        export_codebook_full.setStatusTip("Eksporter stor kodebok som PDF")
        export_codebook_full.triggered.connect(
            lambda: generate_codebook_pdf(database=self.database)
        )
        export_codebook.addAction(export_codebook_full)

        # Export small codebook
        export_codebook_small = QAction("Liten kodebok", self)
        export_codebook_small.setStatusTip("Eksporter liten kodebok som PDF")
        export_codebook_small.triggered.connect(
            lambda: generate_codebook_pdf(database=self.database, small=True)
        )
        export_codebook.addAction(export_codebook_small)

        # Hot keys
        self.action_shortcut_help = QAction("Hurtigtaster", self)
        self.action_shortcut_help.setStatusTip(
            "Vis oversikt over hurtigtaster"
        )
        help_menu.addAction(self.action_shortcut_help)

        # Easy use
        self.action_basic_use_help = QAction("Enkel bruk", self)
        self.action_basic_use_help.setStatusTip(
            "Vis enkel bruk av programvaren"
        )
        help_menu.addAction(self.action_basic_use_help)

    def open_codebook_tab(self):
        """Open tab containing CodebookWidget.

        Select codebook-tab if it is already open,
        create and select codebook-tab if not open.
        """
        # Loop through tabs to look for existing codebook-tab:
        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == "Kodebok":
                self.tabs.setCurrentIndex(i)
                break
        # Codebook-tab does not exist, create, add and select tab
        else:
            tab = CodebookWidget(self.database)
            self.tabs.addTab(tab, "Kodebok")
            self.tabs.setCurrentWidget(tab)

    def open_soi_workspace_tab(self):
        """Open and select tab containing a SOIWorkspaceWidget.

        Tab will have newly created SOI.
        """
        soi = SOI()
        self.add_soi_tab(soi)

    def close_tab(self, index):
        """Close tab at given index.

        Parameters
        ----------
        index : int
            Index of the tab to close.
        """
        widget_in_tab = self.tabs.widget(index)

        # Close db-connection if tab is a CodebookWidget or SOIDbWidget
        if isinstance(widget_in_tab, (CodebookWidget, SOIDbWidget)):
            widget_in_tab.view.close_db_connection()

        self.tabs.removeTab(index)

    def try_export_soi(self, medium=ExportMedium.COMPRESSED):
        """Export the SOI in the current tab.

        Feedback is given through a dialog if the current tab does not contain
        an SOI (tab is not a SOIWorkspaceWidget).

        Parameters
        ----------
        medium : ExportMedium
            Which medium to export SOI to. Must be one of the enums in
            ExportMedium.

        Raises
        ------
        ValueError
            If export medium is unknown.
        """
        tab_widget = self.tabs.currentWidget()

        # If tab contains an SOI
        if isinstance(tab_widget, SOIWorkspaceWidget):
            if medium == ExportMedium.COMPRESSED:
                export_soi(tab_widget.soi, True)
            elif medium == ExportMedium.UNCOMPRESSED:
                export_soi(tab_widget.soi, False)
            elif medium == ExportMedium.PDF:
                self.prompt_user_and_produce_pdf()
            else:
                raise ValueError(f"Unknown medium for export '{medium}'")
        else:
            exec_info_dialog(
                "Valgt tab er ingen SOI-tab",
                "Den valgte taben inneholder ingen SOI.\n"
                "For å eksportere en SOI må riktig tab velges.",
            )

    def prompt_user_and_produce_pdf(self):
        """Prompt user for PDF options and produce PDF."""
        dialog_pdf_options = PdfExportOptionsDialog()
        dialog_code = dialog_pdf_options.exec()
        if dialog_code == QDialog.DialogCode.Accepted:
            chosen_number_of_copies = (
                dialog_pdf_options.spinbox_number_of_copies.value()
            )
            chosen_resolution = dialog_pdf_options.spinbox_resolution.value()

            tab_widget = self.tabs.currentWidget()
            tab_widget.view.produce_pdf(
                chosen_number_of_copies, chosen_resolution
            )

    def import_soi(self):
        """Import serialized SOI.

        Launches a QFileDialog with a name-filter, where .txt and .json are
        accepted file extensions.
        A SOIWorkspaceWidget containing the SOI-object is created and opened
        in a new tab, which is selected.
        """
        # Get file-path from dialog
        file_path = QFileDialog().getOpenFileName(
            self,
            "Åpne SOI",
            os.getcwd(),
            "Text/JSON-filer (SOI_*.txt SOI_*.json)",
        )[0]

        if len(file_path) > 0:
            soi = import_soi(file_path, self.database)
            self.add_soi_tab(soi)

    def add_soi_tab(self, soi):
        """Add SOI tab with given SOI.

        Parameters
        ----------
        soi : soitool.soi.SOI
            SOI to populate tab with.
        """
        # Create and select tab
        tab = SOIWorkspaceWidget(self.database, soi)
        self.tabs.addTab(tab, soi.title)
        self.tabs.setCurrentWidget(tab)

        # Update tab-title when SOI-title changes
        soi.add_update_property_listener(
            lambda: self.tabs.setTabText(self.tabs.indexOf(tab), soi.title)
        )

    def regenerate_codes(self, auto=False):
        """Regenerate codebook-codes and update codebook-tab if open.

        Parameters
        ----------
        auto : bool, optional
            True if self.timer called this function, by default False.
        """
        if auto:

            def regenerate():
                self.database.update_codebook_auto(self.timer)

        else:

            def regenerate():
                self.database.update_codebook()

        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == "Kodebok":
                view = self.tabs.widget(i).view
                view.setModel(None)
                regenerate()
                view.setModel(CodebookTableModel())
                break
        else:
            regenerate()

    def save_soi_db(self):
        """Save the SOI of the current tab in the database."""
        tab_widget = self.tabs.currentWidget()

        # If tab contains an SOI
        if isinstance(tab_widget, SOIWorkspaceWidget):
            # Update tab showing SOI's in db if it is open,
            # and pause database-lock by codebook-tab if it is open
            soi_db_view = None
            codebook_db_view = None
            for i in range(self.tabs.count()):
                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":
                    codebook_db_view = self.tabs.widget(i).view
                    codebook_db_view.setModel(None)

            self.database.insert_or_update_soi(tab_widget.soi)

            if soi_db_view is not None:
                soi_db_view.setModel(SOITableModel())
            if codebook_db_view is not None:
                codebook_db_view.setModel(CodebookTableModel())
        else:
            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 database.",
            )

    def show_soi_db(self):
        """Open and select tab containing SOIDbWidget.

        Select tab if it is already open,
        create and select tab if not open.
        """
        # 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 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 database")
            self.tabs.setCurrentWidget(tab)

    def open_shortcut_help(self):
        """Open shortcut dialog."""
        self.popup_shortcut_help.setWindowTitle("Hurtigtaster")
        self.popup_shortcut_help.exec()

    def open_basic_use_help(self):
        """Open basic usage dialog."""
        self.popup_basic_use_help.setWindowTitle("Enkel bruk")
        self.popup_basic_use_help.exec()

Ancestors

  • PySide2.QtWidgets.QMainWindow
  • PySide2.QtWidgets.QWidget
  • PySide2.QtCore.QObject
  • PySide2.QtGui.QPaintDevice
  • Shiboken.Object

Class variables

var staticMetaObject

Methods

def add_soi_tab(self, soi)

Add SOI tab with given SOI.

Parameters

soi : SOI
SOI to populate tab with.
Expand source code
def add_soi_tab(self, soi):
    """Add SOI tab with given SOI.

    Parameters
    ----------
    soi : soitool.soi.SOI
        SOI to populate tab with.
    """
    # Create and select tab
    tab = SOIWorkspaceWidget(self.database, soi)
    self.tabs.addTab(tab, soi.title)
    self.tabs.setCurrentWidget(tab)

    # Update tab-title when SOI-title changes
    soi.add_update_property_listener(
        lambda: self.tabs.setTabText(self.tabs.indexOf(tab), soi.title)
    )
def close_tab(self, index)

Close tab at given index.

Parameters

index : int
Index of the tab to close.
Expand source code
def close_tab(self, index):
    """Close tab at given index.

    Parameters
    ----------
    index : int
        Index of the tab to close.
    """
    widget_in_tab = self.tabs.widget(index)

    # Close db-connection if tab is a CodebookWidget or SOIDbWidget
    if isinstance(widget_in_tab, (CodebookWidget, SOIDbWidget)):
        widget_in_tab.view.close_db_connection()

    self.tabs.removeTab(index)
def import_soi(self)

Import serialized SOI.

Launches a QFileDialog with a name-filter, where .txt and .json are accepted file extensions. A SOIWorkspaceWidget containing the SOI-object is created and opened in a new tab, which is selected.

Expand source code
def import_soi(self):
    """Import serialized SOI.

    Launches a QFileDialog with a name-filter, where .txt and .json are
    accepted file extensions.
    A SOIWorkspaceWidget containing the SOI-object is created and opened
    in a new tab, which is selected.
    """
    # Get file-path from dialog
    file_path = QFileDialog().getOpenFileName(
        self,
        "Åpne SOI",
        os.getcwd(),
        "Text/JSON-filer (SOI_*.txt SOI_*.json)",
    )[0]

    if len(file_path) > 0:
        soi = import_soi(file_path, self.database)
        self.add_soi_tab(soi)
def open_basic_use_help(self)

Open basic usage dialog.

Expand source code
def open_basic_use_help(self):
    """Open basic usage dialog."""
    self.popup_basic_use_help.setWindowTitle("Enkel bruk")
    self.popup_basic_use_help.exec()
def open_codebook_tab(self)

Open tab containing CodebookWidget.

Select codebook-tab if it is already open, create and select codebook-tab if not open.

Expand source code
def open_codebook_tab(self):
    """Open tab containing CodebookWidget.

    Select codebook-tab if it is already open,
    create and select codebook-tab if not open.
    """
    # Loop through tabs to look for existing codebook-tab:
    for i in range(self.tabs.count()):
        if self.tabs.tabText(i) == "Kodebok":
            self.tabs.setCurrentIndex(i)
            break
    # Codebook-tab does not exist, create, add and select tab
    else:
        tab = CodebookWidget(self.database)
        self.tabs.addTab(tab, "Kodebok")
        self.tabs.setCurrentWidget(tab)
def open_shortcut_help(self)

Open shortcut dialog.

Expand source code
def open_shortcut_help(self):
    """Open shortcut dialog."""
    self.popup_shortcut_help.setWindowTitle("Hurtigtaster")
    self.popup_shortcut_help.exec()
def open_soi_workspace_tab(self)

Open and select tab containing a SOIWorkspaceWidget.

Tab will have newly created SOI.

Expand source code
def open_soi_workspace_tab(self):
    """Open and select tab containing a SOIWorkspaceWidget.

    Tab will have newly created SOI.
    """
    soi = SOI()
    self.add_soi_tab(soi)
def prompt_user_and_produce_pdf(self)

Prompt user for PDF options and produce PDF.

Expand source code
def prompt_user_and_produce_pdf(self):
    """Prompt user for PDF options and produce PDF."""
    dialog_pdf_options = PdfExportOptionsDialog()
    dialog_code = dialog_pdf_options.exec()
    if dialog_code == QDialog.DialogCode.Accepted:
        chosen_number_of_copies = (
            dialog_pdf_options.spinbox_number_of_copies.value()
        )
        chosen_resolution = dialog_pdf_options.spinbox_resolution.value()

        tab_widget = self.tabs.currentWidget()
        tab_widget.view.produce_pdf(
            chosen_number_of_copies, chosen_resolution
        )
def regenerate_codes(self, auto=False)

Regenerate codebook-codes and update codebook-tab if open.

Parameters

auto : bool, optional
True if self.timer called this function, by default False.
Expand source code
def regenerate_codes(self, auto=False):
    """Regenerate codebook-codes and update codebook-tab if open.

    Parameters
    ----------
    auto : bool, optional
        True if self.timer called this function, by default False.
    """
    if auto:

        def regenerate():
            self.database.update_codebook_auto(self.timer)

    else:

        def regenerate():
            self.database.update_codebook()

    for i in range(self.tabs.count()):
        if self.tabs.tabText(i) == "Kodebok":
            view = self.tabs.widget(i).view
            view.setModel(None)
            regenerate()
            view.setModel(CodebookTableModel())
            break
    else:
        regenerate()
def save_soi_db(self)

Save the SOI of the current tab in the database.

Expand source code
def save_soi_db(self):
    """Save the SOI of the current tab in the database."""
    tab_widget = self.tabs.currentWidget()

    # If tab contains an SOI
    if isinstance(tab_widget, SOIWorkspaceWidget):
        # Update tab showing SOI's in db if it is open,
        # and pause database-lock by codebook-tab if it is open
        soi_db_view = None
        codebook_db_view = None
        for i in range(self.tabs.count()):
            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":
                codebook_db_view = self.tabs.widget(i).view
                codebook_db_view.setModel(None)

        self.database.insert_or_update_soi(tab_widget.soi)

        if soi_db_view is not None:
            soi_db_view.setModel(SOITableModel())
        if codebook_db_view is not None:
            codebook_db_view.setModel(CodebookTableModel())
    else:
        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 database.",
        )
def setup_menubar(self)

Set up menubar with submenus and actions.

Expand source code
def setup_menubar(self):
    """Set up menubar with submenus and actions."""
    menu = self.menuBar()
    file_menu = menu.addMenu("SOI")
    codebook_menu = menu.addMenu("Kodebok")
    help_menu = menu.addMenu("Hjelp")

    # New SOI
    new_soi = QAction("Ny", self)
    new_soi.setShortcut("Ctrl+n")
    new_soi.setStatusTip("Opprett en ny SOI")
    file_menu.addAction(new_soi)
    new_soi.triggered.connect(self.open_soi_workspace_tab)

    # Open file
    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 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)
    file_menu.addAction(open_file_db)

    # Preview SOI
    preview_soi = QAction("Forhåndsvis")
    preview_soi.setShortcut("Ctrl+p")
    preview_soi.setStatusTip("Forhåndsvis SOI som PDF")
    file_menu.addAction(preview_soi)

    # Save to DB
    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)
    file_menu.addAction(save_soi)

    # Export SOI
    export_serialized_soi = file_menu.addMenu("Eksporter")
    # Compressed SOI
    export_compressed = QAction("Komprimert", self)
    export_compressed.setShortcut("Ctrl+e")
    export_compressed.setStatusTip("Eksporter komprimert SOI")
    export_compressed.triggered.connect(
        lambda: self.try_export_soi(medium=ExportMedium.COMPRESSED)
    )
    # Uncompressed SOI
    export_uncompressed = QAction("Ukomprimert", self)
    export_uncompressed.setStatusTip("Eksporter ukomprimert SOI")
    export_uncompressed.triggered.connect(
        lambda: self.try_export_soi(medium=ExportMedium.UNCOMPRESSED)
    )
    # 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)
    )
    export_serialized_soi.addAction(export_compressed)
    export_serialized_soi.addAction(export_uncompressed)
    export_serialized_soi.addAction(export_pdf)
    file_menu.addMenu(export_serialized_soi)

    # View/edit Codebook
    codebook = QAction("Se/rediger kodebok", self)
    codebook.setStatusTip("Se/rediger kodebok")
    codebook.triggered.connect(self.open_codebook_tab)
    codebook_menu.addAction(codebook)

    # Regenerate codebook-codes:
    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)

    # Export codebook as PDF
    export_codebook = codebook_menu.addMenu("Eksporter")

    # Export full codebook
    export_codebook_full = QAction("Stor kodebok", self)
    export_codebook_full.setStatusTip("Eksporter stor kodebok som PDF")
    export_codebook_full.triggered.connect(
        lambda: generate_codebook_pdf(database=self.database)
    )
    export_codebook.addAction(export_codebook_full)

    # Export small codebook
    export_codebook_small = QAction("Liten kodebok", self)
    export_codebook_small.setStatusTip("Eksporter liten kodebok som PDF")
    export_codebook_small.triggered.connect(
        lambda: generate_codebook_pdf(database=self.database, small=True)
    )
    export_codebook.addAction(export_codebook_small)

    # Hot keys
    self.action_shortcut_help = QAction("Hurtigtaster", self)
    self.action_shortcut_help.setStatusTip(
        "Vis oversikt over hurtigtaster"
    )
    help_menu.addAction(self.action_shortcut_help)

    # Easy use
    self.action_basic_use_help = QAction("Enkel bruk", self)
    self.action_basic_use_help.setStatusTip(
        "Vis enkel bruk av programvaren"
    )
    help_menu.addAction(self.action_basic_use_help)
def show_soi_db(self)

Open and select tab containing SOIDbWidget.

Select tab if it is already open, create and select tab if not open.

Expand source code
def show_soi_db(self):
    """Open and select tab containing SOIDbWidget.

    Select tab if it is already open,
    create and select tab if not open.
    """
    # 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 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 database")
        self.tabs.setCurrentWidget(tab)
def try_export_soi(self, medium=ExportMedium.COMPRESSED)

Export the SOI in the current tab.

Feedback is given through a dialog if the current tab does not contain an SOI (tab is not a SOIWorkspaceWidget).

Parameters

medium : ExportMedium
Which medium to export SOI to. Must be one of the enums in ExportMedium.

Raises

ValueError
If export medium is unknown.
Expand source code
def try_export_soi(self, medium=ExportMedium.COMPRESSED):
    """Export the SOI in the current tab.

    Feedback is given through a dialog if the current tab does not contain
    an SOI (tab is not a SOIWorkspaceWidget).

    Parameters
    ----------
    medium : ExportMedium
        Which medium to export SOI to. Must be one of the enums in
        ExportMedium.

    Raises
    ------
    ValueError
        If export medium is unknown.
    """
    tab_widget = self.tabs.currentWidget()

    # If tab contains an SOI
    if isinstance(tab_widget, SOIWorkspaceWidget):
        if medium == ExportMedium.COMPRESSED:
            export_soi(tab_widget.soi, True)
        elif medium == ExportMedium.UNCOMPRESSED:
            export_soi(tab_widget.soi, False)
        elif medium == ExportMedium.PDF:
            self.prompt_user_and_produce_pdf()
        else:
            raise ValueError(f"Unknown medium for export '{medium}'")
    else:
        exec_info_dialog(
            "Valgt tab er ingen SOI-tab",
            "Den valgte taben inneholder ingen SOI.\n"
            "For å eksportere en SOI må riktig tab velges.",
        )