Skip to content
Snippets Groups Projects
Commit ed21eb7c authored by Thomas Holene Løkkeborg's avatar Thomas Holene Løkkeborg
Browse files

Merge branch 'master' into bump-pdoc3-version-til-0-7-5

parents 63543f1d 8eddc2d4
No related branches found
No related tags found
1 merge request!19#37 bump pdoc3 versjon for å få bugfix
Pipeline #72800 passed
...@@ -5,6 +5,8 @@ stages: ...@@ -5,6 +5,8 @@ stages:
job_lint_flake8: job_lint_flake8:
stage: lint stage: lint
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
- flake8 --version - flake8 --version
...@@ -12,6 +14,8 @@ job_lint_flake8: ...@@ -12,6 +14,8 @@ job_lint_flake8:
job_lint_pylint: job_lint_pylint:
stage: lint stage: lint
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
- pylint --version - pylint --version
...@@ -19,6 +23,8 @@ job_lint_pylint: ...@@ -19,6 +23,8 @@ job_lint_pylint:
job_lint_bandit: job_lint_bandit:
stage: lint stage: lint
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
- bandit --version - bandit --version
...@@ -26,6 +32,8 @@ job_lint_bandit: ...@@ -26,6 +32,8 @@ job_lint_bandit:
job_lint_pydocstyle: job_lint_pydocstyle:
stage: lint stage: lint
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
- pydocstyle --version - pydocstyle --version
...@@ -33,6 +41,8 @@ job_lint_pydocstyle: ...@@ -33,6 +41,8 @@ job_lint_pydocstyle:
job_test_gui_ubuntu_vnc: job_test_gui_ubuntu_vnc:
stage: test stage: test
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
# -platform because running with a screen is not supported # -platform because running with a screen is not supported
...@@ -57,6 +67,8 @@ job_test_gui_ubuntu: ...@@ -57,6 +67,8 @@ job_test_gui_ubuntu:
job_pages_smoke_test: job_pages_smoke_test:
stage: deploy stage: deploy
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
- mkdir public - mkdir public
...@@ -70,6 +82,8 @@ job_pages_smoke_test: ...@@ -70,6 +82,8 @@ job_pages_smoke_test:
# name has to be pages # name has to be pages
pages: pages:
stage: deploy stage: deploy
tags:
- ci-ubuntu-executor-docker
image: morkolai/soitool-ci image: morkolai/soitool-ci
script: script:
- mkdir public - mkdir public
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
...@@ -286,7 +286,7 @@ indent-after-paren=4 ...@@ -286,7 +286,7 @@ indent-after-paren=4
indent-string=' ' indent-string=' '
# Maximum number of characters on a single line. # Maximum number of characters on a single line.
max-line-length=100 max-line-length=79
# Maximum number of lines in a module. # Maximum number of lines in a module.
max-module-lines=1000 max-module-lines=1000
...@@ -404,6 +404,8 @@ function-naming-style=snake_case ...@@ -404,6 +404,8 @@ function-naming-style=snake_case
# Good variable names which should always be accepted, separated by a comma. # Good variable names which should always be accepted, separated by a comma.
good-names=app, good-names=app,
x,
y,
ok, ok,
i, i,
j, j,
...@@ -503,7 +505,7 @@ valid-metaclass-classmethod-first-arg=cls ...@@ -503,7 +505,7 @@ valid-metaclass-classmethod-first-arg=cls
max-args=5 max-args=5
# Maximum number of attributes for a class (see R0902). # Maximum number of attributes for a class (see R0902).
max-attributes=7 max-attributes=15
# Maximum number of boolean expressions in an if statement. # Maximum number of boolean expressions in an if statement.
max-bool-expr=5 max-bool-expr=5
......
...@@ -6,11 +6,13 @@ out to separate modules. ...@@ -6,11 +6,13 @@ out to separate modules.
""" """
import sys import sys
import os import os
from PySide2.QtCore import Qt from PySide2.QtCore import QRectF, QPoint, QTimer, Qt
from PySide2.QtWidgets import QTabWidget, QWidget, QMainWindow, \ from PySide2.QtWidgets import QTabWidget, QWidget, QMainWindow, \
QApplication, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, \ QApplication, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, \
QAbstractItemView, QListWidget, QListWidgetItem, QAction QAbstractItemView, QListWidget, QListWidgetItem, QAction, QGraphicsScene, \
from PySide2.QtGui import QIcon QGraphicsView, QScrollArea, QGraphicsRectItem
from PySide2.QtGui import QBrush, QIcon, QPalette, QFont, QPixmap
from soitool.modules.module_table import TableModule
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
...@@ -110,6 +112,8 @@ class SOIWorkspaceWidget(QWidget): ...@@ -110,6 +112,8 @@ class SOIWorkspaceWidget(QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.soi = SOI()
self.layout_wrapper = QHBoxLayout() self.layout_wrapper = QHBoxLayout()
self.layout_sidebar = QVBoxLayout() self.layout_sidebar = QVBoxLayout()
...@@ -119,7 +123,9 @@ class SOIWorkspaceWidget(QWidget): ...@@ -119,7 +123,9 @@ class SOIWorkspaceWidget(QWidget):
self.button_new_module.setStatusTip("Legg til en ny modul") self.button_new_module.setStatusTip("Legg til en ny modul")
self.button_setup = QPushButton("Oppsett") self.button_setup = QPushButton("Oppsett")
self.list_modules = QListWidget() self.list_modules = QListWidget()
self.view = ViewArea() self.view = InlineEditableSOIView(self.soi)
self.widget_sidebar = QWidget()
self.widget_sidebar.setFixedWidth(200)
# prepare module list # prepare module list
self.setup_list_modules() self.setup_list_modules()
...@@ -132,10 +138,10 @@ class SOIWorkspaceWidget(QWidget): ...@@ -132,10 +138,10 @@ class SOIWorkspaceWidget(QWidget):
self.layout_sidebar.addWidget(QListWidget(), 1) self.layout_sidebar.addWidget(QListWidget(), 1)
self.layout_sidebar.addWidget(self.button_new_module) self.layout_sidebar.addWidget(self.button_new_module)
self.layout_sidebar.addWidget(self.button_setup) self.layout_sidebar.addWidget(self.button_setup)
self.layout_wrapper.addLayout(self.layout_sidebar) self.widget_sidebar.setLayout(self.layout_sidebar)
self.layout_wrapper.addWidget(self.widget_sidebar)
self.layout_wrapper.addWidget(self.view) self.layout_wrapper.addWidget(self.view)
self.setFixedWidth(200)
self.setLayout(self.layout_wrapper) self.setLayout(self.layout_wrapper)
def setup_list_modules(self): def setup_list_modules(self):
...@@ -168,19 +174,315 @@ class SOIWorkspaceWidget(QWidget): ...@@ -168,19 +174,315 @@ class SOIWorkspaceWidget(QWidget):
self.list_modules.insertItem(i, item) self.list_modules.insertItem(i, item)
class ViewArea(QWidget): class SOI():
"""Widget som kan byttes ut med view, edit etc.""" """Temporary representation of SOI.
Holds all info about an SOI necessary to view and edit it.
"""
A4_RATIO = 1.414
# height must be adjusted to something that will look right when real
# wigets are placed inside the pages
HEIGHT = 1700
WIDTH = HEIGHT * A4_RATIO
PADDING = 100
CONTENT_WIDTH = WIDTH - PADDING * 2
CONTENT_HEIGHT = HEIGHT - PADDING * 2
HEADER_HEIGHT = 100
MODULE_PADDING = 10
def __init__(self): def __init__(self):
# NOTE
# * test modules, just to have something show up on screen
# * not valid until reorganize has been run, as the positions must be
# updated
self.modules = [
{
"widget": TableModule(),
"meta": {
"x": 0,
"y": 0,
"page": 1
}
},
{
"widget": TableModule(),
"meta": {
"x": 0,
"y": 0,
"page": 1
}
},
{
"widget": TableModule(),
"meta": {
"x": 0,
"y": 0,
"page": 2
}
}
]
self.reorganize()
def update_module_widget_position(self, module):
"""Update position of module widget based on meta position.
This function is very much WIP..
Parameters
----------
module : see description
should be dict of fields "meta" and "widget", where "meta" is
itself a dict with fields "x", "y" and "page", and "widget" is a
widget based on "ModuleBase"
"""
distance_to_start_of_next_soi_content_y = self.CONTENT_HEIGHT + \
self.PADDING * 2 + self.HEADER_HEIGHT
scene_skip_distance_page_height = \
distance_to_start_of_next_soi_content_y * \
(module["meta"]["page"] - 1)
new_x = module["meta"]["x"] + self.PADDING
new_y = module["meta"]["y"] + self.PADDING + self.HEADER_HEIGHT + \
scene_skip_distance_page_height
module["widget"].set_pos(QPoint(new_x, new_y))
def reorganize(self):
"""Update x,y positions of modules.
In the future this is where rectpack will do it's magic, for now it is
a WIP just to get some kind of positioning working. The positioning
scheme for now is as follows: for each page 1 and 2, position widgets
next to each other from left to right
"""
x = self.MODULE_PADDING
first_page_modules = [module for module in self.modules
if module["meta"]["page"] == 1]
for module in first_page_modules:
module["meta"]["x"] = x
module["meta"]["y"] = self.MODULE_PADDING
self.update_module_widget_position(module)
widget_width, _ = module["widget"].get_size()
x = x + self.MODULE_PADDING + widget_width
# NOTE the following is simply duplicated.. left like this to KISS
# will be replaced by rectpack anyways
x = self.MODULE_PADDING
second_page_modules = [module for module in self.modules
if module["meta"]["page"] == 2]
for module in second_page_modules:
module["meta"]["x"] = x
module["meta"]["y"] = self.MODULE_PADDING
self.update_module_widget_position(module)
widget_width, _ = module["widget"].get_size()
x = x + self.MODULE_PADDING + widget_width
class InlineEditableSOIView(QScrollArea):
"""Widget som kan byttes ut med view, edit etc."""
def is_widget_in_scene(self, widget):
"""Indicate wether given widget already has a proxy in the scene."""
for page in self.pages:
for proxy in page:
if proxy.widget() == widget:
return True
return False
def setup_proxies(self):
"""Set up proxies for the widgets of SOI modules."""
for module in self.soi.modules:
if not self.is_widget_in_scene(module["widget"]):
proxy = self.scene.addWidget(module["widget"])
while len(self.pages) < module["meta"]["page"]:
self.pages.append(set())
self.pages[module["meta"]["page"] - 1].add(proxy)
def mousePressEvent(self, _):
"""Reorganize modules when pressed.
This is a temporary way to activate reorganization of widgets. Note
that will not be triggered by clicks on modules in the scene
"""
self.soi.reorganize()
def __init__(self, soi):
super().__init__() super().__init__()
test = QLabel("Test")
layout = QHBoxLayout() self.soi = soi
layout.addWidget(test)
self.setLayout(layout) self.pages = []
# necessary to make the scroll area fill the space it's given
self.setWidgetResizable(True)
# scene and view widgets
self.scene = QGraphicsScene()
self.setup_scene()
self.view = QGraphicsView(self.scene)
self.setWidget(self.view)
self.setup_proxies()
self.update_drawn_pages()
# self.launch_auto_zoom()
def update_drawn_pages(self):
"""Update drawn pages to reflect self.pages."""
for i, modules in enumerate(self.pages, start=1):
x = 0
y = self.soi.HEIGHT * max(i - 1, 0) + \
self.soi.PADDING * max(i - 1, 0)
# adjust page size
full_scene_height = y + self.soi.HEIGHT
self.scene.setSceneRect(QRectF(0, 0, self.soi.WIDTH,
full_scene_height))
self.draw_page(x, y)
self.draw_header(x + self.soi.PADDING, y + self.soi.PADDING, i)
for module in modules:
# redraw of pages requires modules to be moved to front again
module.setZValue(1)
def draw_header(self, x, y, page_number):
"""Draw header staring at given position.
Parameters
----------
x : int
y : int
"""
# title
label_title = QLabel("SOI TITLE HERE")
label_title.move(x, y)
label_title.setStyleSheet("background-color: rgba(0,0,0,0%)")
label_title.setFont(QFont("Times New Roman", 50))
self.scene.addWidget(label_title)
# page numbering
page_number = QLabel("{} av {}".format(page_number, len(self.pages)))
page_number.setStyleSheet("background-color: rgba(0,0,0,0%)")
page_number.setFont(QFont("Times New Roman", 50))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = \
page_number.fontMetrics().boundingRect(page_number.text()).width()
page_number.move(x + (self.soi.CONTENT_WIDTH - label_width) / 2, y)
self.scene.addWidget(page_number)
# grading
grading = QLabel("UGRADERT")
grading.setStyleSheet("background-color: rgba(0,0,0,0%); color: red")
grading.setFont(QFont("Times New Roman", 50))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = \
grading.fontMetrics().boundingRect(grading.text()).width()
x_pos = x + self.soi.CONTENT_WIDTH - label_width - \
self.soi.HEADER_HEIGHT
grading.move(x_pos, y)
self.scene.addWidget(grading)
# patch
pixmap = QPixmap("soitool/media/HVlogo.png")
patch = QLabel()
patch.setPixmap(pixmap.scaled(self.soi.HEADER_HEIGHT,
self.soi.HEADER_HEIGHT))
patch.move(x + self.soi.CONTENT_WIDTH - self.soi.HEADER_HEIGHT, y)
self.scene.addWidget(patch)
def draw_page(self, x, y):
"""Draw page starting at given position.
Parameters
----------
x : int
y : int
"""
# color the page white
page_background = QGraphicsRectItem(x, y, self.soi.WIDTH,
self.soi.HEIGHT)
page_background.setBrush(QBrush(Qt.white))
self.scene.addItem(page_background)
# draw borders
self.scene.addRect(x, y, self.soi.WIDTH, self.soi.HEIGHT)
self.scene.addRect(x + self.soi.PADDING, y + self.soi.PADDING,
self.soi.CONTENT_WIDTH, self.soi.CONTENT_HEIGHT)
self.scene.addRect(x + self.soi.PADDING, y + self.soi.PADDING,
self.soi.CONTENT_WIDTH, self.soi.CONTENT_HEIGHT)
self.scene.addRect(x + self.soi.PADDING, y + self.soi.PADDING,
self.soi.CONTENT_WIDTH, self.soi.HEADER_HEIGHT)
def setup_scene(self):
"""Prepare scene for use.
Draws borders, background, etc.
"""
# sets the background color of the scene to be the appropriate
# system-specific background color
# source: https://stackoverflow.com/a/23880531/3545896
# source: https://stackoverflow.com/questions/15519749/how-to-get-widget-background-qcolor
self.scene.setBackgroundBrush(app.palette().color(QPalette.Window))
self.update_drawn_pages()
def launch_auto_zoom(self):
"""Zoom in a regular interval.
Used to demonstrate zooming only, should be removed once the project
matures.
"""
def do_on_timeout():
self.zoom(1 / 1.00005)
timer = QTimer(self)
timer.timeout.connect(do_on_timeout)
timer.start(10)
def zoom(self, zoom_factor):
"""Zoom GraphicsView by zoom_factor.
source: https://stackoverflow.com/a/29026916/3545896
Parameters
----------
zoom_factor : float
> 1 to zoom in and < 1 to zoom out
"""
# Set Anchors
self.view.setTransformationAnchor(QGraphicsView.NoAnchor)
self.view.setResizeAnchor(QGraphicsView.NoAnchor)
# Zoom
self.view.scale(zoom_factor, zoom_factor)
pos_old = self.view.mapToScene(QPoint(self.soi.WIDTH / 2,
self.soi.HEIGHT / 2))
pos_new = self.view.mapToScene(self.soi.WIDTH / 2, self.soi.HEIGHT / 2)
delta = pos_new - pos_old
self.view.translate(delta.x(), delta.y())
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
WINDOW = MainWindow() WINDOW = MainWindow()
WINDOW.show() WINDOW.showMaximized()
app.exec_() app.exec_()
...@@ -9,6 +9,10 @@ class ModuleBase(ABC): ...@@ -9,6 +9,10 @@ class ModuleBase(ABC):
"""Abstract method, should be implemented by derived class.""" """Abstract method, should be implemented by derived class."""
raise NotImplementedError raise NotImplementedError
def set_pos(self, pos):
"""Abstract method, should be implemented by derived class."""
raise NotImplementedError
def render_onto_pdf(self): def render_onto_pdf(self):
"""Abstract method, should be implemented by derived class.""" """Abstract method, should be implemented by derived class."""
raise NotImplementedError raise NotImplementedError
...@@ -27,6 +27,10 @@ class TableModule(ModuleBase, QTableWidget, metaclass=Meta): ...@@ -27,6 +27,10 @@ class TableModule(ModuleBase, QTableWidget, metaclass=Meta):
Has shortcuts for adding and removing rows and columns. Has shortcuts for adding and removing rows and columns.
""" """
def set_pos(self, pos):
"""Set position of widget."""
self.move(pos)
def __init__(self): def __init__(self):
QTableWidget.__init__(self) QTableWidget.__init__(self)
super(QTableWidget) super(QTableWidget)
......
...@@ -59,13 +59,19 @@ class TestMain(unittest.TestCase): ...@@ -59,13 +59,19 @@ class TestMain(unittest.TestCase):
"""Test at endring av tekst funker.""" """Test at endring av tekst funker."""
def change_text_and_ok(): def change_text_and_ok():
while self.widget.dlg_input is None: # in PySide2 we need to store a reference to this. If we don't the
app.processEvents() # widget is garbage collected somehow before we get to use
child_line_edit = self.widget.dlg_input.findChild(QtWidgets.QLineEdit) # child_line_edit (a child of the active widget)
active_widget = app.activeModalWidget()
child_line_edit = active_widget.findChild(
QtWidgets.QLineEdit
)
QtTest.QTest.keyClicks(child_line_edit, self.test_text2) QtTest.QTest.keyClicks(child_line_edit, self.test_text2)
QtTest.QTest.keyClick(child_line_edit, QtCore.Qt.Key_Enter) QtTest.QTest.keyClick(child_line_edit, QtCore.Qt.Key_Enter)
QtCore.QTimer.singleShot(100, change_text_and_ok) QtCore.QTimer.singleShot(0, change_text_and_ok)
QtTest.QTest.mouseClick(self.widget.button, QtCore.Qt.LeftButton) QtTest.QTest.mouseClick(self.widget.button, QtCore.Qt.LeftButton)
self.assertEqual( self.assertEqual(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment