From 83d955cdf5270e82fb4ee237894ee802d9293e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 28 Nov 2021 23:27:50 +0100 Subject: [PATCH 01/20] :tada: The title of the blocks is editable by clicking on it. Changed the behavior of the move test so that the mouse clicks further right outside the title area. --- opencodeblocks/graphics/blocks/block.py | 33 ++++++++++++++----------- tests/integration/test_blocks.py | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 0d13dd0f..26398a40 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import QPointF, QRectF, Qt from PyQt5.QtGui import QBrush, QMouseEvent, QPen, QColor, QFont, QPainter, QPainterPath from PyQt5.QtWidgets import QGraphicsItem, QGraphicsProxyWidget, \ - QGraphicsSceneMouseEvent, QLabel, QSplitter, QSplitterHandle, \ + QGraphicsSceneMouseEvent, QLineEdit, QSplitter, QSplitterHandle, \ QStyleOptionGraphicsItem, QWidget from opencodeblocks.core.serializable import Serializable @@ -18,6 +18,7 @@ if TYPE_CHECKING: from opencodeblocks.graphics.scene.scene import OCBScene +BACKGROUND_COLOR = QColor("#E3212121") class OCBBlock(QGraphicsItem, Serializable): @@ -55,15 +56,12 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = self.sockets_in = [] self.sockets_out = [] - self.title_height = 3 * title_size - self.title = title + self.title_height = 3.5 * title_size self.title_left_offset = 0 self._pen_outline = QPen(QColor("#7F000000")) self._pen_outline_selected = QPen(QColor("#FFFFA637")) - - self._brush_title = QBrush(QColor("#FF313131")) - self._brush_background = QBrush(QColor("#E3212121")) + self._brush_background = QBrush(BACKGROUND_COLOR) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) @@ -79,8 +77,8 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = int(height) ) - self.title_widget = QLabel(self.title, self.root) - self.title_widget.setAttribute(Qt.WA_TransparentForMouseEvents) + self.title_widget = QLineEdit(title, self.root) + # self.title_widget.setAttribute(Qt.WA_TransparentForMouseEvents) self.title_widget.setAttribute(Qt.WA_TranslucentBackground) self.setTitleGraphics( title_color, @@ -133,8 +131,16 @@ def setTitleGraphics(self, color: str, font: str, padding: title padding. """ - self.title_widget.setMargin(int(padding)) - self.title_widget.setStyleSheet(f"QLabel {{ color : {color} }}") + # self.title_widget.setMargin(int(padding)) + self.title_widget.setStyleSheet( + f""" + QLineEdit {{ + color : {color}; + background-color: #E3212121; + border:none; + padding: {padding}px; + }}""" + ) self.title_widget.setFont(QFont(font, size)) def paint(self, painter: QPainter, @@ -247,7 +253,7 @@ def update_all(self): self.title_widget.setGeometry( int(self.edge_size + self.title_left_offset), int(self.edge_size / 2), - int(self.width - 2 * self.edge_size), + int(self.width / 3), int(self.title_height) ) self.size_grip.setGeometry( @@ -260,13 +266,12 @@ def update_all(self): @property def title(self): """ Block title. """ - return self._title + return self.title_widget.text() @title.setter def title(self, value: str): - self._title = value if hasattr(self, 'title_widget'): - self.title_widget.setText(self._title) + self.title_widget.setText(value) @property def width(self): diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index 449f7763..d9273c74 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -55,7 +55,7 @@ def testing_drag(msgQueue): pos_block = QPointF(self.block1.pos().x(), self.block1.pos().y()) pos_block.setX( - pos_block.x() + self.block1.title_height + self.block1.edge_size + pos_block.x() + self.block1.width - self.block1.edge_size * 2 ) pos_block.setY(pos_block.y() + self.block1.title_height/2) From 08eca81b09f822c6b57ff81067fc1a4f10cea25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 00:02:25 +0100 Subject: [PATCH 02/20] :umbrella: Fix the move test by making the cursor inside the window. Reduced the move amount to prevent failure due to moving the block outside the window. The goal of this test is to check that the block moves at the same pace as the mouse --- tests/integration/test_blocks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index d9273c74..6dc368e5 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -45,7 +45,7 @@ def test_move_blocks(self, qtbot): QApplication.processEvents() - expected_move_amount = [70, -30] + expected_move_amount = [-20, -30] STOP_MSG = "stop" CHECK_MSG = "check" @@ -55,7 +55,7 @@ def testing_drag(msgQueue): pos_block = QPointF(self.block1.pos().x(), self.block1.pos().y()) pos_block.setX( - pos_block.x() + self.block1.width - self.block1.edge_size * 2 + pos_block.x() + self.block1.width - self.block1.edge_size * 3 ) pos_block.setY(pos_block.y() + self.block1.title_height/2) From 1919a99adc4864832351d374dbed7f16c2f185c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 00:08:53 +0100 Subject: [PATCH 03/20] :umbrella: More work to make the move test work --- tests/integration/test_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index 6dc368e5..4ab691b3 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -45,7 +45,7 @@ def test_move_blocks(self, qtbot): QApplication.processEvents() - expected_move_amount = [-20, -30] + expected_move_amount = [20, -30] STOP_MSG = "stop" CHECK_MSG = "check" From 31370ed8198a30114d4f9198cd3836b5f09e28b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 00:12:39 +0100 Subject: [PATCH 04/20] :umbrella: Narrowing down the issue --- tests/integration/test_blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index 4ab691b3..1f343e1b 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -55,7 +55,7 @@ def testing_drag(msgQueue): pos_block = QPointF(self.block1.pos().x(), self.block1.pos().y()) pos_block.setX( - pos_block.x() + self.block1.width - self.block1.edge_size * 3 + pos_block.x() + self.block1.width/2 ) pos_block.setY(pos_block.y() + self.block1.title_height/2) From be288e0c7f7f4916bd4ef7f9095162edba135d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 00:21:32 +0100 Subject: [PATCH 05/20] :beetle: Block is now centered on screen. --- tests/integration/test_blocks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index 1f343e1b..c6122f36 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -43,6 +43,13 @@ def test_move_blocks(self, qtbot): self.ocb_widget.scene.addItem(self.block1) self.subwindow.show() + # put block1 at the bottom left + # This line works because the zoom is 1 by default. + self.ocb_widget.view.horizontalScrollBar().setValue(self.block1.x()) + self.ocb_widget.view.verticalScrollBar().setValue( + self.block1.y() - self.ocb_widget.view.height() + self.block1.height + ) + QApplication.processEvents() expected_move_amount = [20, -30] From 9476fb77013188e6db59b10cc0d219c53c9b830f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 00:50:10 +0100 Subject: [PATCH 06/20] :tada: The title is wider --- opencodeblocks/graphics/blocks/block.py | 2 +- tests/integration/test_blocks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 26398a40..d83b554a 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -253,7 +253,7 @@ def update_all(self): self.title_widget.setGeometry( int(self.edge_size + self.title_left_offset), int(self.edge_size / 2), - int(self.width / 3), + int(self.width / 2), int(self.title_height) ) self.size_grip.setGeometry( diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index c6122f36..9f44fcf3 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -62,7 +62,7 @@ def testing_drag(msgQueue): pos_block = QPointF(self.block1.pos().x(), self.block1.pos().y()) pos_block.setX( - pos_block.x() + self.block1.width/2 + pos_block.x() + self.block1.width*2/3 ) pos_block.setY(pos_block.y() + self.block1.title_height/2) From 7939e5aecf7128dee08aeb529ad476b7b9de24fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 01:30:24 +0100 Subject: [PATCH 07/20] :tada: To edit the title a double click is required. The dragging test fails for unknown reasons. --- opencodeblocks/graphics/blocks/block.py | 40 ++++++++++++++++++++++--- tests/integration/test_blocks.py | 2 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index d83b554a..86b4dd16 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -5,8 +5,10 @@ from typing import TYPE_CHECKING, Optional, OrderedDict, Tuple +import time + from PyQt5.QtCore import QPointF, QRectF, Qt -from PyQt5.QtGui import QBrush, QMouseEvent, QPen, QColor, QFont, QPainter, QPainterPath +from PyQt5.QtGui import QBrush, QFocusEvent, QMouseEvent, QPen, QColor, QFont, QPainter, QPainterPath from PyQt5.QtWidgets import QGraphicsItem, QGraphicsProxyWidget, \ QGraphicsSceneMouseEvent, QLineEdit, QSplitter, QSplitterHandle, \ QStyleOptionGraphicsItem, QWidget @@ -20,6 +22,35 @@ BACKGROUND_COLOR = QColor("#E3212121") + +class OCBTitle(QLineEdit): + """ The title of an OCBBlock. Needs to be double clicked to interact """ + + def __init__(self, content: str, parent: QWidget = None): + """ Create a new title for an OCBBlock """ + super().__init__(content, parent) + self.clickTime = None + self.setReadOnly(True) + + def mousePressEvent(self, event: QMouseEvent): + if self.clickTime is not None and self.isReadOnly() and time.time() - self.clickTime > 0.3: + self.parent().mousePressEvent(event) + else: + self.mouseDoubleClickEvent(event) + self.clickTime = time.time() + + def focusOutEvent(self, event: QFocusEvent): + """ The title is read-only when focused is lost """ + self.setReadOnly(True) + self.deselect() + + def mouseDoubleClickEvent(self, event: QMouseEvent): + """ Toggle readonly mode when double clicking """ + self.setReadOnly(not self.isReadOnly()) + if not self.isReadOnly(): + self.setFocus(Qt.MouseFocusReason) + + class OCBBlock(QGraphicsItem, Serializable): """ Base class for blocks in OpenCodeBlocks. """ @@ -77,7 +108,7 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = int(height) ) - self.title_widget = QLineEdit(title, self.root) + self.title_widget = OCBTitle(title, self.root) # self.title_widget.setAttribute(Qt.WA_TransparentForMouseEvents) self.title_widget.setAttribute(Qt.WA_TranslucentBackground) self.setTitleGraphics( @@ -91,7 +122,8 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = self.size_grip = BlockSizeGrip(self, self.root) - if type(self) == OCBBlock: # DO NOT TRUST codacy !!! isinstance != type + if isinstance( + self, OCBBlock): # DO NOT TRUST codacy !!! isinstance != type # This has to be called at the end of the constructor of # every class inheriting this. self.holder.setWidget(self.root) @@ -253,7 +285,7 @@ def update_all(self): self.title_widget.setGeometry( int(self.edge_size + self.title_left_offset), int(self.edge_size / 2), - int(self.width / 2), + int(self.width - self.edge_size * 3), int(self.title_height) ) self.size_grip.setGeometry( diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index 9f44fcf3..632d7b33 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -62,7 +62,7 @@ def testing_drag(msgQueue): pos_block = QPointF(self.block1.pos().x(), self.block1.pos().y()) pos_block.setX( - pos_block.x() + self.block1.width*2/3 + pos_block.x() + self.block1.title_height + self.block1.edge_size ) pos_block.setY(pos_block.y() + self.block1.title_height/2) From 60580db16a1b5aa63ca4a6ddc7fc7bc1cca13f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 02:00:15 +0100 Subject: [PATCH 08/20] :beetle: Fix issue with test. A boolean expression was previously incorrect --- opencodeblocks/graphics/blocks/block.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 86b4dd16..d9100d2c 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -8,7 +8,8 @@ import time from PyQt5.QtCore import QPointF, QRectF, Qt -from PyQt5.QtGui import QBrush, QFocusEvent, QMouseEvent, QPen, QColor, QFont, QPainter, QPainterPath +from PyQt5.QtGui import QBrush, QFocusEvent, QMouseEvent, QPen, QColor, QFont, \ + QPainter, QPainterPath from PyQt5.QtWidgets import QGraphicsItem, QGraphicsProxyWidget, \ QGraphicsSceneMouseEvent, QLineEdit, QSplitter, QSplitterHandle, \ QStyleOptionGraphicsItem, QWidget @@ -33,7 +34,12 @@ def __init__(self, content: str, parent: QWidget = None): self.setReadOnly(True) def mousePressEvent(self, event: QMouseEvent): - if self.clickTime is not None and self.isReadOnly() and time.time() - self.clickTime > 0.3: + """ + Detect double clicks and single clicks are react accordingly by + dispatching the event to the parent or the current widget + """ + if self.clickTime is None or ( + self.isReadOnly() and time.time() - self.clickTime > 0.3): self.parent().mousePressEvent(event) else: self.mouseDoubleClickEvent(event) From e7f14bd8c69f1fc436e6059a7a155c3e26302740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 02:08:30 +0100 Subject: [PATCH 09/20] :beetle: The cursor moves at the correct position when clicking the title of a block --- opencodeblocks/graphics/blocks/block.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index d9100d2c..c98fece6 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -41,8 +41,11 @@ def mousePressEvent(self, event: QMouseEvent): if self.clickTime is None or ( self.isReadOnly() and time.time() - self.clickTime > 0.3): self.parent().mousePressEvent(event) - else: + elif self.isReadOnly(): self.mouseDoubleClickEvent(event) + super().mousePressEvent(event) + else: + super().mousePressEvent(event) self.clickTime = time.time() def focusOutEvent(self, event: QFocusEvent): From 8150366e93a54785fc8327bd93fe35cbeea735d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 02:16:14 +0100 Subject: [PATCH 10/20] :beetle: This line seems to change without any reason. I will investigate this. Can Codacy edit it automatically ? --- opencodeblocks/graphics/blocks/block.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index c98fece6..4b09db29 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -131,8 +131,7 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = self.size_grip = BlockSizeGrip(self, self.root) - if isinstance( - self, OCBBlock): # DO NOT TRUST codacy !!! isinstance != type + if type(self) == OCBBlock: # DO NOT TRUST codacy !!! type(self) should be used, not isinstance. # This has to be called at the end of the constructor of # every class inheriting this. self.holder.setWidget(self.root) From 9cca4521780faa3d4e5b4638f4ac0f9fea071b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 29 Nov 2021 21:25:09 +0100 Subject: [PATCH 11/20] Remove comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathïs Fédérico <60117466+MathisFederico@users.noreply.github.com> --- opencodeblocks/graphics/blocks/block.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 4b09db29..a99ae9ef 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -118,7 +118,6 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = ) self.title_widget = OCBTitle(title, self.root) - # self.title_widget.setAttribute(Qt.WA_TransparentForMouseEvents) self.title_widget.setAttribute(Qt.WA_TranslucentBackground) self.setTitleGraphics( title_color, From b8e4003e3aed50e9032da5c18c7095d442a2ddb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 20:08:32 +0100 Subject: [PATCH 12/20] :hammer: Moved widgets into their own files. --- opencodeblocks/graphics/blocks/block.py | 72 ++----------------- .../graphics/blocks/widgets/__init__.py | 8 +++ .../blocks/{ => widgets}/blocksizegrip.py | 2 +- .../graphics/blocks/widgets/blocksplitter.py | 28 ++++++++ .../graphics/blocks/widgets/blocktitle.py | 41 +++++++++++ 5 files changed, 84 insertions(+), 67 deletions(-) create mode 100644 opencodeblocks/graphics/blocks/widgets/__init__.py rename opencodeblocks/graphics/blocks/{ => widgets}/blocksizegrip.py (98%) create mode 100644 opencodeblocks/graphics/blocks/widgets/blocksplitter.py create mode 100644 opencodeblocks/graphics/blocks/widgets/blocktitle.py diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 74b4c0ec..72103563 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -8,15 +8,15 @@ import time from PyQt5.QtCore import QPointF, QRectF, Qt -from PyQt5.QtGui import QBrush, QFocusEvent, QMouseEvent, QPen, QColor, QFont, \ +from PyQt5.QtGui import QBrush, QPen, QColor, QFont, \ QPainter, QPainterPath from PyQt5.QtWidgets import QGraphicsItem, QGraphicsProxyWidget, \ - QGraphicsSceneMouseEvent, QLineEdit, QSplitter, QSplitterHandle, \ + QGraphicsSceneMouseEvent, \ QStyleOptionGraphicsItem, QWidget from opencodeblocks.core.serializable import Serializable from opencodeblocks.graphics.socket import OCBSocket -from opencodeblocks.graphics.blocks.blocksizegrip import BlockSizeGrip +from opencodeblocks.graphics.blocks.widgets import OCBSplitter, OCBSizeGrip, OCBTitle if TYPE_CHECKING: from opencodeblocks.graphics.scene.scene import OCBScene @@ -24,42 +24,6 @@ BACKGROUND_COLOR = QColor("#E3212121") -class OCBTitle(QLineEdit): - """ The title of an OCBBlock. Needs to be double clicked to interact """ - - def __init__(self, content: str, parent: QWidget = None): - """ Create a new title for an OCBBlock """ - super().__init__(content, parent) - self.clickTime = None - self.setReadOnly(True) - - def mousePressEvent(self, event: QMouseEvent): - """ - Detect double clicks and single clicks are react accordingly by - dispatching the event to the parent or the current widget - """ - if self.clickTime is None or ( - self.isReadOnly() and time.time() - self.clickTime > 0.3): - self.parent().mousePressEvent(event) - elif self.isReadOnly(): - self.mouseDoubleClickEvent(event) - super().mousePressEvent(event) - else: - super().mousePressEvent(event) - self.clickTime = time.time() - - def focusOutEvent(self, event: QFocusEvent): - """ The title is read-only when focused is lost """ - self.setReadOnly(True) - self.deselect() - - def mouseDoubleClickEvent(self, event: QMouseEvent): - """ Toggle readonly mode when double clicking """ - self.setReadOnly(not self.isReadOnly()) - if not self.isReadOnly(): - self.setFocus(Qt.MouseFocusReason) - - class OCBBlock(QGraphicsItem, Serializable): """ Base class for blocks in OpenCodeBlocks. """ @@ -127,7 +91,7 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = self.splitter = OCBSplitter(self, Qt.Vertical, self.root) - self.size_grip = BlockSizeGrip(self, self.root) + self.size_grip = OCBSizeGrip(self, self.root) if type(self) == OCBBlock: # DO NOT TRUST codacy !!! type(self) should be used, not isinstance. # This has to be called at the end of the constructor of @@ -174,7 +138,7 @@ def setTitleGraphics(self, color: str, font: str, f""" QLineEdit {{ color : {color}; - background-color: #E3212121; + background-color: transparent; border:none; padding: {padding}px; }}""" @@ -291,7 +255,7 @@ def update_all(self): self.title_widget.setGeometry( int(self.edge_size + self.title_left_offset), int(self.edge_size / 2), - int(self.width - self.edge_size * 3), + int(self.width - self.edge_size * 3 - self.title_left_offset), int(self.title_height) ) self.size_grip.setGeometry( @@ -370,27 +334,3 @@ def deserialize(self, data: dict, hashmap: dict = None, hashmap.update({socket_data['id']: socket}) self.update_all() - - -class OCBSplitterHandle(QSplitterHandle): - """ A handle for splitters with undoable events """ - - def mouseReleaseEvent(self, evt: QMouseEvent): - """ When releasing the handle, save the state to history """ - scene = self.parent().block.scene() - if scene is not None: - scene.history.checkpoint("Resize block", set_modified=True) - return super().mouseReleaseEvent(evt) - - -class OCBSplitter(QSplitter): - """ A spliter with undoable events """ - - def __init__(self, block: OCBBlock, orientation: int, parent: QWidget): - """ Create a new OCBSplitter """ - super().__init__(orientation, parent) - self.block = block - - def createHandle(self): - """ Return the middle handle of the splitter """ - return OCBSplitterHandle(self.orientation(), self) diff --git a/opencodeblocks/graphics/blocks/widgets/__init__.py b/opencodeblocks/graphics/blocks/widgets/__init__.py new file mode 100644 index 00000000..5f2b3607 --- /dev/null +++ b/opencodeblocks/graphics/blocks/widgets/__init__.py @@ -0,0 +1,8 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python +# Copyright (C) 2021 Mathïs FEDERICO + +""" Module for the OCB Blocks Widgets. """ + +from opencodeblocks.graphics.blocks.widgets.blocksplitter import OCBSplitter +from opencodeblocks.graphics.blocks.widgets.blocktitle import OCBTitle +from opencodeblocks.graphics.blocks.widgets.blocksizegrip import OCBSizeGrip diff --git a/opencodeblocks/graphics/blocks/blocksizegrip.py b/opencodeblocks/graphics/blocks/widgets/blocksizegrip.py similarity index 98% rename from opencodeblocks/graphics/blocks/blocksizegrip.py rename to opencodeblocks/graphics/blocks/widgets/blocksizegrip.py index 2c8eb4ed..c08ee4b9 100644 --- a/opencodeblocks/graphics/blocks/blocksizegrip.py +++ b/opencodeblocks/graphics/blocks/widgets/blocksizegrip.py @@ -11,7 +11,7 @@ from PyQt5.QtGui import QMouseEvent -class BlockSizeGrip(QSizeGrip): +class OCBSizeGrip(QSizeGrip): """ A grip to resize a block """ def __init__(self, block: QGraphicsItem, parent: QWidget = None): diff --git a/opencodeblocks/graphics/blocks/widgets/blocksplitter.py b/opencodeblocks/graphics/blocks/widgets/blocksplitter.py new file mode 100644 index 00000000..4afc8697 --- /dev/null +++ b/opencodeblocks/graphics/blocks/widgets/blocksplitter.py @@ -0,0 +1,28 @@ +from typing import TYPE_CHECKING + +from PyQt5.QtGui import QMouseEvent +from PyQt5.QtWidgets import QSplitter, QSplitterHandle, QWidget + + +class OCBSplitterHandle(QSplitterHandle): + """ A handle for splitters with undoable events """ + + def mouseReleaseEvent(self, evt: QMouseEvent): + """ When releasing the handle, save the state to history """ + scene = self.parent().block.scene() + if scene is not None: + scene.history.checkpoint("Resize block", set_modified=True) + return super().mouseReleaseEvent(evt) + + +class OCBSplitter(QSplitter): + """ A spliter with undoable events """ + + def __init__(self, block: QWidget, orientation: int, parent: QWidget): + """ Create a new OCBSplitter """ + super().__init__(orientation, parent) + self.block = block + + def createHandle(self): + """ Return the middle handle of the splitter """ + return OCBSplitterHandle(self.orientation(), self) \ No newline at end of file diff --git a/opencodeblocks/graphics/blocks/widgets/blocktitle.py b/opencodeblocks/graphics/blocks/widgets/blocktitle.py new file mode 100644 index 00000000..d3944d67 --- /dev/null +++ b/opencodeblocks/graphics/blocks/widgets/blocktitle.py @@ -0,0 +1,41 @@ + +import time +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFocusEvent, QMouseEvent +from PyQt5.QtWidgets import QLineEdit, QWidget + + +class OCBTitle(QLineEdit): + """ The title of an OCBBlock. Needs to be double clicked to interact """ + + def __init__(self, content: str, parent: QWidget = None): + """ Create a new title for an OCBBlock """ + super().__init__(content, parent) + self.clickTime = None + self.setReadOnly(True) + + def mousePressEvent(self, event: QMouseEvent): + """ + Detect double clicks and single clicks are react accordingly by + dispatching the event to the parent or the current widget + """ + if self.clickTime is None or ( + self.isReadOnly() and time.time() - self.clickTime > 0.3): + self.parent().mousePressEvent(event) + elif self.isReadOnly(): + self.mouseDoubleClickEvent(event) + super().mousePressEvent(event) + else: + super().mousePressEvent(event) + self.clickTime = time.time() + + def focusOutEvent(self, event: QFocusEvent): + """ The title is read-only when focused is lost """ + self.setReadOnly(True) + self.deselect() + + def mouseDoubleClickEvent(self, event: QMouseEvent): + """ Toggle readonly mode when double clicking """ + self.setReadOnly(not self.isReadOnly()) + if not self.isReadOnly(): + self.setFocus(Qt.MouseFocusReason) From e3fb45e8c449443b1190960796e90c28774461fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 21:04:02 +0100 Subject: [PATCH 13/20] :beetle: The content of the title is aligned to the left when the title is not focused --- opencodeblocks/graphics/blocks/widgets/blocktitle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opencodeblocks/graphics/blocks/widgets/blocktitle.py b/opencodeblocks/graphics/blocks/widgets/blocktitle.py index d3944d67..e2ac01eb 100644 --- a/opencodeblocks/graphics/blocks/widgets/blocktitle.py +++ b/opencodeblocks/graphics/blocks/widgets/blocktitle.py @@ -13,6 +13,7 @@ def __init__(self, content: str, parent: QWidget = None): super().__init__(content, parent) self.clickTime = None self.setReadOnly(True) + self.setCursorPosition(0) def mousePressEvent(self, event: QMouseEvent): """ @@ -32,6 +33,7 @@ def mousePressEvent(self, event: QMouseEvent): def focusOutEvent(self, event: QFocusEvent): """ The title is read-only when focused is lost """ self.setReadOnly(True) + self.setCursorPosition(0) self.deselect() def mouseDoubleClickEvent(self, event: QMouseEvent): From cf5f95a4c51591c51ba079fe3d13be10af9e69dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Tue, 30 Nov 2021 21:33:05 +0100 Subject: [PATCH 14/20] :sparkles: Applied black code style :wrench: Refactor update_all --- opencodeblocks/graphics/blocks/block.py | 263 +++++++++++--------- opencodeblocks/graphics/blocks/codeblock.py | 50 ++-- 2 files changed, 163 insertions(+), 150 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 72103563..cab85b86 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -8,11 +8,14 @@ import time from PyQt5.QtCore import QPointF, QRectF, Qt -from PyQt5.QtGui import QBrush, QPen, QColor, QFont, \ - QPainter, QPainterPath -from PyQt5.QtWidgets import QGraphicsItem, QGraphicsProxyWidget, \ - QGraphicsSceneMouseEvent, \ - QStyleOptionGraphicsItem, QWidget +from PyQt5.QtGui import QBrush, QPen, QColor, QFont, QPainter, QPainterPath +from PyQt5.QtWidgets import ( + QGraphicsItem, + QGraphicsProxyWidget, + QGraphicsSceneMouseEvent, + QStyleOptionGraphicsItem, + QWidget, +) from opencodeblocks.core.serializable import Serializable from opencodeblocks.graphics.socket import OCBSocket @@ -26,13 +29,24 @@ class OCBBlock(QGraphicsItem, Serializable): - """ Base class for blocks in OpenCodeBlocks. """ - - def __init__(self, block_type: str = 'base', source: str = '', position: tuple = (0, 0), - width: int = 300, height: int = 200, edge_size: float = 10.0, - title: str = 'New block', title_color: str = 'white', title_font: str = "Ubuntu", - title_size: int = 10, title_padding=4.0, parent: Optional['QGraphicsItem'] = None): - """ Base class for blocks in OpenCodeBlocks. + """Base class for blocks in OpenCodeBlocks.""" + + def __init__( + self, + block_type: str = "base", + source: str = "", + position: tuple = (0, 0), + width: int = 300, + height: int = 200, + edge_size: float = 10.0, + title: str = "New block", + title_color: str = "white", + title_font: str = "Ubuntu", + title_size: int = 10, + title_padding=4.0, + parent: Optional["QGraphicsItem"] = None, + ): + """Base class for blocks in OpenCodeBlocks. Args: block_type: Block type. @@ -74,26 +88,19 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = self.holder = QGraphicsProxyWidget(self) self.root = QWidget() self.root.setAttribute(Qt.WA_TranslucentBackground) - self.root.setGeometry( - 0, 0, - int(width), - int(height) - ) + self.root.setGeometry(0, 0, int(width), int(height)) self.title_widget = OCBTitle(title, self.root) self.title_widget.setAttribute(Qt.WA_TranslucentBackground) - self.setTitleGraphics( - title_color, - title_font, - title_size, - title_padding - ) + self.setTitleGraphics(title_color, title_font, title_size, title_padding) self.splitter = OCBSplitter(self, Qt.Vertical, self.root) self.size_grip = OCBSizeGrip(self, self.root) - if type(self) == OCBBlock: # DO NOT TRUST codacy !!! type(self) should be used, not isinstance. + if ( + type(self) == OCBBlock + ): # DO NOT TRUST codacy !!! type(self) should be used, not isinstance. # This has to be called at the end of the constructor of # every class inheriting this. self.holder.setWidget(self.root) @@ -106,25 +113,24 @@ def __init__(self, block_type: str = 'base', source: str = '', position: tuple = self.moved = False self.metadata = { - 'title_metadata': { - 'color': title_color, - 'font': title_font, - 'size': title_size, - 'padding': title_padding, + "title_metadata": { + "color": title_color, + "font": title_font, + "size": title_size, + "padding": title_padding, }, } - def scene(self) -> 'OCBScene': - """ Get the current OCBScene containing the block. """ + def scene(self) -> "OCBScene": + """Get the current OCBScene containing the block.""" return super().scene() def boundingRect(self) -> QRectF: - """ Get the the block bounding box. """ + """Get the the block bounding box.""" return QRectF(0, 0, self.width, self.height).normalized() - def setTitleGraphics(self, color: str, font: str, - size: int, padding: float): - """ Set the title graphics. + def setTitleGraphics(self, color: str, font: str, size: int, padding: float): + """Set the title graphics. Args: color: title color. @@ -138,44 +144,45 @@ def setTitleGraphics(self, color: str, font: str, f""" QLineEdit {{ color : {color}; - background-color: transparent; + background-color: #FF0000; border:none; padding: {padding}px; }}""" ) self.title_widget.setFont(QFont(font, size)) - def paint(self, painter: QPainter, - option: QStyleOptionGraphicsItem, # pylint:disable=unused-argument - widget: Optional[QWidget] = None): # pylint:disable=unused-argument - """ Paint the block. """ + def paint( + self, + painter: QPainter, + option: QStyleOptionGraphicsItem, # pylint:disable=unused-argument + widget: Optional[QWidget] = None, + ): # pylint:disable=unused-argument + """Paint the block.""" # content path_content = QPainterPath() path_content.setFillRule(Qt.FillRule.WindingFill) - path_content.addRoundedRect(0, 0, self.width, self.height, - self.edge_size, self.edge_size) + path_content.addRoundedRect( + 0, 0, self.width, self.height, self.edge_size, self.edge_size + ) painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(self._brush_background) painter.drawPath(path_content.simplified()) # outline path_outline = QPainterPath() - path_outline.addRoundedRect(0, 0, self.width, self.height, - self.edge_size, self.edge_size) + path_outline.addRoundedRect( + 0, 0, self.width, self.height, self.edge_size, self.edge_size + ) painter.setPen( - self._pen_outline_selected if self.isSelected() else self._pen_outline) + self._pen_outline_selected if self.isSelected() else self._pen_outline + ) painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawPath(path_outline.simplified()) - def _is_in_resize_area(self, pos: QPointF): - """ Return True if the given position is in the block resize_area. """ - return self.width - self.edge_size < pos.x() \ - and self.height - self.edge_size < pos.y() - def get_socket_pos(self, socket: OCBSocket) -> Tuple[float]: - """ Get a socket position to place them on the block sides. """ - if socket.socket_type == 'input': + """Get a socket position to place them on the block sides.""" + if socket.socket_type == "input": x = 0 sockets = self.sockets_in else: @@ -187,26 +194,25 @@ def get_socket_pos(self, socket: OCBSocket) -> Tuple[float]: y = y_offset else: side_lenght = self.height - y_offset - 2 * socket.radius - self.edge_size - y = y_offset + side_lenght * \ - sockets.index(socket) / (len(sockets) - 1) + y = y_offset + side_lenght * sockets.index(socket) / (len(sockets) - 1) return x, y def update_sockets(self): - """ Update the sockets positions. """ + """Update the sockets positions.""" for socket in self.sockets_in + self.sockets_out: socket.setPos(*self.get_socket_pos(socket)) def add_socket(self, socket: OCBSocket): - """ Add a socket to the block. """ - if socket.socket_type == 'input': + """Add a socket to the block.""" + if socket.socket_type == "input": self.sockets_in.append(socket) else: self.sockets_out.append(socket) self.update_sockets() def remove_socket(self, socket: OCBSocket): - """ Remove a socket from the block. """ - if socket.socket_type == 'input': + """Remove a socket from the block.""" + if socket.socket_type == "input": self.sockets_in.remove(socket) else: self.sockets_out.remove(socket) @@ -214,123 +220,134 @@ def remove_socket(self, socket: OCBSocket): self.update_sockets() def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): - """ OCBBlock reaction to a mouseReleaseEvent. """ + """OCBBlock reaction to a mouseReleaseEvent.""" if self.moved: self.moved = False self.scene().history.checkpoint("Moved block", set_modified=True) super().mouseReleaseEvent(event) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): - """ OCBBlock reaction to a mouseMoveEvent. """ + """OCBBlock reaction to a mouseMoveEvent.""" super().mouseMoveEvent(event) self.moved = True def remove(self): - """ Remove the block from the scene containing it. """ + """Remove the block from the scene containing it.""" scene = self.scene() for socket in self.sockets_in + self.sockets_out: self.remove_socket(socket) if scene is not None: scene.removeItem(self) + def update_splitter(self): + # We make the resizing of splitter only affect + # the last element of the split view + sizes = self.splitter.sizes() + old_height = self.splitter.height() + self.splitter.setGeometry( + int(self.edge_size), + int(self.edge_size + self.title_height), + int(self.width - self.edge_size * 2), + int(self.height - self.edge_size * 2 - self.title_height), + ) + if len(sizes) > 1: + height_delta = self.splitter.height() - old_height + sizes[-1] += height_delta + self.splitter.setSizes(sizes) + + def update_title(self): + self.title_widget.setGeometry( + int(self.edge_size + self.title_left_offset), + int(self.edge_size / 2), + int(self.width - self.edge_size * 3 - self.title_left_offset), + int(self.title_widget.height()), + ) + + def update_size_grip(self): + self.size_grip.setGeometry( + int(self.width - self.edge_size * 2), + int(self.height - self.edge_size * 2), + int(self.edge_size * 1.7), + int(self.edge_size * 1.7), + ) + def update_all(self): - """ Update sockets and title. """ + """Update sockets and title.""" self.update_sockets() - if hasattr(self, 'title_widget'): - # We make the resizing of splitter only affect - # the last element of the split view - sizes = self.splitter.sizes() - old_height = self.splitter.height() - self.splitter.setGeometry( - int(self.edge_size), - int(self.edge_size + self.title_height), - int(self.width - self.edge_size * 2), - int(self.height - self.edge_size * 2 - self.title_height) - ) - if len(sizes) > 1: - height_delta = self.splitter.height() - old_height - sizes[-1] += height_delta - self.splitter.setSizes(sizes) - - self.title_widget.setGeometry( - int(self.edge_size + self.title_left_offset), - int(self.edge_size / 2), - int(self.width - self.edge_size * 3 - self.title_left_offset), - int(self.title_height) - ) - self.size_grip.setGeometry( - int(self.width - self.edge_size * 2), - int(self.height - self.edge_size * 2), - int(self.edge_size * 1.7), - int(self.edge_size * 1.7) - ) + self.update_splitter() + self.update_title() + self.update_size_grip() @property def title(self): - """ Block title. """ + """Block title.""" return self.title_widget.text() @title.setter def title(self, value: str): - if hasattr(self, 'title_widget'): + if hasattr(self, "title_widget"): self.title_widget.setText(value) @property def width(self): - """ Block width. """ + """Block width.""" return self.root.width() @width.setter def width(self, value: float): self.root.setGeometry(0, 0, int(value), self.root.height()) - self.update_all() @property def height(self): - """ Block height. """ + """Block height.""" return self.root.height() @height.setter def height(self, value: float): self.root.setGeometry(0, 0, self.root.width(), int(value)) - self.update_all() def serialize(self) -> OrderedDict: metadata = OrderedDict(sorted(self.metadata.items())) - return OrderedDict([ - ('id', self.id), - ('title', self.title), - ('block_type', self.block_type), - ('source', self.source), - ('stdout', self.stdout), - ('splitter_pos', self.splitter.sizes()), - ('position', [self.pos().x(), self.pos().y()]), - ('width', self.width), - ('height', self.height), - ('metadata', metadata), - ('sockets', [socket.serialize() - for socket in self.sockets_in + self.sockets_out]), - ]) - - def deserialize(self, data: dict, hashmap: dict = None, - restore_id=True) -> None: + return OrderedDict( + [ + ("id", self.id), + ("title", self.title), + ("block_type", self.block_type), + ("source", self.source), + ("stdout", self.stdout), + ("splitter_pos", self.splitter.sizes()), + ("position", [self.pos().x(), self.pos().y()]), + ("width", self.width), + ("height", self.height), + ("metadata", metadata), + ( + "sockets", + [ + socket.serialize() + for socket in self.sockets_in + self.sockets_out + ], + ), + ] + ) + + def deserialize(self, data: dict, hashmap: dict = None, restore_id=True) -> None: if restore_id: - self.id = data['id'] - for dataname in ('title', 'block_type', 'source', 'stdout', 'width', 'height'): + self.id = data["id"] + for dataname in ("title", "block_type", "source", "stdout", "width", "height"): setattr(self, dataname, data[dataname]) - self.setPos(QPointF(*data['position'])) - self.metadata = dict(data['metadata']) - self.setTitleGraphics(**self.metadata['title_metadata']) + self.setPos(QPointF(*data["position"])) + self.metadata = dict(data["metadata"]) + self.setTitleGraphics(**self.metadata["title_metadata"]) - if 'splitter_pos' in data: - self.splitter.setSizes(data['splitter_pos']) + if "splitter_pos" in data: + self.splitter.setSizes(data["splitter_pos"]) - for socket_data in data['sockets']: + for socket_data in data["sockets"]: socket = OCBSocket(block=self) socket.deserialize(socket_data, hashmap, restore_id) self.add_socket(socket) if hashmap is not None: - hashmap.update({socket_data['id']: socket}) + hashmap.update({socket_data["id"]: socket}) self.update_all() diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index aba18bb7..2e54bda4 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -32,7 +32,7 @@ def __init__(self, **kwargs): self.source_editor = PythonEditor(self) - super().__init__(block_type='code', **kwargs) + super().__init__(block_type="code", **kwargs) self.output_panel_height = self.height / 3 self._min_output_panel_height = 20 @@ -55,18 +55,18 @@ def __init__(self, **kwargs): self.update_all() # Set the geometry of display and source_editor def init_output_panel(self): - """ Initialize the output display widget: QLabel """ + """Initialize the output display widget: QLabel""" output_panel = QTextEdit() output_panel.setReadOnly(True) output_panel.setFont(self.source_editor.font()) return output_panel def init_run_button(self): - """ Initialize the run button """ + """Initialize the run button""" run_button = QPushButton(">", self.root) - run_button.setMinimumWidth(int(self.edge_size)) + run_button.move(int(self.edge_size), int(self.edge_size / 2)) + run_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) run_button.clicked.connect(self.run_code) - run_button.raise_() return run_button def run_code(self): @@ -81,26 +81,21 @@ def run_code(self): worker.signals.image.connect(self.handle_image) self.source_editor.threadpool.start(worker) - def update_all(self): - """ Update the code block parts. """ - super().update_all() - if hasattr(self, 'run_button'): - self.run_button.setGeometry( - int(self.edge_size), - int(self.edge_size / 2), - int(2.5 * self.edge_size), - int(2.5 * self.edge_size) - ) - + def update_output_panel(self): # Close output panel if no output if self.stdout == "": self.previous_splitter_size = self.splitter.sizes() self.output_closed = True self.splitter.setSizes([1, 0]) + def update_all(self): + """Update the code block parts.""" + super().update_all() + self.update_output_panel() + @property def source(self) -> str: - """ Source code. """ + """Source code.""" return self.source_editor.text() @source.setter @@ -114,8 +109,8 @@ def stdout(self) -> str: @stdout.setter def stdout(self, value: str): self._stdout = value - if hasattr(self, 'output_panel'): - if value.startswith(''): + if hasattr(self, "output_panel"): + if value.startswith(""): display_text = self.b64_to_html(value[5:]) else: display_text = self.str_to_html(value) @@ -138,18 +133,19 @@ def str_to_html(text: str): # Convert ANSI escape codes to HTML text = conv.convert(text) # Replace background color - text = text.replace('background-color: #000000', - 'background-color: transparent') + text = text.replace( + "background-color: #000000", "background-color: transparent" + ) return text def handle_stdout(self, value: str): - """ Handle the stdout signal """ + """Handle the stdout signal""" # If there is a new line # Save every line but the last one - if value.find('\n') != -1: - lines = value.split('\n') - self._cached_stdout += '\n'.join(lines[:-1]) + '\n' + if value.find("\n") != -1: + lines = value.split("\n") + self._cached_stdout += "\n".join(lines[:-1]) + "\n" value = lines[-1] # Update the last line only @@ -160,5 +156,5 @@ def b64_to_html(image: str): return f'' def handle_image(self, image: str): - """ Handle the image signal """ - self.stdout = '' + image + """Handle the image signal""" + self.stdout = "" + image From 5104973ae5fbd79fd42044f6d4e713a106dc58cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Tue, 30 Nov 2021 22:05:36 +0100 Subject: [PATCH 15/20] :wrench: Refactor OCBTitle --- examples/mnist.ipyg | 58 +++++++-------- opencodeblocks/graphics/blocks/block.py | 73 +++++-------------- opencodeblocks/graphics/blocks/codeblock.py | 8 ++ .../graphics/blocks/widgets/blocktitle.py | 60 ++++++++++++--- 4 files changed, 105 insertions(+), 94 deletions(-) diff --git a/examples/mnist.ipyg b/examples/mnist.ipyg index 35c2b1f9..43b367db 100644 --- a/examples/mnist.ipyg +++ b/examples/mnist.ipyg @@ -9,7 +9,7 @@ "stdout": "Epoch 1/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 10s 4ms/step - loss: 0.2116 - accuracy: 0.9367\nEpoch 2/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0853 - accuracy: 0.9738\nEpoch 3/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0597 - accuracy: 0.9813\nEpoch 4/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0472 - accuracy: 0.9848\n", "splitter_pos": [ 85, - 259 + 261 ], "position": [ 1062.374999999999, @@ -21,7 +21,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -31,7 +31,7 @@ "type": "input", "position": [ 0.0, - 42.0 + 40.0 ], "metadata": { "color": "#e02c2c", @@ -45,7 +45,7 @@ "type": "output", "position": [ 1064.0, - 42.0 + 40.0 ], "metadata": { "color": "#35bc31", @@ -64,7 +64,7 @@ "stdout": "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\n", "splitter_pos": [ 0, - 278 + 280 ], "position": [ 2330.066406249998, @@ -76,7 +76,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -86,7 +86,7 @@ "type": "input", "position": [ 0.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -104,8 +104,8 @@ "source": "metrics = model.evaluate(x_test, y_test)\r\nprint(f\"mean_loss:{metrics[0]:.2f}, mean_acc:{metrics[1]:.2f}\")\r\n", "stdout": "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r313/313 [==============================] - 1s 2ms/step - loss: 0.0552 - accuracy: 0.9823\nmean_loss:0.06, mean_acc:0.98\n", "splitter_pos": [ - 76, - 76 + 77, + 77 ], "position": [ 2295.17578125, @@ -117,7 +117,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -127,7 +127,7 @@ "type": "input", "position": [ 0.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -145,7 +145,7 @@ "source": "from tensorflow.keras.datasets import mnist\r\n(x_train, y_train), (x_test, y_test) = mnist.load_data()\r\n", "stdout": "", "splitter_pos": [ - 86, + 88, 0 ], "position": [ @@ -158,7 +158,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -168,7 +168,7 @@ "type": "output", "position": [ 850.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -186,8 +186,8 @@ "source": "x_train = x_train.astype('float32') / 255.0\r\nx_test = x_test.astype('float32') / 255.0\r\n\r\nx_train = x_train.reshape(x_train.shape[0], 28, 28, 1)\r\nx_test = x_test.reshape(x_test.shape[0], 28, 28, 1)\r\n\r\nprint('train:', x_train.shape, '|test:', x_test.shape)\r\n", "stdout": "train: (60000, 28, 28, 1) |test: (10000, 28, 28, 1)\n", "splitter_pos": [ - 206, - 85 + 85, + 208 ], "position": [ 44.48828125000068, @@ -199,7 +199,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -209,7 +209,7 @@ "type": "input", "position": [ 0.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -223,7 +223,7 @@ "type": "output", "position": [ 855.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -241,7 +241,7 @@ "source": "import tensorflow as tf\r\nfrom tensorflow.keras.layers import (Dense, Flatten,\r\nConv2D, MaxPooling2D, Dropout)\r\nfrom tensorflow.keras.models import Sequential\r\n\r\nmodel = Sequential()\r\nmodel.add(Conv2D(28, kernel_size=(3,3), input_shape=x_train.shape[1:]))\r\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\r\nmodel.add(Flatten())\r\nmodel.add(Dense(128, activation=tf.nn.relu))\r\nmodel.add(Dropout(0.2))\r\nmodel.add(Dense(10,activation=tf.nn.softmax))\r\n\r\nmodel.compile(optimizer='adam', \r\n loss='sparse_categorical_crossentropy', \r\n metrics=['accuracy'])\r\n", "stdout": "", "splitter_pos": [ - 418, + 420, 0 ], "position": [ @@ -254,7 +254,7 @@ "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -264,7 +264,7 @@ "type": "output", "position": [ 1002.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -277,25 +277,25 @@ }, { "id": 2828158533848, - "title": "Plot Image Dataset Example", + "title": "Plot Dataset Example", "block_type": "code", "source": "import matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\n# Display an example from the dataset\r\nrd_index = np.random.randint(len(x_train))\r\nplt.imshow(x_train[rd_index], cmap='gray')\r\nplt.title('Class '+ str(y_train[rd_index]))\r\n", "stdout": "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\n", "splitter_pos": [ 0, - 274 + 284 ], "position": [ - 103.60937500000011, - -734.9375 + 95.60937500000011, + -728.9375 ], "width": 300, - "height": 329, + "height": 337, "metadata": { "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, + "size": 12, "padding": 4.0 } }, @@ -305,7 +305,7 @@ "type": "input", "position": [ 0.0, - 42.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index cab85b86..f67ecd03 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -1,14 +1,13 @@ # OpenCodeBlock an open-source tool for modular visual programing in python # Copyright (C) 2021 Mathïs FEDERICO +# pylint:disable=unused-argument """ Module for the base OCB Block. """ -from typing import TYPE_CHECKING, Optional, OrderedDict, Tuple - -import time +from typing import TYPE_CHECKING, Optional, OrderedDict, Tuple, Union from PyQt5.QtCore import QPointF, QRectF, Qt -from PyQt5.QtGui import QBrush, QPen, QColor, QFont, QPainter, QPainterPath +from PyQt5.QtGui import QBrush, QPen, QColor, QPainter, QPainterPath from PyQt5.QtWidgets import ( QGraphicsItem, QGraphicsProxyWidget, @@ -39,11 +38,7 @@ def __init__( width: int = 300, height: int = 200, edge_size: float = 10.0, - title: str = "New block", - title_color: str = "white", - title_font: str = "Ubuntu", - title_size: int = 10, - title_padding=4.0, + title: Union[OCBTitle, str] = "New block", parent: Optional["QGraphicsItem"] = None, ): """Base class for blocks in OpenCodeBlocks. @@ -56,10 +51,6 @@ def __init__( height: Block height. edge_size: Block edges size. title: Block title. - title_color: Color of the block title. - title_font: Font of the block title. - title_size: Size of the block title. - title_padding: Padding of the block title. parent: Parent of the block. """ @@ -73,9 +64,6 @@ def __init__( self.sockets_in = [] self.sockets_out = [] - self.title_height = 3.5 * title_size - self.title_left_offset = 0 - self._pen_outline = QPen(QColor("#7F000000")) self._pen_outline_selected = QPen(QColor("#FFFFA637")) self._brush_background = QBrush(BACKGROUND_COLOR) @@ -90,9 +78,8 @@ def __init__( self.root.setAttribute(Qt.WA_TranslucentBackground) self.root.setGeometry(0, 0, int(width), int(height)) - self.title_widget = OCBTitle(title, self.root) + self.title_widget = OCBTitle(title, parent=self.root) self.title_widget.setAttribute(Qt.WA_TranslucentBackground) - self.setTitleGraphics(title_color, title_font, title_size, title_padding) self.splitter = OCBSplitter(self, Qt.Vertical, self.root) @@ -112,14 +99,7 @@ def __init__( self.height = height self.moved = False - self.metadata = { - "title_metadata": { - "color": title_color, - "font": title_font, - "size": title_size, - "padding": title_padding, - }, - } + self.metadata = {} def scene(self) -> "OCBScene": """Get the current OCBScene containing the block.""" @@ -129,34 +109,12 @@ def boundingRect(self) -> QRectF: """Get the the block bounding box.""" return QRectF(0, 0, self.width, self.height).normalized() - def setTitleGraphics(self, color: str, font: str, size: int, padding: float): - """Set the title graphics. - - Args: - color: title color. - font: title font. - size: title size. - padding: title padding. - - """ - # self.title_widget.setMargin(int(padding)) - self.title_widget.setStyleSheet( - f""" - QLineEdit {{ - color : {color}; - background-color: #FF0000; - border:none; - padding: {padding}px; - }}""" - ) - self.title_widget.setFont(QFont(font, size)) - def paint( self, painter: QPainter, - option: QStyleOptionGraphicsItem, # pylint:disable=unused-argument + option: QStyleOptionGraphicsItem, widget: Optional[QWidget] = None, - ): # pylint:disable=unused-argument + ): """Paint the block.""" # content @@ -189,7 +147,7 @@ def get_socket_pos(self, socket: OCBSocket) -> Tuple[float]: x = self.width sockets = self.sockets_out - y_offset = self.title_height + 2 * socket.radius + y_offset = self.title_widget.height() + 2 * socket.radius if len(sockets) < 2: y = y_offset else: @@ -246,9 +204,9 @@ def update_splitter(self): old_height = self.splitter.height() self.splitter.setGeometry( int(self.edge_size), - int(self.edge_size + self.title_height), + int(self.edge_size + self.title_widget.height()), int(self.width - self.edge_size * 2), - int(self.height - self.edge_size * 2 - self.title_height), + int(self.height - self.edge_size * 2 - self.title_widget.height()), ) if len(sizes) > 1: height_delta = self.splitter.height() - old_height @@ -257,9 +215,9 @@ def update_splitter(self): def update_title(self): self.title_widget.setGeometry( - int(self.edge_size + self.title_left_offset), + int(self.edge_size), int(self.edge_size / 2), - int(self.width - self.edge_size * 3 - self.title_left_offset), + int(self.width - self.edge_size * 3), int(self.title_widget.height()), ) @@ -307,6 +265,7 @@ def height(self, value: float): self.root.setGeometry(0, 0, self.root.width(), int(value)) def serialize(self) -> OrderedDict: + self.metadata.update({"title_metadata": self.title_widget.serialize()}) metadata = OrderedDict(sorted(self.metadata.items())) return OrderedDict( [ @@ -338,7 +297,9 @@ def deserialize(self, data: dict, hashmap: dict = None, restore_id=True) -> None self.setPos(QPointF(*data["position"])) self.metadata = dict(data["metadata"]) - self.setTitleGraphics(**self.metadata["title_metadata"]) + self.title_widget.deserialize( + self.metadata["title_metadata"], hashmap, restore_id + ) if "splitter_pos" in data: self.splitter.setSizes(data["splitter_pos"]) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index 2e54bda4..aff37959 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -81,6 +81,14 @@ def run_code(self): worker.signals.image.connect(self.handle_image) self.source_editor.threadpool.start(worker) + def update_title(self): + self.title_widget.setGeometry( + int(self.edge_size) + self.run_button.width(), + int(self.edge_size / 2), + int(self.width - self.edge_size * 3 - self.run_button.width()), + int(self.title_widget.height()), + ) + def update_output_panel(self): # Close output panel if no output if self.stdout == "": diff --git a/opencodeblocks/graphics/blocks/widgets/blocktitle.py b/opencodeblocks/graphics/blocks/widgets/blocktitle.py index e2ac01eb..617b18fd 100644 --- a/opencodeblocks/graphics/blocks/widgets/blocktitle.py +++ b/opencodeblocks/graphics/blocks/widgets/blocktitle.py @@ -1,27 +1,53 @@ +# pylint:disable=unused-argument import time +from typing import OrderedDict from PyQt5.QtCore import Qt -from PyQt5.QtGui import QFocusEvent, QMouseEvent +from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent from PyQt5.QtWidgets import QLineEdit, QWidget +from opencodeblocks.core.serializable import Serializable -class OCBTitle(QLineEdit): - """ The title of an OCBBlock. Needs to be double clicked to interact """ - def __init__(self, content: str, parent: QWidget = None): - """ Create a new title for an OCBBlock """ - super().__init__(content, parent) +class OCBTitle(QLineEdit, Serializable): + """The title of an OCBBlock. Needs to be double clicked to interact""" + + def __init__( + self, + text: str, + color: str = "white", + font: str = "Ubuntu", + size: int = 12, + parent: QWidget = None, + ): + """Create a new title for an OCBBlock""" + Serializable.__init__(self) + QLineEdit.__init__(self, text, parent) self.clickTime = None + self.init_ui(color, font, size) self.setReadOnly(True) self.setCursorPosition(0) + def init_ui(self, color: str, font: str, size: int): + self.color = color + self.setStyleSheet( + f""" + QLineEdit {{ + color : {self.color}; + background-color: #FF0000; + border:none; + }}""" + ) + self.setFont(QFont(font, size)) + def mousePressEvent(self, event: QMouseEvent): """ Detect double clicks and single clicks are react accordingly by dispatching the event to the parent or the current widget """ if self.clickTime is None or ( - self.isReadOnly() and time.time() - self.clickTime > 0.3): + self.isReadOnly() and time.time() - self.clickTime > 0.3 + ): self.parent().mousePressEvent(event) elif self.isReadOnly(): self.mouseDoubleClickEvent(event) @@ -31,13 +57,29 @@ def mousePressEvent(self, event: QMouseEvent): self.clickTime = time.time() def focusOutEvent(self, event: QFocusEvent): - """ The title is read-only when focused is lost """ + """The title is read-only when focused is lost""" self.setReadOnly(True) self.setCursorPosition(0) self.deselect() def mouseDoubleClickEvent(self, event: QMouseEvent): - """ Toggle readonly mode when double clicking """ + """Toggle readonly mode when double clicking""" self.setReadOnly(not self.isReadOnly()) if not self.isReadOnly(): self.setFocus(Qt.MouseFocusReason) + + def serialize(self) -> OrderedDict: + return OrderedDict( + [ + ("color", self.color), + ("font", self.font().family()), + ("size", self.font().pointSize()), + ] + ) + + def deserialize( + self, data: OrderedDict, hashmap: dict = None, restore_id=True + ) -> None: + if restore_id: + self.id = data.get("id", id(self)) + self.init_ui(data["color"], data["font"], data["size"]) From 71d58fc7aa631d3cc1f8e5bac7ff0e03efbefa57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Tue, 30 Nov 2021 22:07:28 +0100 Subject: [PATCH 16/20] :beetle: Fix test_block --- tests/integration/blocks/test_block.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py index c302a0fb..f8fb019e 100644 --- a/tests/integration/blocks/test_block.py +++ b/tests/integration/blocks/test_block.py @@ -19,10 +19,9 @@ class TestBlocks: - @pytest.fixture(autouse=True) def setup(self): - """ Setup reused variables. """ + """Setup reused variables.""" self.window = OCBWindow() self.ocb_widget = OCBWidget() self.subwindow = self.window.mdiArea.addSubWindow(self.ocb_widget) @@ -30,11 +29,11 @@ def setup(self): self.block = OCBBlock(title="Testing block") def test_create_blocks(self, qtbot: QtBot): - """ can be added to the scene. """ + """can be added to the scene.""" self.ocb_widget.scene.addItem(self.block) def test_move_blocks(self, qtbot: QtBot): - """ can be dragged around with the mouse. """ + """can be dragged around with the mouse.""" self.ocb_widget.scene.addItem(self.block) self.ocb_widget.view.horizontalScrollBar().setValue(self.block.x()) self.ocb_widget.view.verticalScrollBar().setValue( @@ -49,9 +48,9 @@ def testing_drag(msgQueue: CheckingQueue): pos_block = QPointF(self.block.pos().x(), self.block.pos().y()) pos_block.setX( - pos_block.x() + self.block.title_height + self.block.edge_size + pos_block.x() + self.block.title_widget.height() + self.block.edge_size ) - pos_block.setY(pos_block.y() + self.block.title_height/2) + pos_block.setY(pos_block.y() + self.block.title_widget.height() / 2) pos_block = self.ocb_widget.view.mapFromScene(pos_block) pos_block = self.ocb_widget.view.mapToGlobal(pos_block) @@ -60,10 +59,10 @@ def testing_drag(msgQueue: CheckingQueue): pyautogui.mouseDown(button="left") iterations = 5 - for i in range(iterations+1): + for i in range(iterations + 1): pyautogui.moveTo( pos_block.x() + expected_move_amount[0] * i / iterations, - pos_block.y() + expected_move_amount[1] * i / iterations + pos_block.y() + expected_move_amount[1] * i / iterations, ) pyautogui.mouseUp(button="left") @@ -74,7 +73,8 @@ def testing_drag(msgQueue: CheckingQueue): move_amount[1] = move_amount[1] * self.ocb_widget.view.zoom msgQueue.check_equal( - move_amount, expected_move_amount, "Block moved by the correct amound") + move_amount, expected_move_amount, "Block moved by the correct amound" + ) msgQueue.stop() apply_function_inapp(self.window, testing_drag) From b39e41c370200dada01a057380136b5f8e79575d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Tue, 30 Nov 2021 22:23:29 +0100 Subject: [PATCH 17/20] :sparkles: Remove false positive linting --- .pylintrc | 1 + opencodeblocks/graphics/blocks/block.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.pylintrc b/.pylintrc index a45db43c..c58e25a3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -61,6 +61,7 @@ confidence= # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=inconsistent-return-statements, + unidiomatic-typecheck, attribute-defined-outside-init, pointless-statement, no-self-use, diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index f67ecd03..ab7c1f1c 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -85,9 +85,7 @@ def __init__( self.size_grip = OCBSizeGrip(self, self.root) - if ( - type(self) == OCBBlock - ): # DO NOT TRUST codacy !!! type(self) should be used, not isinstance. + if type(self) == OCBBlock: # This has to be called at the end of the constructor of # every class inheriting this. self.holder.setWidget(self.root) From 4fca93163451c102ce817bdc8a882b13d1c24007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= <60117466+MathisFederico@users.noreply.github.com> Date: Tue, 30 Nov 2021 22:29:24 +0100 Subject: [PATCH 18/20] :wrench: Make titles background transparent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antoine Delègue --- opencodeblocks/graphics/blocks/widgets/blocktitle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencodeblocks/graphics/blocks/widgets/blocktitle.py b/opencodeblocks/graphics/blocks/widgets/blocktitle.py index 617b18fd..7c02ac79 100644 --- a/opencodeblocks/graphics/blocks/widgets/blocktitle.py +++ b/opencodeblocks/graphics/blocks/widgets/blocktitle.py @@ -34,7 +34,7 @@ def init_ui(self, color: str, font: str, size: int): f""" QLineEdit {{ color : {self.color}; - background-color: #FF0000; + background-color: transparent; border:none; }}""" ) From 1ffaf92e09aa1cb039c16ebb36a6a4f712ed6397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 22:28:27 +0100 Subject: [PATCH 19/20] :memo: Add a lot of docstrings :beetle: Made the background color of title transparent instead of red --- opencodeblocks/graphics/blocks/block.py | 7 ++++++- opencodeblocks/graphics/blocks/codeblock.py | 5 ++++- .../graphics/blocks/widgets/blocksplitter.py | 5 ++++- opencodeblocks/graphics/blocks/widgets/blocktitle.py | 11 +++++++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index ab7c1f1c..d9fc4d5b 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -196,6 +196,7 @@ def remove(self): scene.removeItem(self) def update_splitter(self): + """ Change the geometry of the splitter to match the block """ # We make the resizing of splitter only affect # the last element of the split view sizes = self.splitter.sizes() @@ -212,6 +213,7 @@ def update_splitter(self): self.splitter.setSizes(sizes) def update_title(self): + """ Change the geometry of the title to match the block """ self.title_widget.setGeometry( int(self.edge_size), int(self.edge_size / 2), @@ -220,6 +222,7 @@ def update_title(self): ) def update_size_grip(self): + """ Change the geometry of the size grip to match the block""" self.size_grip.setGeometry( int(self.width - self.edge_size * 2), int(self.height - self.edge_size * 2), @@ -228,7 +231,7 @@ def update_size_grip(self): ) def update_all(self): - """Update sockets and title.""" + """ Update sockets and title.""" self.update_sockets() self.update_splitter() self.update_title() @@ -263,6 +266,7 @@ def height(self, value: float): self.root.setGeometry(0, 0, self.root.width(), int(value)) def serialize(self) -> OrderedDict: + """ Return a serialized version of this widget """ self.metadata.update({"title_metadata": self.title_widget.serialize()}) metadata = OrderedDict(sorted(self.metadata.items())) return OrderedDict( @@ -288,6 +292,7 @@ def serialize(self) -> OrderedDict: ) def deserialize(self, data: dict, hashmap: dict = None, restore_id=True) -> None: + """ Restore the block from serialized data """ if restore_id: self.id = data["id"] for dataname in ("title", "block_type", "source", "stdout", "width", "height"): diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index aff37959..862adee7 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -29,7 +29,10 @@ class OCBCodeBlock(OCBBlock): """ def __init__(self, **kwargs): - + """ + Create a new OCBCodeBlock. + Initialize all the child widgets specific to this block type + """ self.source_editor = PythonEditor(self) super().__init__(block_type="code", **kwargs) diff --git a/opencodeblocks/graphics/blocks/widgets/blocksplitter.py b/opencodeblocks/graphics/blocks/widgets/blocksplitter.py index 4afc8697..31137fb0 100644 --- a/opencodeblocks/graphics/blocks/widgets/blocksplitter.py +++ b/opencodeblocks/graphics/blocks/widgets/blocksplitter.py @@ -1,4 +1,7 @@ -from typing import TYPE_CHECKING +""" +Module defining a Splitter, the widget that contains multiple areas inside +a block and allows the user to resize those areas. +""" from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QSplitter, QSplitterHandle, QWidget diff --git a/opencodeblocks/graphics/blocks/widgets/blocktitle.py b/opencodeblocks/graphics/blocks/widgets/blocktitle.py index 7c02ac79..95c1fd3a 100644 --- a/opencodeblocks/graphics/blocks/widgets/blocktitle.py +++ b/opencodeblocks/graphics/blocks/widgets/blocktitle.py @@ -1,4 +1,8 @@ # pylint:disable=unused-argument +""" +Module defining the widget for the title of blocks. +It's a QLineEdit modified so that editing it requires a double click. +""" import time from typing import OrderedDict @@ -20,7 +24,7 @@ def __init__( size: int = 12, parent: QWidget = None, ): - """Create a new title for an OCBBlock""" + """ Create a new title for an OCBBlock """ Serializable.__init__(self) QLineEdit.__init__(self, text, parent) self.clickTime = None @@ -29,6 +33,7 @@ def __init__( self.setCursorPosition(0) def init_ui(self, color: str, font: str, size: int): + """ Apply the style given to the title """ self.color = color self.setStyleSheet( f""" @@ -69,6 +74,7 @@ def mouseDoubleClickEvent(self, event: QMouseEvent): self.setFocus(Qt.MouseFocusReason) def serialize(self) -> OrderedDict: + """ Return a serialized version of this widget """ return OrderedDict( [ ("color", self.color), @@ -79,7 +85,8 @@ def serialize(self) -> OrderedDict: def deserialize( self, data: OrderedDict, hashmap: dict = None, restore_id=True - ) -> None: + ): + """ Restore a title from serialized data """ if restore_id: self.id = data.get("id", id(self)) self.init_ui(data["color"], data["font"], data["size"]) From 73f736f260f1a6d3734b0e81d19d4f5a60fb4cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Tue, 30 Nov 2021 22:47:02 +0100 Subject: [PATCH 20/20] :truck: Renamed a lot of files to make the graphics directory less cluttered. --- opencodeblocks/{graphics => }/blocks/__init__.py | 4 ++-- opencodeblocks/{graphics => }/blocks/block.py | 2 +- opencodeblocks/{graphics => }/blocks/codeblock.py | 2 +- opencodeblocks/blocks/widgets/__init__.py | 8 ++++++++ .../{graphics => }/blocks/widgets/blocksizegrip.py | 0 .../{graphics => }/blocks/widgets/blocksplitter.py | 0 .../{graphics => }/blocks/widgets/blocktitle.py | 0 opencodeblocks/graphics/blocks/widgets/__init__.py | 8 -------- opencodeblocks/graphics/pyeditor.py | 2 +- opencodeblocks/graphics/socket.py | 2 +- opencodeblocks/graphics/view.py | 4 ++-- opencodeblocks/graphics/widget.py | 2 +- opencodeblocks/graphics/window.py | 6 +++--- opencodeblocks/{graphics => }/qss/__init__.py | 2 +- .../{graphics => }/qss/dark_resources.py | 0 opencodeblocks/{graphics => }/qss/ocb.qss | 0 opencodeblocks/{graphics => }/qss/ocb_dark.qss | 0 opencodeblocks/{graphics => }/scene/__init__.py | 2 +- opencodeblocks/{graphics => }/scene/clipboard.py | 2 +- opencodeblocks/{graphics => }/scene/history.py | 2 +- opencodeblocks/{graphics => }/scene/scene.py | 8 ++++---- tests/integration/blocks/test_block.py | 2 +- tests/integration/blocks/test_codeblock.py | 2 +- tests/unit/scene/test_clipboard.py | 2 +- tests/unit/scene/test_history.py | 14 +++++++------- 25 files changed, 38 insertions(+), 38 deletions(-) rename opencodeblocks/{graphics => }/blocks/__init__.py (67%) rename opencodeblocks/{graphics => }/blocks/block.py (99%) rename opencodeblocks/{graphics => }/blocks/codeblock.py (98%) create mode 100644 opencodeblocks/blocks/widgets/__init__.py rename opencodeblocks/{graphics => }/blocks/widgets/blocksizegrip.py (100%) rename opencodeblocks/{graphics => }/blocks/widgets/blocksplitter.py (100%) rename opencodeblocks/{graphics => }/blocks/widgets/blocktitle.py (100%) delete mode 100644 opencodeblocks/graphics/blocks/widgets/__init__.py rename opencodeblocks/{graphics => }/qss/__init__.py (91%) rename opencodeblocks/{graphics => }/qss/dark_resources.py (100%) rename opencodeblocks/{graphics => }/qss/ocb.qss (100%) rename opencodeblocks/{graphics => }/qss/ocb_dark.qss (100%) rename opencodeblocks/{graphics => }/scene/__init__.py (78%) rename opencodeblocks/{graphics => }/scene/clipboard.py (98%) rename opencodeblocks/{graphics => }/scene/history.py (97%) rename opencodeblocks/{graphics => }/scene/scene.py (96%) diff --git a/opencodeblocks/graphics/blocks/__init__.py b/opencodeblocks/blocks/__init__.py similarity index 67% rename from opencodeblocks/graphics/blocks/__init__.py rename to opencodeblocks/blocks/__init__.py index 893604f8..f88fde65 100644 --- a/opencodeblocks/graphics/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -3,8 +3,8 @@ """ Module for the OCB Blocks of different types. """ -from opencodeblocks.graphics.blocks.block import OCBBlock -from opencodeblocks.graphics.blocks.codeblock import OCBCodeBlock +from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.codeblock import OCBCodeBlock BLOCKS = { 'base': OCBBlock, diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/blocks/block.py similarity index 99% rename from opencodeblocks/graphics/blocks/block.py rename to opencodeblocks/blocks/block.py index d9fc4d5b..e6f3384a 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/blocks/block.py @@ -18,7 +18,7 @@ from opencodeblocks.core.serializable import Serializable from opencodeblocks.graphics.socket import OCBSocket -from opencodeblocks.graphics.blocks.widgets import OCBSplitter, OCBSizeGrip, OCBTitle +from opencodeblocks.blocks.widgets import OCBSplitter, OCBSizeGrip, OCBTitle if TYPE_CHECKING: from opencodeblocks.graphics.scene.scene import OCBScene diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py similarity index 98% rename from opencodeblocks/graphics/blocks/codeblock.py rename to opencodeblocks/blocks/codeblock.py index 862adee7..d77a8ce1 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -9,7 +9,7 @@ from ansi2html import Ansi2HTMLConverter -from opencodeblocks.graphics.blocks.block import OCBBlock +from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.graphics.pyeditor import PythonEditor from opencodeblocks.graphics.worker import Worker diff --git a/opencodeblocks/blocks/widgets/__init__.py b/opencodeblocks/blocks/widgets/__init__.py new file mode 100644 index 00000000..d62d3431 --- /dev/null +++ b/opencodeblocks/blocks/widgets/__init__.py @@ -0,0 +1,8 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python +# Copyright (C) 2021 Mathïs FEDERICO + +""" Module for the OCB Blocks Widgets. """ + +from opencodeblocks.blocks.widgets.blocksplitter import OCBSplitter +from opencodeblocks.blocks.widgets.blocktitle import OCBTitle +from opencodeblocks.blocks.widgets.blocksizegrip import OCBSizeGrip diff --git a/opencodeblocks/graphics/blocks/widgets/blocksizegrip.py b/opencodeblocks/blocks/widgets/blocksizegrip.py similarity index 100% rename from opencodeblocks/graphics/blocks/widgets/blocksizegrip.py rename to opencodeblocks/blocks/widgets/blocksizegrip.py diff --git a/opencodeblocks/graphics/blocks/widgets/blocksplitter.py b/opencodeblocks/blocks/widgets/blocksplitter.py similarity index 100% rename from opencodeblocks/graphics/blocks/widgets/blocksplitter.py rename to opencodeblocks/blocks/widgets/blocksplitter.py diff --git a/opencodeblocks/graphics/blocks/widgets/blocktitle.py b/opencodeblocks/blocks/widgets/blocktitle.py similarity index 100% rename from opencodeblocks/graphics/blocks/widgets/blocktitle.py rename to opencodeblocks/blocks/widgets/blocktitle.py diff --git a/opencodeblocks/graphics/blocks/widgets/__init__.py b/opencodeblocks/graphics/blocks/widgets/__init__.py deleted file mode 100644 index 5f2b3607..00000000 --- a/opencodeblocks/graphics/blocks/widgets/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# OpenCodeBlock an open-source tool for modular visual programing in python -# Copyright (C) 2021 Mathïs FEDERICO - -""" Module for the OCB Blocks Widgets. """ - -from opencodeblocks.graphics.blocks.widgets.blocksplitter import OCBSplitter -from opencodeblocks.graphics.blocks.widgets.blocktitle import OCBTitle -from opencodeblocks.graphics.blocks.widgets.blocksizegrip import OCBSizeGrip diff --git a/opencodeblocks/graphics/pyeditor.py b/opencodeblocks/graphics/pyeditor.py index 0ab39a94..d7db6b62 100644 --- a/opencodeblocks/graphics/pyeditor.py +++ b/opencodeblocks/graphics/pyeditor.py @@ -9,7 +9,7 @@ from PyQt5.Qsci import QsciScintilla, QsciLexerPython from opencodeblocks.graphics.theme_manager import theme_manager -from opencodeblocks.graphics.blocks.block import OCBBlock +from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.graphics.kernel import Kernel kernel = Kernel() diff --git a/opencodeblocks/graphics/socket.py b/opencodeblocks/graphics/socket.py index 484ad501..d4fbd152 100644 --- a/opencodeblocks/graphics/socket.py +++ b/opencodeblocks/graphics/socket.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from opencodeblocks.graphics.edge import OCBEdge - from opencodeblocks.graphics.blocks.block import OCBBlock + from opencodeblocks.blocks.block import OCBBlock class OCBSocket(QGraphicsItem, Serializable): diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py index c76ffd91..be48822d 100644 --- a/opencodeblocks/graphics/view.py +++ b/opencodeblocks/graphics/view.py @@ -13,10 +13,10 @@ from PyQt5.sip import isdeleted -from opencodeblocks.graphics.scene import OCBScene +from opencodeblocks.scene import OCBScene from opencodeblocks.graphics.socket import OCBSocket from opencodeblocks.graphics.edge import OCBEdge -from opencodeblocks.graphics.blocks import OCBBlock +from opencodeblocks.blocks import OCBBlock class OCBView(QGraphicsView): diff --git a/opencodeblocks/graphics/widget.py b/opencodeblocks/graphics/widget.py index 761b5ebf..0a847e04 100644 --- a/opencodeblocks/graphics/widget.py +++ b/opencodeblocks/graphics/widget.py @@ -8,7 +8,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QWidget from PyQt5.QtCore import Qt -from opencodeblocks.graphics.scene import OCBScene +from opencodeblocks.scene import OCBScene from opencodeblocks.graphics.view import OCBView diff --git a/opencodeblocks/graphics/window.py b/opencodeblocks/graphics/window.py index d50f1b7e..e344ea70 100644 --- a/opencodeblocks/graphics/window.py +++ b/opencodeblocks/graphics/window.py @@ -13,7 +13,7 @@ from opencodeblocks.graphics.widget import OCBWidget from opencodeblocks.graphics.theme_manager import theme_manager -from opencodeblocks.graphics.qss import loadStylesheets +from opencodeblocks.qss import loadStylesheets class OCBWindow(QMainWindow): @@ -24,9 +24,9 @@ def __init__(self): super().__init__() self.stylesheet_filename = os.path.join( - os.path.dirname(__file__), 'qss', 'ocb.qss') + os.path.dirname(__file__),'..', 'qss', 'ocb.qss') loadStylesheets(( - os.path.join(os.path.dirname(__file__), 'qss', 'ocb_dark.qss'), + os.path.join(os.path.dirname(__file__),'..', 'qss', 'ocb_dark.qss'), self.stylesheet_filename )) diff --git a/opencodeblocks/graphics/qss/__init__.py b/opencodeblocks/qss/__init__.py similarity index 91% rename from opencodeblocks/graphics/qss/__init__.py rename to opencodeblocks/qss/__init__.py index a44a510c..16024f9c 100644 --- a/opencodeblocks/graphics/qss/__init__.py +++ b/opencodeblocks/qss/__init__.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import QFile from PyQt5.QtWidgets import QApplication -from opencodeblocks.graphics.qss import dark_resources +from opencodeblocks.qss import dark_resources def loadStylesheets(filenames: List[str]): diff --git a/opencodeblocks/graphics/qss/dark_resources.py b/opencodeblocks/qss/dark_resources.py similarity index 100% rename from opencodeblocks/graphics/qss/dark_resources.py rename to opencodeblocks/qss/dark_resources.py diff --git a/opencodeblocks/graphics/qss/ocb.qss b/opencodeblocks/qss/ocb.qss similarity index 100% rename from opencodeblocks/graphics/qss/ocb.qss rename to opencodeblocks/qss/ocb.qss diff --git a/opencodeblocks/graphics/qss/ocb_dark.qss b/opencodeblocks/qss/ocb_dark.qss similarity index 100% rename from opencodeblocks/graphics/qss/ocb_dark.qss rename to opencodeblocks/qss/ocb_dark.qss diff --git a/opencodeblocks/graphics/scene/__init__.py b/opencodeblocks/scene/__init__.py similarity index 78% rename from opencodeblocks/graphics/scene/__init__.py rename to opencodeblocks/scene/__init__.py index efb3c80c..5da0fc6b 100644 --- a/opencodeblocks/graphics/scene/__init__.py +++ b/opencodeblocks/scene/__init__.py @@ -3,4 +3,4 @@ """ Module for the OCBScene creation and manipulations. """ -from opencodeblocks.graphics.scene.scene import OCBScene +from opencodeblocks.scene.scene import OCBScene diff --git a/opencodeblocks/graphics/scene/clipboard.py b/opencodeblocks/scene/clipboard.py similarity index 98% rename from opencodeblocks/graphics/scene/clipboard.py rename to opencodeblocks/scene/clipboard.py index c8e02fd6..9e6290fd 100644 --- a/opencodeblocks/graphics/scene/clipboard.py +++ b/opencodeblocks/scene/clipboard.py @@ -9,7 +9,7 @@ import json from PyQt5.QtWidgets import QApplication -from opencodeblocks.graphics.blocks import OCBBlock, OCBCodeBlock +from opencodeblocks.blocks import OCBBlock, OCBCodeBlock from opencodeblocks.graphics.edge import OCBEdge if TYPE_CHECKING: diff --git a/opencodeblocks/graphics/scene/history.py b/opencodeblocks/scene/history.py similarity index 97% rename from opencodeblocks/graphics/scene/history.py rename to opencodeblocks/scene/history.py index a6baabe0..9d82a8a1 100644 --- a/opencodeblocks/graphics/scene/history.py +++ b/opencodeblocks/scene/history.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from opencodeblocks.graphics.scene import OCBScene + from opencodeblocks.scene import OCBScene class SceneHistory(): diff --git a/opencodeblocks/graphics/scene/scene.py b/opencodeblocks/scene/scene.py similarity index 96% rename from opencodeblocks/graphics/scene/scene.py rename to opencodeblocks/scene/scene.py index fa99e84b..772ba2b2 100644 --- a/opencodeblocks/graphics/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -13,11 +13,11 @@ from PyQt5.QtWidgets import QGraphicsScene from opencodeblocks.core.serializable import Serializable -from opencodeblocks.graphics.blocks.block import OCBBlock -from opencodeblocks.graphics.blocks.codeblock import OCBCodeBlock +from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.graphics.edge import OCBEdge -from opencodeblocks.graphics.scene.clipboard import SceneClipboard -from opencodeblocks.graphics.scene.history import SceneHistory +from opencodeblocks.scene.clipboard import SceneClipboard +from opencodeblocks.scene.history import SceneHistory class OCBScene(QGraphicsScene, Serializable): diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py index f8fb019e..02e6253e 100644 --- a/tests/integration/blocks/test_block.py +++ b/tests/integration/blocks/test_block.py @@ -11,7 +11,7 @@ from PyQt5.QtCore import QPointF -from opencodeblocks.graphics.blocks.codeblock import OCBBlock +from opencodeblocks.blocks.codeblock import OCBBlock from opencodeblocks.graphics.window import OCBWindow from opencodeblocks.graphics.widget import OCBWidget diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py index 42f3cd2f..7ab6b67b 100644 --- a/tests/integration/blocks/test_codeblock.py +++ b/tests/integration/blocks/test_codeblock.py @@ -11,7 +11,7 @@ from PyQt5.QtCore import QPointF -from opencodeblocks.graphics.blocks.codeblock import OCBCodeBlock +from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.graphics.window import OCBWindow from opencodeblocks.graphics.widget import OCBWidget diff --git a/tests/unit/scene/test_clipboard.py b/tests/unit/scene/test_clipboard.py index d5b682e9..99739f20 100644 --- a/tests/unit/scene/test_clipboard.py +++ b/tests/unit/scene/test_clipboard.py @@ -7,7 +7,7 @@ from pytest_mock import MockerFixture import pytest_check as check -from opencodeblocks.graphics.scene.clipboard import SceneClipboard +from opencodeblocks.scene.clipboard import SceneClipboard class TestSerializeSelected: diff --git a/tests/unit/scene/test_history.py b/tests/unit/scene/test_history.py index 52367d34..756e39be 100644 --- a/tests/unit/scene/test_history.py +++ b/tests/unit/scene/test_history.py @@ -7,7 +7,7 @@ from pytest_mock import MockerFixture import pytest_check as check -from opencodeblocks.graphics.scene.history import SceneHistory +from opencodeblocks.scene.history import SceneHistory class TestUndo: @@ -23,7 +23,7 @@ def setup(self, mocker:MockerFixture): def test_undo(self, mocker:MockerFixture): """ should allow for undo without breaking the history stack.""" - mocker.patch('opencodeblocks.graphics.scene.history.SceneHistory.restore') + mocker.patch('opencodeblocks.scene.history.SceneHistory.restore') check.equal(self.history.history_stack, ['A', 'B', 'C', 'D']) check.equal(self.history.history_stack[self.history.current], 'D') @@ -36,7 +36,7 @@ def test_undo(self, mocker:MockerFixture): def test_undo_nostack(self, mocker:MockerFixture): """ should allow to undo without any change if the history stack is empty.""" - mocker.patch('opencodeblocks.graphics.scene.history.SceneHistory.restore') + mocker.patch('opencodeblocks.scene.history.SceneHistory.restore') self.history.history_stack = [] self.history.current = -1 @@ -49,7 +49,7 @@ def test_undo_nostack(self, mocker:MockerFixture): def test_undo_end_of_stack(self, mocker:MockerFixture): """ should allow to undo without any change if at the end of the history stack.""" - mocker.patch('opencodeblocks.graphics.scene.history.SceneHistory.restore') + mocker.patch('opencodeblocks.scene.history.SceneHistory.restore') self.history.current = 0 check.equal(self.history.history_stack, ['A', 'B', 'C', 'D']) @@ -75,7 +75,7 @@ def setup(self, mocker:MockerFixture): def test_redo(self, mocker:MockerFixture): """ should allow for redo without changing the history stack.""" - mocker.patch('opencodeblocks.graphics.scene.history.SceneHistory.restore') + mocker.patch('opencodeblocks.scene.history.SceneHistory.restore') check.equal(self.history.history_stack, ['A', 'B', 'C', 'D']) check.equal(self.history.history_stack[self.history.current], 'B') @@ -88,7 +88,7 @@ def test_redo(self, mocker:MockerFixture): def test_redo_nostack(self, mocker:MockerFixture): """ should allow to redo without any change if the history stack is empty.""" - mocker.patch('opencodeblocks.graphics.scene.history.SceneHistory.restore') + mocker.patch('opencodeblocks.scene.history.SceneHistory.restore') self.history.history_stack = [] self.history.current = -1 @@ -101,7 +101,7 @@ def test_redo_nostack(self, mocker:MockerFixture): def test_redo_end_of_stack(self, mocker:MockerFixture): """ should allow to redo without any change if at the beggining of the history stack.""" - mocker.patch('opencodeblocks.graphics.scene.history.SceneHistory.restore') + mocker.patch('opencodeblocks.scene.history.SceneHistory.restore') self.history.current = 3 check.equal(self.history.history_stack, ['A', 'B', 'C', 'D'])