Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
51 changes: 42 additions & 9 deletions opencodeblocks/graphics/blocks/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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': {
Expand Down Expand Up @@ -130,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. """
Expand Down Expand Up @@ -172,21 +175,51 @@ def remove_socket(self, socket:OCBSocket):
socket.remove()
self.update_sockets()

def hoverMoveEvent(self, event:QGraphicsSceneHoverEvent):
""" Triggered when hovering over a block """
pos = event.pos()
if self._is_in_resize_area(pos):
if not self.resizing_hover:
self._start_hovering()
elif self.resizing_hover:
self._stop_hovering()
return super().hoverMoveEvent(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._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)
if self.resizing_hover and event.buttons() == Qt.MouseButton.LeftButton:
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)
Expand Down
117 changes: 96 additions & 21 deletions opencodeblocks/graphics/blocks/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,59 @@

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.

The following is always true:
output_panel_height + source_panel_height + edge_size*2 + title_height == height

"""

def __init__(self, **kwargs):
super().__init__(block_type='code', **kwargs)

self.output_panel_height = self.height / 3
self._min_output_panel_height = 20
self._min_source_editor_height = 20

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

@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'):
Expand All @@ -45,21 +66,22 @@ 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()

@property
def source(self) -> str:
""" Source code. """
return self._source

@source.setter
def source(self, value:str):
self._source = value
Expand All @@ -69,7 +91,7 @@ def source(self, value:str):

@property
def stdout(self) -> str:
""" Code output. """
""" Code output. Be careful, this also includes stderr """
return self._stdout
@stdout.setter
def stdout(self, value:str):
Expand All @@ -84,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
Expand Down Expand Up @@ -111,23 +134,75 @@ 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_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 - self.edge_size/2 < pos.x() and \
source_editor_start - self.edge_size < pos.y() < source_editor_start + self.edge_size


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()

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 independently.
"""
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()

self.moved = True
super().mouseMoveEvent(event)

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
5 changes: 2 additions & 3 deletions opencodeblocks/graphics/function_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading