diff --git a/.gitignore b/.gitignore index 3f51e187..21565abd 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,6 @@ dmypy.json # Sublime text *.sublime-workspace + +# Pyright config +pyrightconfig.json diff --git a/main.py b/main.py index 4915304b..2df8be1e 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,10 @@ import os import sys +import asyncio + +if os.name == "nt": # If on windows + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) from qtpy.QtWidgets import QApplication from opencodeblocks.graphics.window import OCBWindow diff --git a/opencodeblocks/graphics/blocks/block.py b/opencodeblocks/graphics/blocks/block.py index 5475a3b9..c7e05840 100644 --- a/opencodeblocks/graphics/blocks/block.py +++ b/opencodeblocks/graphics/blocks/block.py @@ -6,12 +6,14 @@ from typing import TYPE_CHECKING, Optional, OrderedDict, Tuple 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, QGraphicsSceneHoverEvent +from PyQt5.QtGui import QBrush, QMouseEvent, QPen, QColor, QFont, QPainter, QPainterPath +from PyQt5.QtWidgets import QGraphicsItem, QGraphicsProxyWidget, \ + QGraphicsSceneMouseEvent, QLabel, QSplitter, QSplitterHandle, \ + QStyleOptionGraphicsItem, QWidget from opencodeblocks.core.serializable import Serializable from opencodeblocks.graphics.socket import OCBSocket +from opencodeblocks.graphics.blocks.blocksizegrip import BlockSizeGrip if TYPE_CHECKING: from opencodeblocks.graphics.scene.scene import OCBScene @@ -21,10 +23,10 @@ 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): + 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: @@ -51,17 +53,9 @@ def __init__(self, block_type:str='base', source:str='', position:tuple=(0, 0), self.sockets_in = [] self.sockets_out = [] - self._min_width = 300 - self._min_height = 100 - - self.width = width - self.height = height - self.edge_size = edge_size - self.title_height = 3 * title_size - self.title_graphics = QGraphicsTextItem(self) - self.setTitleGraphics(title_color, title_font, title_size, title_padding) self.title = title + self.title_left_offset = 0 self._pen_outline = QPen(QColor("#7F000000")) self._pen_outline_selected = QPen(QColor("#FFFFA637")) @@ -74,8 +68,40 @@ def __init__(self, block_type:str='base', source:str='', position:tuple=(0, 0), self.setAcceptHoverEvents(True) - self.resizing = False - self.resizing_hover = False # Is the mouse hovering over the resizing area ? + self.holder = QGraphicsProxyWidget(self) + self.root = QWidget() + self.root.setAttribute(Qt.WA_TranslucentBackground) + self.root.setGeometry( + 0, 0, + int(width), + int(height) + ) + + self.title_widget = QLabel(self.title, self.root) + self.title_widget.setAttribute(Qt.WA_TransparentForMouseEvents) + 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) + + self.size_grip = BlockSizeGrip(self, self.root) + + if type(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) + + self.edge_size = edge_size + self.min_width = 300 + self.min_height = 100 + self.width = width + self.height = height + self.moved = False self.metadata = { 'title_metadata': { @@ -94,49 +120,50 @@ 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"QLabel {{ color : {color} }}") + 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 + option: QStyleOptionGraphicsItem, # pylint:disable=unused-argument + widget: Optional[QWidget] = None): # pylint:disable=unused-argument """ Paint the block. """ - # title - path_title = QPainterPath() - path_title.setFillRule(Qt.FillRule.WindingFill) - path_title.addRoundedRect(0, 0, self.width, self.title_height, - self.edge_size, self.edge_size) - path_title.addRect(0, self.title_height - self.edge_size, - self.edge_size, self.edge_size) - path_title.addRect(self.width - self.edge_size, self.title_height - self.edge_size, - self.edge_size, self.edge_size) - painter.setPen(Qt.PenStyle.NoPen) - painter.setBrush(self._brush_title) - painter.drawPath(path_title.simplified()) # content - path_title = QPainterPath() - path_title.setFillRule(Qt.FillRule.WindingFill) - path_title.addRoundedRect(0, self.title_height, self.width, self.height - self.title_height, - self.edge_size, self.edge_size) - path_title.addRect(0, self.title_height, self.edge_size, self.edge_size) - path_title.addRect(self.width - self.edge_size, self.title_height, - self.edge_size, self.edge_size) + path_content = QPainterPath() + path_content.setFillRule(Qt.FillRule.WindingFill) + 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_title.simplified()) + painter.drawPath(path_content.simplified()) # outline path_outline = QPainterPath() 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.edge_size, self.edge_size) + painter.setPen( + 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): + 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*2 < pos.x() \ - and self.height - self.edge_size*2 < pos.y() + 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]: + 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': x = 0 @@ -150,7 +177,8 @@ 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): @@ -158,7 +186,7 @@ def update_sockets(self): for socket in self.sockets_in + self.sockets_out: socket.setPos(*self.get_socket_pos(socket)) - def add_socket(self, socket:OCBSocket): + def add_socket(self, socket: OCBSocket): """ Add a socket to the block. """ if socket.socket_type == 'input': self.sockets_in.append(socket) @@ -166,7 +194,7 @@ def add_socket(self, socket:OCBSocket): self.sockets_out.append(socket) self.update_sockets() - def remove_socket(self, socket:OCBSocket): + def remove_socket(self, socket: OCBSocket): """ Remove a socket from the block. """ if socket.socket_type == 'input': self.sockets_in.remove(socket) @@ -175,83 +203,17 @@ 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.resizing_hover and event.buttons() == Qt.MouseButton.LeftButton: - self._start_resize(pos) - super().mousePressEvent(event) - - def mouseReleaseEvent(self, event:QGraphicsSceneMouseEvent): + def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): """ OCBBlock reaction to a mouseReleaseEvent. """ - if self.resizing: - self.scene().history.checkpoint("Resized block", set_modified=True) - self._stop_resize() if self.moved: self.moved = False self.scene().history.checkpoint("Moved block", set_modified=True) super().mouseReleaseEvent(event) - def mouseMoveEvent(self, event:QGraphicsSceneMouseEvent): + def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): """ OCBBlock reaction to a mouseMoveEvent. """ - if self.resizing: - delta = event.pos() - self.resize_start - self.width = max(self.width + delta.x(), self._min_width) - self.height = max(self.height + delta.y(), self._min_height) - 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 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_graphics.setDefaultTextColor(QColor(color)) - self.title_graphics.setFont(QFont(font, size)) - self.title_graphics.setPos(padding, 0) - self.title_graphics.setTextWidth(self.width - 2 * self.edge_size) + super().mouseMoveEvent(event) + self.moved = True def remove(self): """ Remove the block from the scene containing it. """ @@ -264,35 +226,64 @@ def remove(self): def update_all(self): """ Update sockets and title. """ self.update_sockets() - if hasattr(self, 'title_graphics'): - self.title_graphics.setTextWidth(self.width - 2 * self.edge_size) + 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 - 2 * self.edge_size), + 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) + ) @property def title(self): """ Block title. """ return self._title + @title.setter - def title(self, value:str): + def title(self, value: str): self._title = value - if hasattr(self, 'title_graphics'): - self.title_graphics.setPlainText(self._title) + if hasattr(self, 'title_widget'): + self.title_widget.setText(self._title) @property def width(self): """ Block width. """ - return self._width + return self.root.width() + @width.setter - def width(self, value:float): - self._width = value + def width(self, value: float): + self.root.setGeometry(0, 0, int(value), self.root.height()) self.update_all() @property def height(self): """ Block height. """ - return self._height + return self.root.height() + @height.setter - def height(self, value:float): - self._height = value + def height(self, value: float): + self.root.setGeometry(0, 0, self.root.width(), int(value)) self.update_all() def serialize(self) -> OrderedDict: @@ -302,24 +293,55 @@ def serialize(self) -> OrderedDict: ('title', self.title), ('block_type', self.block_type), ('source', self.source), + ('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]), + ('sockets', [socket.serialize() + for socket in self.sockets_in + self.sockets_out]), ]) - def deserialize(self, data: dict, hashmap:dict=None, restore_id=True) -> None: + 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', 'width', 'height'): setattr(self, dataname, data[dataname]) + 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']) + for socket_data in data['sockets']: socket = OCBSocket(block=self) socket.deserialize(socket_data, hashmap, restore_id) self.add_socket(socket) hashmap.update({socket_data['id']: socket}) + + +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/blocksizegrip.py b/opencodeblocks/graphics/blocks/blocksizegrip.py new file mode 100644 index 00000000..c86c5c32 --- /dev/null +++ b/opencodeblocks/graphics/blocks/blocksizegrip.py @@ -0,0 +1,74 @@ + +""" +Implements the SizeGrip Widget for the Blocks. + +The size grip is the little icon at the bottom right of a block that is used to +resize a block. +""" + +from PyQt5.QtCore import QPoint +from PyQt5.QtWidgets import QGraphicsItem, QSizeGrip, QWidget +from PyQt5.QtGui import QMouseEvent + + +class BlockSizeGrip(QSizeGrip): + """ A grip to resize a block """ + + def __init__(self, block: QGraphicsItem, parent: QWidget = None): + """ + Constructor for BlockSizeGrip + + block is the QGraphicsItem holding the QSizeGrip. + It's usually an OCBBlock + """ + super().__init__(parent) + self.mouseX = 0 + self.mouseY = 0 + self.block = block + self.resizing = False + + def mousePressEvent(self, mouseEvent: QMouseEvent): + """ Start the resizing """ + self.mouseX = mouseEvent.globalX() + self.mouseY = mouseEvent.globalY() + self.resizing = True + + def mouseReleaseEvent(self, mouseEvent: QMouseEvent): + """ Stop the resizing """ + self.resizing = False + self.block.scene().history.checkpoint("Resized block", set_modified=True) + + @property + def _zoom(self) -> float: + """ Returns how much the scene is """ + return self.block.scene().views()[0].zoom + + def mouseMoveEvent(self, mouseEvent: QMouseEvent): + """ Performs resizing of the root widget """ + transformed_pt1 = self.block.mapFromScene(QPoint(0, 0)) + transformed_pt2 = self.block.mapFromScene(QPoint(1, 1)) + + pt = transformed_pt2 - transformed_pt1 + pt /= self._zoom + + delta_x = (mouseEvent.globalX() - self.mouseX) * pt.x() + delta_y = (mouseEvent.globalY() - self.mouseY) * pt.y() + # Here, we use globalx and globaly instead of x() and y(). + # This is because when using x() and y(), the mouse position is taken + # relative to the grip, so if the grip moves, the deltaX and deltaY changes. + # This creates a shaking effect when resizing. We use global to not + # have this effect. + new_width = max( + self.block.width + int(delta_x), + self.block.min_width + ) + new_height = max( + self.block.height + int(delta_y), + self.block.min_height + ) + + self.parent().setGeometry(0, 0, new_width, new_height) + self.block.update_all() + + self.mouseX = mouseEvent.globalX() + self.mouseY = mouseEvent.globalY() diff --git a/opencodeblocks/graphics/blocks/codeblock.py b/opencodeblocks/graphics/blocks/codeblock.py index 9c86bed4..7879cb4d 100644 --- a/opencodeblocks/graphics/blocks/codeblock.py +++ b/opencodeblocks/graphics/blocks/codeblock.py @@ -3,13 +3,13 @@ """ Module for the base OCB Code Block. """ + import re -from typing import Optional -from PyQt5.QtCore import QCoreApplication, Qt, QByteArray, QPointF -from PyQt5.QtGui import QPainter, QPainterPath, QPixmap -from PyQt5.QtWidgets import QStyleOptionGraphicsItem, QWidget, QGraphicsProxyWidget, QLabel, \ - QGraphicsSceneMouseEvent, QApplication, QPushButton +from PyQt5.QtCore import QCoreApplication, QByteArray +from PyQt5.QtGui import QPixmap +from PyQt5.QtWidgets import QLabel, QPushButton + from opencodeblocks.graphics.blocks.block import OCBBlock from opencodeblocks.graphics.pyeditor import PythonEditor @@ -37,58 +37,32 @@ def __init__(self, **kwargs): self._min_source_editor_height = 20 self.source_editor = self.init_source_editor() - self.display = self.init_display() + self.output_panel = self.init_output_panel() self.run_button = self.init_run_button() self.stdout = "" self.image = "" + self.title_left_offset = 3 * self.edge_size - self.resizing_source_code = False + self.holder.setWidget(self.root) 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_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 + self.splitter.addWidget(source_editor) + return source_editor def update_all(self): """ Update the code block parts. """ - if hasattr(self, 'source_editor'): - editor_widget = self.source_editor.widget() - editor_widget.setGeometry( - int(self.edge_size), - int(self.edge_size + self.title_height), - int(self._width - 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.output_panel_height - self.edge_size), - int(self.width - 2*self.edge_size), - int(self.output_panel_height) - ) - run_button_widget = self.run_button.widget() - run_button_widget.setGeometry( + super().update_all() + if hasattr(self, 'run_button'): + self.run_button.setGeometry( int(self.edge_size), - int(self.edge_size + self.title_height), - int(2.5*self.edge_size), - int(2.5*self.edge_size) + int(self.edge_size / 2), + int(2.5 * self.edge_size), + int(2.5 * self.edge_size) ) - super().update_all() @property def source(self) -> str: @@ -99,8 +73,7 @@ def source(self) -> str: def source(self, value: str): self._source = value if hasattr(self, 'source_editor'): - editor_widget = self.source_editor.widget() - editor_widget.setText(self._source) + self.source_editor.setText(self._source) @property def stdout(self) -> str: @@ -111,14 +84,15 @@ def stdout(self) -> str: def stdout(self, value: str): self._stdout = value if hasattr(self, 'source_editor'): - # If there is a text output, erase the image output and display the text output + # If there is a text output, erase the image output and display the + # text output self.image = "" - editor_widget = self.display.widget() + # Remove ANSI color codes text = ansi_escape.sub('', value) # Remove backspaces (tf loading bars) text = text.replace('\x08', '') - editor_widget.setText(text) + self.output_panel.setText(text) @property def image(self) -> str: @@ -129,115 +103,42 @@ def image(self) -> str: def image(self, value: str): self._image = value if hasattr(self, 'source_editor') and self.image != "": - # If there is an image output, erase the text output and display the image output - editor_widget = self.display.widget() - editor_widget.setText("") - qlabel = editor_widget + # If there is an image output, erase the text output and display + # the image output + self.output_panel.setText("") ba = QByteArray.fromBase64(str.encode(self.image)) pixmap = QPixmap() pixmap.loadFromData(ba) - qlabel.setPixmap(pixmap) + self.output_panel.setPixmap(pixmap) @source.setter def source(self, value: str): self._source = value if hasattr(self, 'source_editor'): - editor_widget = self.source_editor.widget() + editor_widget = self.source_editor editor_widget.setText(self._source) - def paint(self, painter: QPainter, - option: QStyleOptionGraphicsItem, # pylint:disable=unused-argument - widget: Optional[QWidget] = None): # pylint:disable=unused-argument - """ Paint the code output panel """ - super().paint(painter, option, widget) - path_title = QPainterPath() - path_title.setFillRule(Qt.FillRule.WindingFill) - 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): + def init_output_panel(self): """ Initialize the output display widget: QLabel """ - display_graphics = QGraphicsProxyWidget(self) - display = QLabel() - display.setText("") - display_graphics.setWidget(display) - display_graphics.setZValue(-1) - return display_graphics + output_panel = QLabel() + output_panel.setText("") + + self.splitter.addWidget(output_panel) + return output_panel def init_run_button(self): """ Initialize the run button """ - run_button_graphics = QGraphicsProxyWidget(self) - run_button = QPushButton(">") - run_button.setMinimumWidth(self.edge_size) - run_button_graphics.setWidget(run_button) + run_button = QPushButton(">", self.root) + run_button.setMinimumWidth(int(self.edge_size)) run_button.clicked.connect(self.run_code) - return run_button_graphics + run_button.raise_() + + return run_button def run_code(self): """Run the code in the block""" - code = self.source_editor.widget().text() - kernel = self.source_editor.widget().kernel + code = self.source_editor.text() + kernel = self.source_editor.kernel self.source = code # Execute the code kernel.client.execute(code) diff --git a/opencodeblocks/graphics/pyeditor.py b/opencodeblocks/graphics/pyeditor.py index c146561f..70e571c6 100644 --- a/opencodeblocks/graphics/pyeditor.py +++ b/opencodeblocks/graphics/pyeditor.py @@ -87,7 +87,7 @@ def update_theme(self): def views(self) -> List['OCBView']: """ Get the views in which the python_editor is present. """ - return self.graphicsProxyWidget().scene().views() + return self.block.scene().views() def set_views_mode(self, mode: str): """ Set the views in which the python_editor is present to editing mode. """ diff --git a/opencodeblocks/graphics/scene/history.py b/opencodeblocks/graphics/scene/history.py index 3f93ef7e..a6baabe0 100644 --- a/opencodeblocks/graphics/scene/history.py +++ b/opencodeblocks/graphics/scene/history.py @@ -18,7 +18,7 @@ class SceneHistory(): """ - def __init__(self, scene:'OCBScene', max_stack:int=20): + def __init__(self, scene:'OCBScene', max_stack:int = 50): self.scene = scene self.history_stack = [] self.current = -1 diff --git a/tests/integration/test_blocks.py b/tests/integration/test_blocks.py index 0cff9521..449f7763 100644 --- a/tests/integration/test_blocks.py +++ b/tests/integration/test_blocks.py @@ -54,7 +54,9 @@ def test_move_blocks(self, qtbot): 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/2) + pos_block.setX( + pos_block.x() + self.block1.title_height + self.block1.edge_size + ) pos_block.setY(pos_block.y() + self.block1.title_height/2) pos_block = self.ocb_widget.view.mapFromScene(pos_block)