Module soitool.modules.module_base

Base/interface of each module.

Expand source code
"""Base/interface of each module."""
from abc import ABC
from PySide2 import QtGui
from PySide2.QtCore import Qt


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


# Fonts for modules
HEADLINE_FONT = qfont_with_pixel_size("Times New Roman", 34, 100)
SUB_HEADLINE_FONT = qfont_with_pixel_size("Times New Roman", 26, 100)
DEFAULT_FONT = qfont_with_pixel_size("Times New Roman", 23)


class ModuleBase(ABC):
    """Interface for SOI-modules."""

    type = None

    def __init__(self):
        """Class-variable 'type' should be set by derived class."""
        if self.type is None:
            raise NotImplementedError
        self.headline_font = HEADLINE_FONT
        self.setFont(DEFAULT_FONT)

    def get_size(self):
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    def get_data(self):
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    def prepare_for_pdf_export(self):
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    @staticmethod
    def get_user_friendly_name():
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    @staticmethod
    def get_icon():
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError


def resize_table(table, columns=True, has_headline=False):
    """Resize a given QTableWidget.

    On purpose not resizing rows, as this causes different heights from screen
    to screen.

    Parameters
    ----------
    table : QTableWidget
        QTablewidget-instance to resize.
    columns : bool
        Resizes columns to contents if True, by default True.
    has_headline : bool
        True if the table has a headline, by default False.
        Last column is widened if headline is wider than table.
    """
    if columns:
        table.resizeColumnsToContents()

    # If table has a headline, make sure table is wide enough to fit it.
    if has_headline:
        last_column_index = table.columnCount() - 1
        table.resizeColumnToContents(last_column_index)
        width, height = get_table_size(table)

        # Get width of headline
        headline = table.item(0, 0).text()
        headline_width = (
            QtGui.QFontMetricsF(HEADLINE_FONT).horizontalAdvance(headline) + 10
        )
        # If headline is wider than table
        if width < headline_width:
            difference = headline_width - width
            width += difference
            old_width = table.columnWidth(last_column_index)
            table.setColumnWidth(last_column_index, old_width + difference)
    else:
        width, height = get_table_size(table)

    table.setFixedWidth(width)
    table.setFixedHeight(height)


def get_table_size(widget):
    """Calculate and return total width and height of a QTableWidget.

    Parameters
    ----------
    widget : QTableWidget
        QTableWidget-instance to calculate and return size of.

    Returns
    -------
    Tuple
        Total (width, height)
    """
    width = 0
    height = 0

    for i in range(widget.columnCount()):
        width += widget.columnWidth(i)

    for i in range(widget.rowCount()):
        height += widget.rowHeight(i)

    # Without the following the size is always slightly off, apparently by 2
    # px. This might be due to borders around the table
    width += 2
    height += 2

    return width, height


def set_module_pos(widget, pos):
    """Set position of module (widget).

    Parameters
    ----------
    widget : QWidget
        Widget to move.
    pos : QPoint
        Position (x, y).
    """
    widget.move(pos)


def prepare_table_for_pdf_export(widget):
    """Prepare QTableWidget for PDF-export.

    Deselect cells and clear focus.

    Parameters
    ----------
    widget : QTableWidget
        Table to prepare.
    """
    widget.clearSelection()
    widget.clearFocus()


def prepare_line_edit_for_pdf_export(widget):
    """Prepare QLineEdit for PDF-export.

    Deselect text and clear focus.

    Parameters
    ----------
    widget : QLineEdit
        QLineEdit to prepare.
    """
    widget.deselect()
    widget.clearFocus()


def prepare_text_edit_for_pdf_export(widget):
    """Prepare QTextEdit for PDF-export.

    Deselect text and clear focus.

    Parameters
    ----------
    widget : QTextEdit
        QTextEdit to prepare.
    """
    text_cursor = widget.textCursor()
    text_cursor.clearSelection()
    widget.setTextCursor(text_cursor)

    widget.clearFocus()


def is_event_add_row(event):
    """Check if the event is 'CTRL +'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'CTRL +'.
    """
    return (
        event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_Plus
    )


def is_event_remove_row(event):
    """Check if the event is 'CTRL -'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'CTRL -'.
    """
    # Underline is in practice minus on some computers in this situation. We
    # don't know why. For this reason we check both for underline and minus
    return event.modifiers() == Qt.ControlModifier and (
        event.key() == Qt.Key_Underscore or event.key() == Qt.Key_Minus
    )


def is_event_add_column(event):
    """Check if the event is 'SHIFT +'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'SHIFT +'.
    """
    return event.key() == Qt.Key_Question


def is_event_remove_column(event):
    """Check if the event is 'SHIFT -'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'SHIFT -'.
    """
    # Underline is in practice minus on some computers in this situation. We
    # don't know why. For this reason we check both for underline and minus
    return event.modifiers() == Qt.ShiftModifier and (
        event.key() == Qt.Key_Underscore or event.key() == Qt.Key_Minus
    )


def is_event_edit_module(event):
    """Check if the event is 'Ctrl R'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'Ctrl R', false otherwise.
    """
    return event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_R

Functions

def get_table_size(widget)

Calculate and return total width and height of a QTableWidget.

Parameters

widget : QTableWidget
QTableWidget-instance to calculate and return size of.

Returns

Tuple
Total (width, height)
Expand source code
def get_table_size(widget):
    """Calculate and return total width and height of a QTableWidget.

    Parameters
    ----------
    widget : QTableWidget
        QTableWidget-instance to calculate and return size of.

    Returns
    -------
    Tuple
        Total (width, height)
    """
    width = 0
    height = 0

    for i in range(widget.columnCount()):
        width += widget.columnWidth(i)

    for i in range(widget.rowCount()):
        height += widget.rowHeight(i)

    # Without the following the size is always slightly off, apparently by 2
    # px. This might be due to borders around the table
    width += 2
    height += 2

    return width, height
def is_event_add_column(event)

Check if the event is 'SHIFT +'.

Parameters

event : QKeyEvent
Event to check.

Returns

bool
True if event is 'SHIFT +'.
Expand source code
def is_event_add_column(event):
    """Check if the event is 'SHIFT +'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'SHIFT +'.
    """
    return event.key() == Qt.Key_Question
def is_event_add_row(event)

Check if the event is 'CTRL +'.

Parameters

event : QKeyEvent
Event to check.

Returns

bool
True if event is 'CTRL +'.
Expand source code
def is_event_add_row(event):
    """Check if the event is 'CTRL +'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'CTRL +'.
    """
    return (
        event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_Plus
    )
def is_event_edit_module(event)

Check if the event is 'Ctrl R'.

Parameters

event : QKeyEvent
Event to check.

Returns

bool
True if event is 'Ctrl R', false otherwise.
Expand source code
def is_event_edit_module(event):
    """Check if the event is 'Ctrl R'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'Ctrl R', false otherwise.
    """
    return event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_R
def is_event_remove_column(event)

Check if the event is 'SHIFT -'.

Parameters

event : QKeyEvent
Event to check.

Returns

bool
True if event is 'SHIFT -'.
Expand source code
def is_event_remove_column(event):
    """Check if the event is 'SHIFT -'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'SHIFT -'.
    """
    # Underline is in practice minus on some computers in this situation. We
    # don't know why. For this reason we check both for underline and minus
    return event.modifiers() == Qt.ShiftModifier and (
        event.key() == Qt.Key_Underscore or event.key() == Qt.Key_Minus
    )
def is_event_remove_row(event)

Check if the event is 'CTRL -'.

Parameters

event : QKeyEvent
Event to check.

Returns

bool
True if event is 'CTRL -'.
Expand source code
def is_event_remove_row(event):
    """Check if the event is 'CTRL -'.

    Parameters
    ----------
    event : QKeyEvent
        Event to check.

    Returns
    -------
    bool
        True if event is 'CTRL -'.
    """
    # Underline is in practice minus on some computers in this situation. We
    # don't know why. For this reason we check both for underline and minus
    return event.modifiers() == Qt.ControlModifier and (
        event.key() == Qt.Key_Underscore or event.key() == Qt.Key_Minus
    )
def prepare_line_edit_for_pdf_export(widget)

Prepare QLineEdit for PDF-export.

Deselect text and clear focus.

Parameters

widget : QLineEdit
QLineEdit to prepare.
Expand source code
def prepare_line_edit_for_pdf_export(widget):
    """Prepare QLineEdit for PDF-export.

    Deselect text and clear focus.

    Parameters
    ----------
    widget : QLineEdit
        QLineEdit to prepare.
    """
    widget.deselect()
    widget.clearFocus()
def prepare_table_for_pdf_export(widget)

Prepare QTableWidget for PDF-export.

Deselect cells and clear focus.

Parameters

widget : QTableWidget
Table to prepare.
Expand source code
def prepare_table_for_pdf_export(widget):
    """Prepare QTableWidget for PDF-export.

    Deselect cells and clear focus.

    Parameters
    ----------
    widget : QTableWidget
        Table to prepare.
    """
    widget.clearSelection()
    widget.clearFocus()
def prepare_text_edit_for_pdf_export(widget)

Prepare QTextEdit for PDF-export.

Deselect text and clear focus.

Parameters

widget : QTextEdit
QTextEdit to prepare.
Expand source code
def prepare_text_edit_for_pdf_export(widget):
    """Prepare QTextEdit for PDF-export.

    Deselect text and clear focus.

    Parameters
    ----------
    widget : QTextEdit
        QTextEdit to prepare.
    """
    text_cursor = widget.textCursor()
    text_cursor.clearSelection()
    widget.setTextCursor(text_cursor)

    widget.clearFocus()
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
Expand source code
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
def resize_table(table, columns=True, has_headline=False)

Resize a given QTableWidget.

On purpose not resizing rows, as this causes different heights from screen to screen.

Parameters

table : QTableWidget
QTablewidget-instance to resize.
columns : bool
Resizes columns to contents if True, by default True.
has_headline : bool
True if the table has a headline, by default False. Last column is widened if headline is wider than table.
Expand source code
def resize_table(table, columns=True, has_headline=False):
    """Resize a given QTableWidget.

    On purpose not resizing rows, as this causes different heights from screen
    to screen.

    Parameters
    ----------
    table : QTableWidget
        QTablewidget-instance to resize.
    columns : bool
        Resizes columns to contents if True, by default True.
    has_headline : bool
        True if the table has a headline, by default False.
        Last column is widened if headline is wider than table.
    """
    if columns:
        table.resizeColumnsToContents()

    # If table has a headline, make sure table is wide enough to fit it.
    if has_headline:
        last_column_index = table.columnCount() - 1
        table.resizeColumnToContents(last_column_index)
        width, height = get_table_size(table)

        # Get width of headline
        headline = table.item(0, 0).text()
        headline_width = (
            QtGui.QFontMetricsF(HEADLINE_FONT).horizontalAdvance(headline) + 10
        )
        # If headline is wider than table
        if width < headline_width:
            difference = headline_width - width
            width += difference
            old_width = table.columnWidth(last_column_index)
            table.setColumnWidth(last_column_index, old_width + difference)
    else:
        width, height = get_table_size(table)

    table.setFixedWidth(width)
    table.setFixedHeight(height)
def set_module_pos(widget, pos)

Set position of module (widget).

Parameters

widget : QWidget
Widget to move.
pos : QPoint
Position (x, y).
Expand source code
def set_module_pos(widget, pos):
    """Set position of module (widget).

    Parameters
    ----------
    widget : QWidget
        Widget to move.
    pos : QPoint
        Position (x, y).
    """
    widget.move(pos)

Classes

class ModuleBase

Interface for SOI-modules.

Class-variable 'type' should be set by derived class.

Expand source code
class ModuleBase(ABC):
    """Interface for SOI-modules."""

    type = None

    def __init__(self):
        """Class-variable 'type' should be set by derived class."""
        if self.type is None:
            raise NotImplementedError
        self.headline_font = HEADLINE_FONT
        self.setFont(DEFAULT_FONT)

    def get_size(self):
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    def get_data(self):
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    def prepare_for_pdf_export(self):
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    @staticmethod
    def get_user_friendly_name():
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

    @staticmethod
    def get_icon():
        """Abstract method, should be implemented by derived class."""
        raise NotImplementedError

Ancestors

  • abc.ABC

Subclasses

Class variables

var type

Static methods

def get_icon()

Abstract method, should be implemented by derived class.

Expand source code
@staticmethod
def get_icon():
    """Abstract method, should be implemented by derived class."""
    raise NotImplementedError
def get_user_friendly_name()

Abstract method, should be implemented by derived class.

Expand source code
@staticmethod
def get_user_friendly_name():
    """Abstract method, should be implemented by derived class."""
    raise NotImplementedError

Methods

def get_data(self)

Abstract method, should be implemented by derived class.

Expand source code
def get_data(self):
    """Abstract method, should be implemented by derived class."""
    raise NotImplementedError
def get_size(self)

Abstract method, should be implemented by derived class.

Expand source code
def get_size(self):
    """Abstract method, should be implemented by derived class."""
    raise NotImplementedError
def prepare_for_pdf_export(self)

Abstract method, should be implemented by derived class.

Expand source code
def prepare_for_pdf_export(self):
    """Abstract method, should be implemented by derived class."""
    raise NotImplementedError