From c106af317cbf4cfa0e06307de5426add268ba1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 13 Nov 2021 16:48:57 +0100 Subject: [PATCH 01/13] The cursor changes to resize mode when hovering over the resizing area. This makes it easier to locate the resizing area when using the interface. --- opencodeblocks/graphics/blocks/block.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 90630bf6..c82f7e73 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -72,7 +72,10 @@ def __init__(self, block_type:str='base', source:str='', position:tuple=(0, 0), self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + self.setAcceptHoverEvents(True) + self.resizing = False + self.resizing_hover = False # Is the mouse hovering over the resizing area ? self.moved = False self.metadata = { 'title_metadata': { @@ -172,6 +175,24 @@ def remove_socket(self, socket:OCBSocket): socket.remove() self.update_sockets() + def hoverMoveEvent(self, event): + pos = event.pos() + if self._is_in_resize_area(pos): + if not self.resizing_hover: + self.resizing_hover = True + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + elif self.resizing_hover: + self.resizing_hover = False + QApplication.restoreOverrideCursor() + + return super().hoverMoveEvent(event) + def hoverLeaveEvent(self, event): + if self.resizing_hover: + self.resizing_hover = False + QApplication.restoreOverrideCursor() + + return super().hoverLeaveEvent(event) + def mousePressEvent(self, event:QGraphicsSceneMouseEvent): """ OCBBlock reaction to a mousePressEvent. """ pos = event.pos() From 4560323d190ea481f4d5cb29fe63a441d574e084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 13 Nov 2021 18:05:38 +0100 Subject: [PATCH 02/13] Add ability to resize the source_editor and the output display part of a block independently. During this process, some duplicated code for setting the size of said panel is removed. --- opencodeblocks/graphics/blocks/block.py | 2 + opencodeblocks/graphics/blocks/codeblock.py | 138 +++++++++++++++++--- 2 files changed, 119 insertions(+), 21 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index c82f7e73..a704b75d 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -176,6 +176,7 @@ def remove_socket(self, socket:OCBSocket): self.update_sockets() def hoverMoveEvent(self, event): + """ Triggered when hovering over a block """ pos = event.pos() if self._is_in_resize_area(pos): if not self.resizing_hover: @@ -187,6 +188,7 @@ def hoverMoveEvent(self, event): return super().hoverMoveEvent(event) def hoverLeaveEvent(self, event): + """ Triggered when the mouse stops hovering over a block """ if self.resizing_hover: self.resizing_hover = False QApplication.restoreOverrideCursor() diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index 7ede6bb6..6f956ce7 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -5,38 +5,61 @@ from typing import Optional -from PyQt5.QtCore import Qt, QByteArray +from PyQt5.QtCore import Qt, QByteArray, QPointF from PyQt5.QtGui import QPainter, QPainterPath, QPixmap -from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget, QGraphicsProxyWidget, QLabel +from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget, QGraphicsProxyWidget, QLabel, \ + QGraphicsSceneMouseEvent, QApplication from opencodeblocks.graphics.blocks.block import OCBBlock from opencodeblocks.graphics.pyeditor import PythonEditor class OCBCodeBlock(OCBBlock): - """ Code Block. """ + """ + Code Block + + Features an area to edit code as well as a panel to display the output. + + """ def __init__(self, **kwargs): + """ + Note that self.output_panel_height < self.height, + because the output panel is part of the display. + Moreover, the following is always true: + output_panel_height + source_panel_height + edge_size*2 + title_height == height + """ super().__init__(block_type='code', **kwargs) + + + self.output_panel_height = 100 + self._min_output_panel_height = 20 + self._min_source_editor_height = 20 + + assert self.height - self.output_panel_height \ + - self.title_height - self.edge_size*2 > 0 + self.source_editor = self.init_source_editor() self.display = self.init_display() self.stdout = "" self.image = "" + self.resizing_source_code = False + + self.update_all() # Set the geometry of display and source_editor + def init_source_editor(self): """ Initialize the python source code editor. """ source_editor_graphics = QGraphicsProxyWidget(self) source_editor = PythonEditor(self) - source_editor.setGeometry( - int(self.edge_size), - int(self.edge_size + self.title_height), - int(self.width - 2*self.edge_size), - int(self.height - self.title_height - 2*self.edge_size) - ) source_editor_graphics.setWidget(source_editor) source_editor_graphics.setZValue(-1) return source_editor_graphics + def _editor_widget_height(self): + return self.height - self.title_height - 2*self.edge_size \ + - self.output_panel_height + def update_all(self): """ Update the code block parts. """ if hasattr(self, 'source_editor'): @@ -45,14 +68,14 @@ def update_all(self): int(self.edge_size), int(self.edge_size + self.title_height), int(self._width - 2*self.edge_size), - int(self.height - self.title_height - 2*self.edge_size) + int(self._editor_widget_height()) ) display_widget = self.display.widget() display_widget.setGeometry( int(self.edge_size), - int(self.height + self.edge_size), + int(self.height - self.output_panel_height - self.edge_size), int(self.width - 2*self.edge_size), - int(self.height*0.3 - 2*self.edge_size) + int(self.output_panel_height) ) super().update_all() @@ -69,7 +92,7 @@ def source(self, value:str): @property def stdout(self) -> str: - """ Code output. """ + """ Code output, without errors """ return self._stdout @stdout.setter def stdout(self, value:str): @@ -111,23 +134,96 @@ def paint(self, painter: QPainter, super().paint(painter, option, widget) path_title = QPainterPath() path_title.setFillRule(Qt.FillRule.WindingFill) - path_title.addRoundedRect(0, 0, self.width, 1.3*self.height, + path_title.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_title.simplified()) + + + def _is_in_code_output_resize_area(self, pos:QPointF): + """ Return True if the given position is in the block resize_area. """ + source_editor_start = self.height - self.output_panel_height - self.edge_size + + return self.width - 2 * self.edge_size < pos.x() and \ + source_editor_start - self.edge_size < pos.y() < source_editor_start + self.edge_size + + def hoverMoveEvent(self, event): + """ Triggered when hovering over a block """ + pos = event.pos() + if self._is_in_resize_area(pos) or self._is_in_code_output_resize_area(pos): + if not self.resizing_hover: + self.resizing_hover = True + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + elif self.resizing_hover: + self.resizing_hover = False + QApplication.restoreOverrideCursor() + # Don't call super() because this might override the cursor + def hoverLeaveEvent(self, event): + """ Triggered when the mouse stops hovering over a block """ + if self.resizing_hover: + self.resizing_hover = False + QApplication.restoreOverrideCursor() + # Don't call super() because this might override the cursor + + def mousePressEvent(self, event:QGraphicsSceneMouseEvent): + """ OCBBlock reaction to a mousePressEvent. """ + pos = event.pos() + resizing_source_code = self._is_in_code_output_resize_area(pos) + if (self._is_in_resize_area(pos) or resizing_source_code) and \ + event.buttons() == Qt.MouseButton.LeftButton: + self.resize_start = pos + self.resizing = True + self.resizing_source_code = resizing_source_code + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + + super().mousePressEvent(event) + + def mouseReleaseEvent(self, event:QGraphicsSceneMouseEvent): + """ OCBBlock reaction to a mouseReleaseEvent. """ + if self.resizing: + self.scene().history.checkpoint("Resized block", set_modified=True) + self.resizing = False + self.resizing_source_code = False + QApplication.restoreOverrideCursor() + if self.moved: + self.moved = False + self.scene().history.checkpoint("Moved block", set_modified=True) + super().mouseReleaseEvent(event) + + def mouseMoveEvent(self, event:QGraphicsSceneMouseEvent): + """ + We override the default resizing behavior as the code part and the display part of the block + block can be resized. + """ + if self.resizing: + delta = event.pos() - self.resize_start + self.width = max(self.width + delta.x(), self._min_width) + + height_delta = max(delta.y(), + # List of all the quantities that must remain negative. + # Mainly: min_height - height must be negative for all elements + self._min_output_panel_height - self.output_panel_height, + self._min_height - self.height, + self._min_source_editor_height - self._editor_widget_height() + ) + + self.height += height_delta + if not self.resizing_source_code: + self.output_panel_height += height_delta + + self.resize_start = event.pos() + self.title_graphics.setTextWidth(self.width - 2 * self.edge_size) + self.update() + else: + super().mouseMoveEvent(event) + self.moved = True def init_display(self): - """ Initialize the ouptput display widget: QLabel """ + """ Initialize the output display widget: QLabel """ display_graphics = QGraphicsProxyWidget(self) display = QLabel() display.setText("") - display.setGeometry( - int(self.edge_size), - int(self.edge_size + self.height), - int(self.width - 2*self.edge_size), - int(self.height*0.3 - 2*self.edge_size) - ) display_graphics.setWidget(display) display_graphics.setZValue(-1) return display_graphics From 2e7461a80201d84c8c076d962659244bb3bd7d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 13 Nov 2021 18:13:38 +0100 Subject: [PATCH 03/13] Removed trailing whitespace --- opencodeblocks/graphics/blocks/codeblock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index 6f956ce7..bd311ea2 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -30,7 +30,7 @@ def __init__(self, **kwargs): output_panel_height + source_panel_height + edge_size*2 + title_height == height """ super().__init__(block_type='code', **kwargs) - + self.output_panel_height = 100 self._min_output_panel_height = 20 @@ -139,7 +139,7 @@ def paint(self, painter: QPainter, painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(self._brush_background) painter.drawPath(path_title.simplified()) - + def _is_in_code_output_resize_area(self, pos:QPointF): """ Return True if the given position is in the block resize_area. """ @@ -199,7 +199,7 @@ def mouseMoveEvent(self, event:QGraphicsSceneMouseEvent): if self.resizing: delta = event.pos() - self.resize_start self.width = max(self.width + delta.x(), self._min_width) - + height_delta = max(delta.y(), # List of all the quantities that must remain negative. # Mainly: min_height - height must be negative for all elements From 51c356139ea98ce67f12810077c83e18f7364400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 14 Nov 2021 01:19:30 +0100 Subject: [PATCH 04/13] :wrench: Rewrote part of block.py to remove repetitions related to hovering events. --- opencodeblocks/graphics/blocks/block.py | 40 +++++---- opencodeblocks/graphics/blocks/codeblock.py | 99 ++++++++------------- 2 files changed, 64 insertions(+), 75 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index a704b75d..ec7ab976 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, QPen, QColor, QFont, QPainter, QPainterPath from PyQt5.QtWidgets import QGraphicsItem, QGraphicsSceneMouseEvent, QGraphicsTextItem, \ - QStyleOptionGraphicsItem, QWidget, QApplication + QStyleOptionGraphicsItem, QWidget, QApplication, QGraphicsSceneHoverEvent from opencodeblocks.core.serializable import Serializable from opencodeblocks.graphics.socket import OCBSocket @@ -180,36 +180,46 @@ def hoverMoveEvent(self, event): pos = event.pos() if self._is_in_resize_area(pos): if not self.resizing_hover: - self.resizing_hover = True - QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + self._start_hovering() elif self.resizing_hover: - self.resizing_hover = False - QApplication.restoreOverrideCursor() - + self._stop_hovering() return super().hoverMoveEvent(event) - def hoverLeaveEvent(self, event): + + def _start_hovering(self): + self.resizing_hover = True + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + + def _stop_hovering(self): + self.resizing_hover = False + QApplication.restoreOverrideCursor() + + def _start_resize(self,pos:QPointF): + self.resizing = True + self.resize_start = pos + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + + def _stop_resize(self): + self.resizing = False + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + + def hoverLeaveEvent(self, event:QGraphicsSceneHoverEvent): """ Triggered when the mouse stops hovering over a block """ if self.resizing_hover: - self.resizing_hover = False - QApplication.restoreOverrideCursor() - + self._stop_hovering() return super().hoverLeaveEvent(event) def mousePressEvent(self, event:QGraphicsSceneMouseEvent): """ OCBBlock reaction to a mousePressEvent. """ pos = event.pos() if self._is_in_resize_area(pos) and event.buttons() == Qt.MouseButton.LeftButton: - self.resize_start = pos - self.resizing = True - QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + self._start_resize(pos) super().mousePressEvent(event) def mouseReleaseEvent(self, event:QGraphicsSceneMouseEvent): """ OCBBlock reaction to a mouseReleaseEvent. """ if self.resizing: self.scene().history.checkpoint("Resized block", set_modified=True) - self.resizing = False - QApplication.restoreOverrideCursor() + self._stop_resize() if self.moved: self.moved = False self.scene().history.checkpoint("Moved block", set_modified=True) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index bd311ea2..909f600e 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -20,25 +20,18 @@ class OCBCodeBlock(OCBBlock): Features an area to edit code as well as a panel to display the output. + The following is always true: + output_panel_height + source_panel_height + edge_size*2 + title_height == height + """ def __init__(self, **kwargs): - """ - Note that self.output_panel_height < self.height, - because the output panel is part of the display. - Moreover, the following is always true: - output_panel_height + source_panel_height + edge_size*2 + title_height == height - """ super().__init__(block_type='code', **kwargs) - - self.output_panel_height = 100 + self.output_panel_height = self.height / 3 self._min_output_panel_height = 20 self._min_source_editor_height = 20 - assert self.height - self.output_panel_height \ - - self.title_height - self.edge_size*2 > 0 - self.source_editor = self.init_source_editor() self.display = self.init_display() self.stdout = "" @@ -56,10 +49,15 @@ def init_source_editor(self): source_editor_graphics.setZValue(-1) return source_editor_graphics + @property def _editor_widget_height(self): return self.height - self.title_height - 2*self.edge_size \ - self.output_panel_height + @_editor_widget_height.setter + def _editor_widget_height(self, value: int): + self.output_panel_height = self.height - value - self.title_height - 2*self.edge_size + def update_all(self): """ Update the code block parts. """ if hasattr(self, 'source_editor'): @@ -68,7 +66,7 @@ def update_all(self): int(self.edge_size), int(self.edge_size + self.title_height), int(self._width - 2*self.edge_size), - int(self._editor_widget_height()) + int(self._editor_widget_height) ) display_widget = self.display.widget() display_widget.setGeometry( @@ -83,6 +81,7 @@ def update_all(self): def source(self) -> str: """ Source code. """ return self._source + @source.setter def source(self, value:str): self._source = value @@ -107,6 +106,7 @@ def stdout(self, value:str): def image(self) -> str: """ Code output. """ return self._image + @image.setter def image(self, value:str): self._image = value @@ -140,61 +140,40 @@ def paint(self, painter: QPainter, painter.setBrush(self._brush_background) painter.drawPath(path_title.simplified()) - - def _is_in_code_output_resize_area(self, pos:QPointF): - """ Return True if the given position is in the block resize_area. """ + def _is_in_resize_source_code_area(self, pos:QPointF): + """ + Return True if the given position is in the area + used to resize the source code widget + """ source_editor_start = self.height - self.output_panel_height - self.edge_size return self.width - 2 * self.edge_size < pos.x() and \ source_editor_start - self.edge_size < pos.y() < source_editor_start + self.edge_size - - def hoverMoveEvent(self, event): - """ Triggered when hovering over a block """ - pos = event.pos() - if self._is_in_resize_area(pos) or self._is_in_code_output_resize_area(pos): - if not self.resizing_hover: - self.resizing_hover = True - QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) - elif self.resizing_hover: - self.resizing_hover = False - QApplication.restoreOverrideCursor() - # Don't call super() because this might override the cursor - def hoverLeaveEvent(self, event): - """ Triggered when the mouse stops hovering over a block """ - if self.resizing_hover: - self.resizing_hover = False - QApplication.restoreOverrideCursor() - # Don't call super() because this might override the cursor - - def mousePressEvent(self, event:QGraphicsSceneMouseEvent): - """ OCBBlock reaction to a mousePressEvent. """ - pos = event.pos() - resizing_source_code = self._is_in_code_output_resize_area(pos) - if (self._is_in_resize_area(pos) or resizing_source_code) and \ - event.buttons() == Qt.MouseButton.LeftButton: - self.resize_start = pos - self.resizing = True - self.resizing_source_code = resizing_source_code - QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) - - super().mousePressEvent(event) - - def mouseReleaseEvent(self, event:QGraphicsSceneMouseEvent): - """ OCBBlock reaction to a mouseReleaseEvent. """ - if self.resizing: - self.scene().history.checkpoint("Resized block", set_modified=True) + + + def _is_in_resize_area(self, pos:QPointF): + """ Return True if the given position is in the block resize_area. """ + + # This block features 2 resizing areas with 2 different behaviors + is_in_bottom_left = super()._is_in_resize_area(pos) + return is_in_bottom_left or self._is_in_resize_source_code_area(pos) + + def _start_resize(self,pos:QPointF): + self.resizing = True + self.resize_start = pos + if self._is_in_resize_source_code_area(pos): + self.resizing_source_code = True + QApplication.setOverrideCursor(Qt.CursorShape.SizeFDiagCursor) + + def _stop_resize(self): self.resizing = False self.resizing_source_code = False QApplication.restoreOverrideCursor() - if self.moved: - self.moved = False - self.scene().history.checkpoint("Moved block", set_modified=True) - super().mouseReleaseEvent(event) def mouseMoveEvent(self, event:QGraphicsSceneMouseEvent): """ We override the default resizing behavior as the code part and the display part of the block - block can be resized. + block can be resized independently. """ if self.resizing: delta = event.pos() - self.resize_start @@ -205,7 +184,7 @@ def mouseMoveEvent(self, event:QGraphicsSceneMouseEvent): # Mainly: min_height - height must be negative for all elements self._min_output_panel_height - self.output_panel_height, self._min_height - self.height, - self._min_source_editor_height - self._editor_widget_height() + self._min_source_editor_height - self._editor_widget_height ) self.height += height_delta @@ -215,9 +194,9 @@ def mouseMoveEvent(self, event:QGraphicsSceneMouseEvent): self.resize_start = event.pos() self.title_graphics.setTextWidth(self.width - 2 * self.edge_size) self.update() - else: - super().mouseMoveEvent(event) - self.moved = True + + self.moved = True + super().mouseMoveEvent(event) def init_display(self): """ Initialize the output display widget: QLabel """ From 0c2b5bf1388db289d42cade5e2a98bca3e5cce36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 14 Nov 2021 01:22:37 +0100 Subject: [PATCH 05/13] :wrench: Added type annotation to event --- opencodeblocks/graphics/blocks/block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index ec7ab976..8de00ec3 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -175,7 +175,7 @@ def remove_socket(self, socket:OCBSocket): socket.remove() self.update_sockets() - def hoverMoveEvent(self, event): + def hoverMoveEvent(self, event:QGraphicsSceneHoverEvent): """ Triggered when hovering over a block """ pos = event.pos() if self._is_in_resize_area(pos): From b5d2bc800eb3c56cbc30cd008f1930f64f4ca3d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 14 Nov 2021 01:34:13 +0100 Subject: [PATCH 06/13] :memo: Remove an incorrect comment --- opencodeblocks/graphics/blocks/codeblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index 909f600e..b6bff90f 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -91,7 +91,7 @@ def source(self, value:str): @property def stdout(self) -> str: - """ Code output, without errors """ + """ Code output. Be careful, this also includes stderr """ return self._stdout @stdout.setter def stdout(self, value:str): From 5e5ea8d5c267b030b345984bbddbadae47b0cfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 14 Nov 2021 01:45:40 +0100 Subject: [PATCH 07/13] :sparkles: Add autopep8 as a dependency to make to easier to fix most pylint issues. Did not run to yet to avoid merge conflicts. --- CONTRIBUTING.md | 6 ++++++ requirements-dev.txt | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f52c6233..fbdd09f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,12 @@ Before doing your **pull request**, check using `pylint` and `pytest` that there pylint .\opencodeblocks\ ``` +Some `pylint` issues can be fixed automatically using `autopep8`, with the following command: + +```bash +autopep8 --in-place --recursive --aggressive opencodeblocks +``` + ```bash pytest --cov=opencodeblocks --cov-report=html tests/unit ``` diff --git a/requirements-dev.txt b/requirements-dev.txt index 764c45a9..f18845f0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,4 +4,5 @@ pytest-mock pytest-check pytest-pspec pylint -pylint-pytest \ No newline at end of file +pylint-pytest +autopep8 \ No newline at end of file From c9ed4daf6fe8ab95828068b6f5c810685f2259e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 14 Nov 2021 02:39:33 +0100 Subject: [PATCH 08/13] :beetle: Fix a bug where the cursor would remain in resize mode when the mouse moves quickly from the resizing area to outside of it. Also, tied the start of the resizing to the cursor appearance to help with consistency between cursor appearance and if the area is resizable if changes are made. --- opencodeblocks/graphics/blocks/block.py | 6 +++--- opencodeblocks/graphics/blocks/codeblock.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 8de00ec3..5475a3b9 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -133,8 +133,8 @@ def paint(self, painter: QPainter, def _is_in_resize_area(self, pos:QPointF): """ Return True if the given position is in the block resize_area. """ - return self.width - pos.x() < 2 * self.edge_size \ - and self.height - pos.y() < 2 * self.edge_size + return self.width - self.edge_size*2 < pos.x() \ + and self.height - self.edge_size*2 < pos.y() def get_socket_pos(self, socket:OCBSocket) -> Tuple[float]: """ Get a socket position to place them on the block sides. """ @@ -211,7 +211,7 @@ def hoverLeaveEvent(self, event:QGraphicsSceneHoverEvent): def mousePressEvent(self, event:QGraphicsSceneMouseEvent): """ OCBBlock reaction to a mousePressEvent. """ pos = event.pos() - if self._is_in_resize_area(pos) and event.buttons() == Qt.MouseButton.LeftButton: + if self.resizing_hover and event.buttons() == Qt.MouseButton.LeftButton: self._start_resize(pos) super().mousePressEvent(event) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index b6bff90f..3a6b7baf 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -147,7 +147,7 @@ def _is_in_resize_source_code_area(self, pos:QPointF): """ source_editor_start = self.height - self.output_panel_height - self.edge_size - return self.width - 2 * self.edge_size < pos.x() and \ + return self.width - self.edge_size/2 < pos.x() and \ source_editor_start - self.edge_size < pos.y() < source_editor_start + self.edge_size From c48caa34b2dcbdb463e375b584bcbbadaa702c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sun, 14 Nov 2021 13:51:31 +0100 Subject: [PATCH 09/13] :wrench: Replace %string by f-string --- opencodeblocks/graphics/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencodeblocks/graphics/window.py b/opencodeblocks/graphics/window.py index 98923246..c2ea695e 100644 --- a/opencodeblocks/graphics/window.py +++ b/opencodeblocks/graphics/window.py @@ -180,7 +180,7 @@ def updateWindowMenu(self): for i, window in enumerate(windows): child = window.widget() - text = "%d %s" % (i + 1, child.windowTitle()) + text = f"{i + 1} {child.windowTitle()}" if i < 9: text = '&' + text From d49aeefce7b5279faf28a622b9464808a01b212d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sun, 14 Nov 2021 13:54:03 +0100 Subject: [PATCH 10/13] :sparkles: Fix Redefining built-in type --- opencodeblocks/graphics/kernel.py | 18 +++++++++--------- opencodeblocks/graphics/pyeditor.py | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index 87ad8e14..4fb71a21 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -19,31 +19,31 @@ def message_to_output(self, message: dict) -> Tuple[str, str]: message: dict representing the a message sent by the kernel Return: - single output found in the message in that order of priority: image > text data > text print > error > nothing - type: 'image' or 'text' + single output found in the message in that order of priority: + image > text data > text print > error > nothing """ - type = 'None' + message_type = 'None' if 'data' in message: if 'image/png' in message['data']: - type = 'image' + message_type = 'image' # output an image (from plt.plot or plt.imshow) out = message['data']['image/png'] else: - type = 'text' + message_type = 'text' # output data as str (for example if code="a=10\na") out = message['data']['text/plain'] elif 'name' in message and message['name'] == "stdout": - type = 'text' + message_type = 'text' # output a print (print("Hello World")) out = message['text'] elif 'traceback' in message: - type = 'text' + message_type = 'text' # output an error out = '\n'.join(message['traceback']) else: - type = 'text' + message_type = 'text' out = '' - return out, type + return out, message_type def execute(self, code: str) -> str: """ diff --git a/opencodeblocks/graphics/pyeditor.py b/opencodeblocks/graphics/pyeditor.py index 1e40c510..689a14a1 100644 --- a/opencodeblocks/graphics/pyeditor.py +++ b/opencodeblocks/graphics/pyeditor.py @@ -131,11 +131,11 @@ def focusOutEvent(self, event: QFocusEvent): # Keep the GUI alive QCoreApplication.processEvents() # Save kernel message and display it - output, type, done = kernel.update_output() + output, output_type, done = kernel.update_output() if done is False: - if type == 'text': + if output_type == 'text': self.block.stdout = output - elif type == 'image': + elif output_type == 'image': self.block.image = output return super().focusOutEvent(event) From 5b60941f6398e75f6b50c7f2cca4d2026f309078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sun, 14 Nov 2021 14:13:05 +0100 Subject: [PATCH 11/13] :sparkles: Fix pylint issues --- opencodeblocks/graphics/blocks/codeblock.py | 4 ++-- opencodeblocks/graphics/function_parsing.py | 5 ++--- opencodeblocks/graphics/qss/dark_resources.py | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index 3a6b7baf..ef088285 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -15,7 +15,7 @@ class OCBCodeBlock(OCBBlock): - """ + """ Code Block Features an area to edit code as well as a panel to display the output. @@ -154,7 +154,7 @@ def _is_in_resize_source_code_area(self, pos:QPointF): def _is_in_resize_area(self, pos:QPointF): """ Return True if the given position is in the block resize_area. """ - # This block features 2 resizing areas with 2 different behaviors + # This block features 2 resizing areas with 2 different behaviors is_in_bottom_left = super()._is_in_resize_area(pos) return is_in_bottom_left or self._is_in_resize_source_code_area(pos) diff --git a/opencodeblocks/graphics/function_parsing.py b/opencodeblocks/graphics/function_parsing.py index 33d5564a..1b371ec6 100644 --- a/opencodeblocks/graphics/function_parsing.py +++ b/opencodeblocks/graphics/function_parsing.py @@ -22,7 +22,7 @@ def run_cell(cell: str) -> str: def run_with_variable_output(cell: str) -> None: """ - This is a proof of concept to show that it is possible + This is a proof of concept to show that it is possible to collect a variable output from a kernel execution Here the kernel executes the code and prints the output repeatedly @@ -33,11 +33,10 @@ def run_with_variable_output(cell: str) -> None: """ kernel.client.execute(cell) done = False - while done == False: + while not done: output, done = kernel.update_output() print(output) - def get_function_name(code: str) -> str: """ Parses a string of code and returns the first function name it finds diff --git a/opencodeblocks/graphics/qss/dark_resources.py b/opencodeblocks/graphics/qss/dark_resources.py index 358c0113..3b7d2693 100644 --- a/opencodeblocks/graphics/qss/dark_resources.py +++ b/opencodeblocks/graphics/qss/dark_resources.py @@ -501,9 +501,11 @@ qt_resource_struct = qt_resource_struct_v2 def qInitResources(): - QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, + qt_resource_name, qt_resource_data) def qCleanupResources(): - QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, + qt_resource_name, qt_resource_data) qInitResources() From 73cffafa3e5ec09229a45d2e7bb7df475b95c419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sun, 14 Nov 2021 14:15:56 +0100 Subject: [PATCH 12/13] :wrench: Refactor Kernel Refactor execute and update_output using new method get_message --- opencodeblocks/graphics/kernel.py | 51 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index 4fb71a21..f4f6282e 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -47,52 +47,53 @@ def message_to_output(self, message: dict) -> Tuple[str, str]: def execute(self, code: str) -> str: """ - Executes code in the kernel and returns the output of the last message sent by the kernel in return + Executes code in the kernel and returns the output of the last message sent by the kernel Args: - code: str representing a piece of Python code to execute + code: String representing a piece of Python code to execute Return: - output from the last message sent by the kernel in return + output from the last message sent by the kernel """ _ = self.client.execute(code) - io_msg_content = {} - if 'execution_state' in io_msg_content and io_msg_content['execution_state'] == 'idle': - return "no output" - - while True: + done = False + while not done: # Check for messages, break the loop when the kernel stops sending messages - message = io_msg_content - try: - io_msg_content = self.client.get_iopub_msg(timeout=1000)[ - 'content'] - if 'execution_state' in io_msg_content and io_msg_content['execution_state'] == 'idle': - break - except queue.Empty: - break - + message, done = self.get_message() return self.message_to_output(message)[0] - def update_output(self) -> Tuple[str, str, bool]: + def get_message(self) -> Tuple[str, bool]: """ - Returns the current output of the kernel + Get message in the jupyter kernel + + Args: + code: String representing a piece of Python code to execute Return: - current output of the kernel; done: bool, True if the kernel has no message to send + Tuple of: + - output from the last message sent by the kernel + - boolean repesenting if the kernel as any other message to send. """ - message = None done = False try: - message = self.client.get_iopub_msg(timeout=1000)[ - 'content'] + message = self.client.get_iopub_msg(timeout=1000)['content'] if 'execution_state' in message and message['execution_state'] == 'idle': done = True except queue.Empty: + message = None done = True + return message, done - out, type = self.message_to_output(message) + def update_output(self) -> Tuple[str, str, bool]: + """ + Returns the current output of the kernel - return out, type, done + Return: + current output of the kernel; done: bool, True if the kernel has no message to send + """ + message, done = self.get_message() + out, _ = self.message_to_output(message) + return out, done def __del__(self): """ From a4b9acda1fde245d31dfcab4ba201208fcc48cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sun, 14 Nov 2021 14:34:14 +0100 Subject: [PATCH 13/13] :beetle: Fix Kernel.execute message was overriden on last iteration when execution_state is idle. --- opencodeblocks/graphics/kernel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index f4f6282e..5d16ea74 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -59,7 +59,9 @@ def execute(self, code: str) -> str: done = False while not done: # Check for messages, break the loop when the kernel stops sending messages - message, done = self.get_message() + new_message, done = self.get_message() + if not done: + message = new_message return self.message_to_output(message)[0] def get_message(self) -> Tuple[str, bool]: