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

#49 redesign av header + nødvendige test & doc oppdateringer

- oppdatert "anatomy of an SOI" forklaring i SOI docstring
- Fikset CONTENT_WIDTH og CONTENT_HEIGHT nå referer til bredde,høyde brukbar til plassering av moduler
parent ccecc4f3
No related branches found
No related tags found
1 merge request!54#104 & #49: PDF som menyvalg & nytt header design
Pipeline #80445 failed
"""Includes functionality for inline editing of SOI."""
from datetime import datetime
from PySide2.QtCore import Qt, QRectF, QTimer, QPoint, QMarginsF
from PySide2.QtWidgets import (
QApplication,
......@@ -12,9 +13,7 @@ from PySide2.QtGui import QFont, QPixmap, QBrush, QPalette, QPainter
from PySide2.QtPrintSupport import QPrinter
from soitool.soi import ModuleLargerThanBinError
from soitool.dialog_wrappers import exec_warning_dialog
from soitool.serialize_export_import_soi import (
generate_soi_filename,
)
from soitool.serialize_export_import_soi import generate_soi_filename
class InlineEditableSOIView(QScrollArea):
......@@ -180,9 +179,16 @@ class InlineEditableSOIView(QScrollArea):
self.update_number_of_pages()
self.draw_pages()
# Ignoring Pylint's errors "Too many local variables" and
# "Too many statements" for this function because it's doing GUI layout
# work, which in nature is tedious and repetitive.
# pylint: disable=R0914,R0915
def draw_header(self, x, y, page_number):
"""Draw header staring at given position.
Source for rotation approach:
* https://stackoverflow.com/a/43389394/3545896
Parameters
----------
x : int
......@@ -190,31 +196,101 @@ class InlineEditableSOIView(QScrollArea):
"""
# title
label_title = QLabel(self.soi.title)
label_title.move(x, y)
label_title.move(x, y + self.soi.HEADER_HEIGHT)
label_title.setStyleSheet("background-color: rgba(0,0,0,0%)")
label_title.setFont(QFont("Times New Roman", 50))
self.scene.addWidget(label_title)
label_title.setFont(QFont("Times New Roman", 35))
proxy = self.scene.addWidget(label_title)
proxy.setRotation(-90)
# description
label_description = QLabel(self.soi.description)
label_description.move(x + 57, y + self.soi.HEADER_HEIGHT)
label_description.setStyleSheet("background-color: rgba(0,0,0,0%)")
label_description.setFont(QFont("Times New Roman", 15))
proxy = self.scene.addWidget(label_description)
proxy.setRotation(-90)
# creation date
creation_date = soi_date_string_to_user_friendly_string(self.soi.date)
label_creation_date = QLabel("Opprettet: {}".format(creation_date))
label_creation_date.setStyleSheet("background-color: rgba(0,0,0,0%)")
label_creation_date.setFont(QFont("Times New Roman", 15))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = (
label_creation_date.fontMetrics()
.boundingRect(label_creation_date.text())
.width()
)
label_creation_date.move(x + 80, y + self.soi.HEADER_HEIGHT)
proxy = self.scene.addWidget(label_creation_date)
proxy.setRotation(-90)
# patch
pixmap = QPixmap(self.soi.icon)
patch = QLabel()
patch.setPixmap(
pixmap.scaled(self.soi.HEADER_WIDTH - 2, self.soi.HEADER_WIDTH - 2)
)
patch.move(
x + 1, y + self.soi.HEADER_HEIGHT / 2 + self.soi.HEADER_WIDTH + 10
)
proxy = self.scene.addWidget(patch)
proxy.setRotation(-90)
# copy number
copy_number = QLabel("1 av 20")
copy_number.setStyleSheet("background-color: rgba(0,0,0,0%)")
copy_number.setFont(QFont("Times New Roman", 40))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = (
copy_number.fontMetrics().boundingRect(copy_number.text()).width()
)
# store for usage when placing copy number title and page number
copy_number_y_pos = y + self.soi.HEADER_HEIGHT / 2 - 100
copy_number.move(x + 18, copy_number_y_pos + label_width / 2)
proxy = self.scene.addWidget(copy_number)
proxy.setRotation(-90)
# copy number title
label_copy_number_header = QLabel("Eksemplar")
label_copy_number_header.setStyleSheet(
"background-color: rgba(0,0,0,0%)"
)
label_copy_number_header.setFont(QFont("Times New Roman", 15))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = (
label_copy_number_header.fontMetrics()
.boundingRect(label_copy_number_header.text())
.width()
)
label_copy_number_header.move(x, copy_number_y_pos + label_width / 2)
proxy = self.scene.addWidget(label_copy_number_header)
proxy.setRotation(-90)
# page numbering
page_number = QLabel(
"{} av {}".format(page_number, self.number_of_pages)
"Side {} av {}".format(page_number, self.number_of_pages)
)
page_number.setStyleSheet("background-color: rgba(0,0,0,0%)")
page_number.setFont(QFont("Times New Roman", 50))
page_number.setFont(QFont("Times New Roman", 15))
# 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)
page_number.move(x + 85, copy_number_y_pos + label_width / 2)
proxy = self.scene.addWidget(page_number)
proxy.setRotation(-90)
# classification
classification = QLabel(self.soi.classification)
classification.setStyleSheet(
"background-color: rgba(0,0,0,0%); " "color: red"
)
classification.setFont(QFont("Times New Roman", 50))
classification.setFont(QFont("Times New Roman", 35))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = (
......@@ -222,20 +298,43 @@ class InlineEditableSOIView(QScrollArea):
.boundingRect(classification.text())
.width()
)
x_pos = (
x + self.soi.CONTENT_WIDTH - label_width - self.soi.HEADER_HEIGHT
)
classification.move(x_pos, y)
self.scene.addWidget(classification)
classification.move(x, y + label_width)
proxy = self.scene.addWidget(classification)
proxy.setRotation(-90)
# patch
pixmap = QPixmap(self.soi.icon)
patch = QLabel()
patch.setPixmap(
pixmap.scaled(self.soi.HEADER_HEIGHT, self.soi.HEADER_HEIGHT)
# from date
valid_from = soi_date_string_to_user_friendly_string(
self.soi.valid_from
)
label_valid_from = QLabel("Gyldig fra: {}".format(valid_from))
label_valid_from.setStyleSheet("background-color: rgba(0,0,0,0%)")
label_valid_from.setFont(QFont("Times New Roman", 15))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = (
label_valid_from.fontMetrics()
.boundingRect(label_valid_from.text())
.width()
)
label_valid_from.move(x + 57, y + label_width)
proxy = self.scene.addWidget(label_valid_from)
proxy.setRotation(-90)
# to date
valid_to = soi_date_string_to_user_friendly_string(self.soi.valid_to)
label_valid_to = QLabel("Gyldig til: {}".format(valid_to))
label_valid_to.setStyleSheet("background-color: rgba(0,0,0,0%)")
label_valid_to.setFont(QFont("Times New Roman", 15))
# source: https://stackoverflow.com/a/8638114/3545896
# CAUTION: does not work if font is set through stylesheet
label_width = (
label_valid_to.fontMetrics()
.boundingRect(label_valid_to.text())
.width()
)
patch.move(x + self.soi.CONTENT_WIDTH - self.soi.HEADER_HEIGHT, y)
self.scene.addWidget(patch)
label_valid_to.move(x + 80, y + label_width)
proxy = self.scene.addWidget(label_valid_to)
proxy.setRotation(-90)
def draw_page(self, x, y):
"""Draw page starting at given position.
......@@ -257,19 +356,13 @@ class InlineEditableSOIView(QScrollArea):
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.soi.WIDTH - self.soi.PADDING * 2,
self.soi.HEIGHT - self.soi.PADDING * 2,
)
self.scene.addRect(
x + self.soi.PADDING,
y + self.soi.PADDING,
self.soi.CONTENT_WIDTH,
self.soi.HEADER_WIDTH,
self.soi.HEADER_HEIGHT,
)
......@@ -324,3 +417,20 @@ class InlineEditableSOIView(QScrollArea):
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())
def soi_date_string_to_user_friendly_string(date_string):
"""Convert SOI date to user-friendly format.
Parameters
----------
date_string : str
String following convention used in SOI, which is 'YYYY-MM-DD'
Returns
-------
str
String following user friendly convention 'DD.MM.YYYY'
"""
parsed_date = datetime.strptime(date_string, "%Y-%m-%d")
return parsed_date.strftime("%d.%m.%Y")
......@@ -141,30 +141,46 @@ class SOI:
the class variables.
```text
SOI.WIDTH
|
v
_________________
SOI.PADDING
| SOI.CONTENT_WIDTH
| | SOI.PADDING
| | |
v v v
+-------------+
| | <- SOI.PADDING
| +---------+ |
| | HEADER | | <- SOI.HEADER_HEIGHT
| +---------+ |
| | MODULES | | <- SOI.CONTENT_HEIGHT
| +---------+ |
| | <- SOI.PADDING
+-------------+
<- SOI.PADDING
+-------------+
| | <- SOI.PADDING
| +---------+ |
| | HEADER | | <- SOI.HEADER_HEIGHT
| +---------+ |
| | MODULES | | <- SOI.CONTENT_HEIGHT
| +---------+ |
| | <- SOI.PADDING
+-------------+
| SOI.HEADER_WIDTH
| | SOI.CONTENT_WIDTH
| | | SOI.PADDING
| | | |
v v v v
_ ___ _________ _
+-----------------+
| | | <- SOI.PADDING | <- SOI.HEIGHT
| +---+---------+ | |
| | H | MODULES | | | <- SOI.CONTENT_HEIGHT |
| | E | | | | |
| | A | | | | |
| | D | | | | |
| | E | | | | |
| | R | | | | |
| +---+---------+ | |
| | | <- SOI.PADDING |
+-----------------+
| <- SOI.PADDING
+-----------------+
| | | <- SOI.PADDING | <- SOI.HEIGHT
| +---+---------+ | |
| | H | MODULES | | | <- SOI.CONTENT_HEIGHT |
| | E | | | | |
| | A | | | | |
| | D | | | | |
| | E | | | | |
| | R | | | | |
| +---+---------+ | |
| | | <- SOI.PADDING |
+-----------------+
```
`MODULES` is where all the modules of the page will show up. Each module
......@@ -180,10 +196,19 @@ class SOI:
* `module["meta"]["y"]` is a value between `0` and `SOI.CONTENT_HEIGHT`.
This value is determined by the placement strategy.
* `module["widget"].pos().x()` is calculated from `module["meta"]["x"]`
as follows: `module["meta"]["x"] + SOI.PADDING`
as follows:
```text
module["meta"]["x"] + SOI.PADDING + SOI.HEADER_WIDTH
```
* `module["widget"].pos().y()` is calculated from `module["meta"]["y"]`
as follows: `module["meta"]["y"] + SOI.PADDING + SOI.HEADER_HEIGHT +
(SOI.PADDING + SOI.HEIGHT) * (module["meta"]["page"] - 1)`
as follows:
```text
module["meta"]["y"] + SOI.PADDING +
(SOI.PADDING + SOI.HEIGHT) * (module["meta"]["page"] - 1)
```
The function `SOI.update_module_widget_position` is responsible for
updating the widget positions based on the "meta" positions, using the
......@@ -238,10 +263,11 @@ class SOI:
HEIGHT = 1700
WIDTH = HEIGHT * A4_RATIO
PADDING = 100
CONTENT_WIDTH = WIDTH - PADDING * 2
CONTENT_HEIGHT = HEIGHT - PADDING * 2
HEADER_HEIGHT = 100
HEADER_WIDTH = 110
HEADER_HEIGHT = HEIGHT - PADDING * 2
MODULE_PADDING = 10
CONTENT_WIDTH = WIDTH - PADDING * 2 - HEADER_WIDTH
CONTENT_HEIGHT = HEIGHT - PADDING * 2
@classmethod
def construct_from_compressed_soi_file(cls, filename):
......@@ -365,20 +391,17 @@ class SOI:
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
)
distance_to_start_of_next_soi_content_y = self.HEIGHT + self.PADDING
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_x = module["meta"]["x"] + self.PADDING + self.HEADER_WIDTH
new_y = (
module["meta"]["y"]
+ self.PADDING
+ self.HEADER_HEIGHT
+ scene_skip_distance_page_height
)
......@@ -471,9 +494,7 @@ class SOI:
# float("inf") to add infinite bins. See https://github.com/secnot/rectpack/blob/master/README.md#api
packer.add_bin(
self.CONTENT_WIDTH,
self.CONTENT_HEIGHT - self.HEADER_HEIGHT,
float("inf"),
self.CONTENT_WIDTH, self.CONTENT_HEIGHT, float("inf"),
)
packer.pack()
......
......@@ -24,9 +24,9 @@ SOITOOL_ROOT_PATH = Path(__file__).parent.parent
TITLE = "testSOI"
DESCRIPTION = "This is a description"
VERSION = "1"
DATE = "01.01.2020"
VALID_FROM = "01.01.2020"
VALID_TO = "02.01.2020"
DATE = "2020-01-01"
VALID_FROM = "2020-01-01"
VALID_TO = "2020-01-02"
ICON = "soitool/media/HVlogo.png"
CLASSIFICATION = "UGRADERT"
ORIENTATION = "portrait"
......@@ -74,9 +74,10 @@ class SerializeTest(unittest.TestCase):
modules=MODULES,
attachments=MODULES,
)
date = datetime.now().strftime("%Y_%m_%d")
file_name_uncompressed = f"SOI_{TITLE}_{date}.json"
file_name_compressed = f"SOI_{TITLE}_{date}.txt"
date = datetime.strptime(DATE, "%Y-%m-%d")
date_formatted_for_file_path = date.strftime("%Y_%m_%d")
file_name_uncompressed = f"SOI_{TITLE}_{date_formatted_for_file_path}.json"
file_name_compressed = f"SOI_{TITLE}_{date_formatted_for_file_path}.txt"
self.file_path_uncompressed = os.path.join(
SOITOOL_ROOT_PATH, file_name_uncompressed
......
......@@ -174,7 +174,7 @@ class TestSOI(unittest.TestCase):
def test_reorganize_of_too_big_module(self):
"""Test exception thrown by reorganize in case of too large modules."""
maximum_width = self.soi.CONTENT_WIDTH
maximum_height = self.soi.CONTENT_HEIGHT - self.soi.HEADER_HEIGHT
maximum_height = self.soi.CONTENT_HEIGHT
module_too_wide = {
"widget": TestModule("red", maximum_width + 1, 100),
......@@ -230,7 +230,7 @@ class TestSOI(unittest.TestCase):
"""
# append module of maximum size
maximum_width = self.soi.CONTENT_WIDTH
maximum_height = self.soi.CONTENT_HEIGHT - self.soi.HEADER_HEIGHT
maximum_height = self.soi.CONTENT_HEIGHT
module_maximum_size = {
"widget": TestModule("red", maximum_width, maximum_height),
"meta": {"x": 0, "y": 0, "page": 1, "name": "maximum_size"},
......@@ -284,13 +284,14 @@ class TestSOI(unittest.TestCase):
self.soi.update_module_widget_position(self.soi.modules[0])
# calculate expected widget positions. See SOI class docstring for an
# explanation of the calculation neededfor y
expected_x = page_2_module["meta"]["x"] + self.soi.PADDING
# explanation of the calculation needed for y
expected_x = (
page_2_module["meta"]["x"]
+ self.soi.PADDING
+ self.soi.HEADER_WIDTH
)
expected_y = (
page_2_module["meta"]["y"]
+ self.soi.PADDING * 2
+ self.soi.HEIGHT
+ self.soi.HEADER_HEIGHT
page_2_module["meta"]["y"] + self.soi.PADDING * 2 + self.soi.HEIGHT
)
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