diff --git a/scripts/.pylintrc b/scripts/.pylintrc index a4c463b20ed3dc0db7b8e1f7e3e4d233720719e9..17b783052542f58797737e105ec35f5d0669a615 100644 --- a/scripts/.pylintrc +++ b/scripts/.pylintrc @@ -141,7 +141,8 @@ disable=print-statement, comprehension-escape, E0611, I1101, - E1101 + E1101, + R0901 # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/soitool/main_window.py b/soitool/main_window.py index 2a50c4d2daf3953e286e9011cc3765674019500f..4f00083a241c9c34de110ed709e7266168c88b8a 100644 --- a/soitool/main_window.py +++ b/soitool/main_window.py @@ -1,10 +1,16 @@ -"""Hovedvinduet.""" +"""MainWindow. + +During initial development this module will include most of the code necessary +to get our project up-and-running. When our project matures we will move code +out to separate modules. +""" import sys import os from PySide2.QtCore import QRectF, QPoint, QTimer, Qt -from PySide2.QtWidgets import QTabWidget, QWidget, QMainWindow, QApplication, \ - QHBoxLayout, QVBoxLayout, QPushButton, QTreeWidget, QGraphicsScene, \ - QGraphicsView, QAction, QScrollArea, QGraphicsRectItem +from PySide2.QtWidgets import QTabWidget, QWidget, QMainWindow, \ + QApplication, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, \ + QAbstractItemView, QListWidget, QListWidgetItem, QAction, QGraphicsScene, \ + QGraphicsView, QScrollArea, QGraphicsRectItem from PySide2.QtGui import QBrush, QIcon, QPalette @@ -83,7 +89,7 @@ class MainWindow(QMainWindow): # Legger til MainWidget som en tab, kanskje flytt ut til egen funksjon tabs = QTabWidget() - tab = MainWidget() + tab = SOIWorkspaceWidget() tabs.addTab(tab, "MainTab") self.setCentralWidget(tabs) @@ -95,30 +101,74 @@ class MainWindow(QMainWindow): self.setWindowIcon(QIcon(filepath)) -class MainWidget(QWidget): - """Hovedwidget til applikasjonen, dette er det som blir tabs.""" +class SOIWorkspaceWidget(QWidget): + """Contains the working area for a single SOI. + + The widget is used inside tabs in our application, and contains a sidebar + with a module list along with a view of the SOI. + """ def __init__(self): super().__init__() - layout1 = QHBoxLayout() - layout2 = QVBoxLayout() - # New module button - new_module = QPushButton("Ny modul") - new_module.setShortcut("Ctrl+m") - new_module.setStatusTip("Legg til en ny modul") + self.layout_wrapper = QHBoxLayout() + self.layout_sidebar = QVBoxLayout() + + # all widgets + self.button_new_module = QPushButton("Ny modul") + self.button_new_module.setShortcut("Ctrl+m") + self.button_new_module.setStatusTip("Legg til en ny modul") + self.button_setup = QPushButton("Oppsett") + self.list_modules = QListWidget() + self.view = ViewArea() + self.widget_sidebar = QWidget() + self.widget_sidebar.setFixedWidth(200) + + # prepare module list + self.setup_list_modules() + self.fill_list_modules() + + # build layouts + self.layout_sidebar.addWidget(QLabel("Moduler:")) + self.layout_sidebar.addWidget(self.list_modules, 5) + self.layout_sidebar.addWidget(QLabel("Vedlegg:")) + self.layout_sidebar.addWidget(QListWidget(), 1) + self.layout_sidebar.addWidget(self.button_new_module) + self.layout_sidebar.addWidget(self.button_setup) + self.widget_sidebar.setLayout(self.layout_sidebar) + self.layout_wrapper.addWidget(self.widget_sidebar) + self.layout_wrapper.addWidget(self.view) + + self.setLayout(self.layout_wrapper) + + def setup_list_modules(self): + """Prepare module list. + + The list contains modules that are drag-and-droppable. + """ + # enable drag-and-drop + self.list_modules.setDragEnabled(True) + self.list_modules.viewport().setAcceptDrops(True) + self.list_modules.viewport().setAcceptDrops(True) + self.list_modules.setDragDropMode(QAbstractItemView.InternalMove) - tree_view = QTreeWidget() - setup = QPushButton("Oppsett") - layout2.addWidget(tree_view) - layout2.addWidget(new_module) - layout2.addWidget(setup) + # source: https://www.qtcentre.org/threads/32500-Horizontal-Scrolling-QListWidget + self.list_modules.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - view = ViewArea() - layout1.addLayout(layout2, 2) - layout1.addWidget(view, 8) + def fill_list_modules(self): + """Fill module list with some items to manually test with. - self.setLayout(layout1) + This function will be removed when we can fill the list properly. + """ + # hardcode some items + items = [ + QListWidgetItem("Frekvenstabell"), + QListWidgetItem("Sambandsdiagram"), + QListWidgetItem("Autentifiseringstavle"), + QListWidgetItem("Subtraktorkoder"), + ] + for i, item in enumerate(items): + self.list_modules.insertItem(i, item) class ViewArea(QWidget): diff --git a/soitool/modules/module_base.py b/soitool/modules/module_base.py new file mode 100644 index 0000000000000000000000000000000000000000..92e7e6bc6145a1a1921cb5580c7b8384d77cad51 --- /dev/null +++ b/soitool/modules/module_base.py @@ -0,0 +1,14 @@ +"""Base/interface of each module.""" +from abc import ABC + + +class ModuleBase(ABC): + """Interface for SOI-modules.""" + + def get_size(self): + """Abstract method, should be implemented by derived class.""" + raise NotImplementedError + + def render_onto_pdf(self): + """Abstract method, should be implemented by derived class.""" + raise NotImplementedError diff --git a/soitool/modules/table_module.py b/soitool/modules/table_module.py new file mode 100644 index 0000000000000000000000000000000000000000..50a68289d164a6f0a6ed99d02250ca0a561c6413 --- /dev/null +++ b/soitool/modules/table_module.py @@ -0,0 +1,128 @@ +"""Module containing subclassed SOIModule (QTableWidget, ModuleBase).""" +from PySide2.QtWidgets import QTableWidget, QTableWidgetItem, QShortcut +from PySide2 import QtGui +from module_base import ModuleBase + +HEADER_FONT = QtGui.QFont() +HEADER_FONT.setFamily('Arial') +HEADER_FONT.setPointSize(12) +HEADER_FONT.setWeight(100) + + +class Meta(type(ModuleBase), type(QTableWidget)): + """Used as a metaclass to enable multiple inheritance.""" + + +class TableModule(ModuleBase, QTableWidget, metaclass=Meta): + """Modified QTableWidget. + + Has shortcuts for adding and removing rows and columns. + Also inherits functions from ModuleBase, which is implemented by this class. + """ + + def __init__(self): + """Initialize QTableWidget.""" + QTableWidget.__init__(self) + super(QTableWidget) + + self.horizontalHeader().hide() + self.verticalHeader().hide() + + self.setColumnCount(2) + self.setRowCount(2) + + self.set_header_item(0, "") + self.set_header_item(1, "") + + self.resizeRowsToContents() + self.resizeColumnsToContents() + + self.cellChanged.connect(self.cell_changed) + + self.set_shortcuts() + + def set_header_item(self, column, text): + """Insert item with header-style. + + Item will always be set on header row (top row). + + Parameters + ---------- + column : int + What column index for inserting item. + text : String + What text the item should contain. + """ + item = QTableWidgetItem(text) + item.setBackground(QtGui.QBrush(QtGui.QColor(220, 220, 220))) + item.setFont(HEADER_FONT) + self.setItem(0, column, item) + + def set_shortcuts(self): + """Set shortcuts for adding and removing rows and columns.""" + shortcut_add_col = QShortcut(QtGui.QKeySequence("Shift++"), self) + shortcut_rem_col = QShortcut(QtGui.QKeySequence("Shift+-"), self) + shortcut_add_row = QShortcut(QtGui.QKeySequence("Ctrl++"), self) + shortcut_rem_row = QShortcut(QtGui.QKeySequence("Ctrl+-"), self) + + shortcut_add_col.activated.connect(self.add_column) + shortcut_rem_col.activated.connect(self.remove_column) + shortcut_add_row.activated.connect(self.add_row) + shortcut_rem_row.activated.connect(self.remove_row) + + def add_column(self): + """Add column to the right of selected column.""" + self.insertColumn(self.columnCount()) + + # From selected column, move all columns one step to the right + to_pos = self.columnCount() - 2 # to_pos is target index for columns + while to_pos > self.currentColumn(): + # Loop through all rows + for j in range(self.rowCount()): + item = self.takeItem(j, to_pos) + self.setItem(j, to_pos + 1, item) + to_pos -= 1 + + self.set_header_item(self.currentColumn() + 1, "") + self.resizeColumnsToContents() + + def remove_column(self): + """Remove selected column if two or more columns exist.""" + if self.columnCount() > 1: + self.removeColumn(self.currentColumn()) + + def add_row(self): + """Add row below selected row.""" + self.insertRow(self.currentRow() + 1) + self.resizeRowsToContents() + + def remove_row(self): + """Remove selected row if two or more rows exist (including header).""" + if self.rowCount() > 2: + self.removeRow(self.currentRow()) + + def cell_changed(self): + """Resize rows and columns to contents when a cell changes.""" + self.resizeColumnsToContents() + self.resizeRowsToContents() + + def get_size(self): + """Get size of widget. + + Returns + ------- + Tuple + (width, height) (total) + """ + width = 0 + for i in range(self.columnCount()): + width += self.columnWidth(i) + + height = 0 + for i in range(self.columnCount()): + height += self.rowHeight(i) + + return width, height + + def render_onto_pdf(self): + """Render onto pdf."""