Skip to content

Commit

Permalink
🔨 Add the concept of an executable block as not all block can be exec…
Browse files Browse the repository at this point in the history
…uted. This will be used to make sliders "executable".
  • Loading branch information
vanyle committed Dec 11, 2021
1 parent 951d557 commit e3ae908
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 126 deletions.
8 changes: 7 additions & 1 deletion opencodeblocks/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@

""" Module for the OCB Blocks of different types. """

from opencodeblocks.blocks.sliderblock import OCBSliderBlock
# Abstract blocks
from opencodeblocks.blocks.block import OCBBlock
from opencodeblocks.blocks.executableblock import OCBExecutableBlock

# Real blocks
from opencodeblocks.blocks.sliderblock import OCBSliderBlock
from opencodeblocks.blocks.codeblock import OCBCodeBlock
from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock
from opencodeblocks.blocks.drawingblock import OCBDrawingBlock


4 changes: 0 additions & 4 deletions opencodeblocks/blocks/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class OCBBlock(QGraphicsItem, Serializable):
def __init__(
self,
block_type: str = "base",
source: str = "",
position: tuple = (0, 0),
width: int = 300,
height: int = 200,
Expand All @@ -45,7 +44,6 @@ def __init__(
Args:
block_type: Block type.
source: Block source text.
position: Block position in the scene.
width: Block width.
height: Block height.
Expand All @@ -58,8 +56,6 @@ def __init__(
Serializable.__init__(self)

self.block_type = block_type
self.source = source
self.stdout = ""
self.setPos(QPointF(*position))
self.sockets_in = []
self.sockets_out = []
Expand Down
113 changes: 7 additions & 106 deletions opencodeblocks/blocks/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
from ansi2html import Ansi2HTMLConverter
from networkx.algorithms.traversal.breadth_first_search import bfs_edges

from opencodeblocks.blocks.block import OCBBlock
from opencodeblocks.blocks.executableblock import OCBExecutableBlock
from opencodeblocks.graphics.socket import OCBSocket
from opencodeblocks.graphics.pyeditor import PythonEditor

conv = Ansi2HTMLConverter()


class OCBCodeBlock(OCBBlock):
class OCBCodeBlock(OCBExecutableBlock):

"""
Code Block
Expand All @@ -33,27 +33,17 @@ def __init__(self, **kwargs):
Create a new OCBCodeBlock.
Initialize all the child widgets specific to this block type
"""
super().__init__(**kwargs)
self.source_editor = PythonEditor(self)
self._source = ""

super().__init__(**kwargs)
self._stdout = ""

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

self.output_closed = True
self._splitter_size = [1, 1]
self._cached_stdout = ""
self.has_been_run = False

# Add exectution flow sockets
exe_sockets = (
OCBSocket(self, socket_type="input", flow_type="exe"),
OCBSocket(self, socket_type="output", flow_type="exe"),
)
for socket in exe_sockets:
self.add_socket(socket)

# Add output pannel
self.output_panel = self.init_output_panel()
Expand Down Expand Up @@ -106,103 +96,14 @@ def run_code(self):
self.run_button.setText("...")
self.run_all_button.setText("...")

# Run code by adding to code to queue
code = self.source_editor.text()
self.source = code
kernel = self.source_editor.kernel
kernel.execution_queue.append((self, code))
if kernel.busy is False:
kernel.run_queue()
self.has_been_run = True
super().run_code() # actually run the code

def reset_buttons(self):
def execution_finished(self):
"""Reset the text of the run buttons"""
super().execution_finished()
self.run_button.setText(">")
self.run_all_button.setText(">>")

def has_input(self) -> bool:
"""Checks whether a block has connected input blocks"""
for input_socket in self.sockets_in:
if len(input_socket.edges) != 0:
return True
return False

def has_output(self) -> bool:
"""Checks whether a block has connected output blocks"""
for output_socket in self.sockets_out:
if len(output_socket.edges) != 0:
return True
return False

def _interrupt_execution(self):
""" Interrupt an execution, reset the blocks in the queue """
for block, _ in self.source_editor.kernel.execution_queue:
# Reset the blocks that have not been run
block.reset_buttons()
block.has_been_run = False
# Clear the queue
self.source_editor.kernel.execution_queue = []
# Interrupt the kernel
self.source_editor.kernel.kernel_manager.interrupt_kernel()

def run_left(self, in_right_button=False):
"""
Run all of the block's dependencies and then run the block
"""
# If the user presses left run when running, cancel the execution
if self.run_button.text() == "..." and not in_right_button:
self._interrupt_execution()
return

# If no dependencies
if not self.has_input():
return self.run_code()

# Create the graph from the scene
graph = self.scene().create_graph()
# BFS through the input graph
edges = bfs_edges(graph, self, reverse=True)
# Run the blocks found except self
blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges]
for block in blocks_to_run[::-1]:
if not block.has_been_run:
block.run_code()

if in_right_button:
# If run_left was called inside of run_right
# self is not necessarily the block that was clicked
# which means that self does not need to be run
if not self.has_been_run:
self.run_code()
else:
# On the contrary if run_left was called outside of run_right
# self is the block that was clicked
# so self needs to be run
self.run_code()

def run_right(self):
"""Run all of the output blocks and all their dependencies"""
# If the user presses right run when running, cancel the execution
if self.run_all_button.text() == "...":
self._interrupt_execution()
return

# If no output, run left
if not self.has_output():
return self.run_left(in_right_button=True)

# Same as run_left but instead of running the blocks, we'll use run_left
graph = self.scene().create_graph()
edges = bfs_edges(graph, self)
blocks_to_run: List["OCBCodeBlock"] = [
self] + [v for _, v in edges]
for block in blocks_to_run[::-1]:
block.run_left(in_right_button=True)

def reset_has_been_run(self):
""" Reset has_been_run, is called when the output is an error """
self.has_been_run = False

def update_title(self):
"""Change the geometry of the title widget"""
self.title_widget.setGeometry(
Expand Down
86 changes: 86 additions & 0 deletions opencodeblocks/blocks/containerblock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# OpenCodeBlock an open-source tool for modular visual programing in python

"""
Exports OCBSliderBlock.
"""

from typing import OrderedDict
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout
from opencodeblocks.blocks.block import OCBBlock


class OCBSliderBlock(OCBBlock):
"""
Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to.
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)

self.layout = QVBoxLayout(self.root)

self.slider = QSlider(Qt.Horizontal)
self.slider.valueChanged.connect(self.valueChanged)

self.variable_layout = QHBoxLayout(self.root)
self.variable_text = QLineEdit("slider_value")
self.variable_value = QLabel(f"{self.slider.value()/100}")

self.variable_text.setFixedWidth(self.root.width() / 2)

self.variable_layout.addWidget(self.variable_text)
self.variable_layout.addWidget(self.variable_value)

self.layout.setContentsMargins(
self.edge_size * 2,
self.title_widget.height() + self.edge_size * 2,
self.edge_size * 2,
self.edge_size * 2
)
self.layout.addWidget(self.slider)
self.layout.addLayout(self.variable_layout)

self.holder.setWidget(self.root)

def valueChanged(self):
""" This is called when the value of the slider changes """
python_code = f"{self.var_name} = {self.value}"
self.variable_value.setText(f"{self.value}")

# The code execution part will be added when the execution flow is merged.
# We print for now
print(python_code)

@property
def value(self):
""" The value of the slider """
return str(self.slider.value() / 100)
@value.setter
def value(self, value: str):
self.slider.setValue(int(float(value) * 100))

@property
def var_name(self):
""" The name of the python variable associated with the slider """
return self.variable_text.text()
@var_name.setter
def var_name(self, value: str):
self.variable_text.setText(value)

def serialize(self):
""" Return a serialized version of this widget """
base_dict = super().serialize()
base_dict["value"] = self.value
base_dict["var_name"] = self.var_name

return base_dict

def deserialize(self, data: OrderedDict,
hashmap: dict = None, restore_id: bool = True):
""" Restore a slider block from it's serialized state """
for dataname in ['value','var_name']:
if dataname in data:
setattr(self, dataname, data[dataname])

super().deserialize(data, hashmap, restore_id)
21 changes: 16 additions & 5 deletions opencodeblocks/blocks/sliderblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout
from opencodeblocks.blocks.block import OCBBlock

from opencodeblocks.graphics.kernel import get_main_kernel

class OCBSliderBlock(OCBBlock):
"""
Expand Down Expand Up @@ -47,10 +47,21 @@ def valueChanged(self):
""" This is called when the value of the slider changes """
python_code = f"{self.var_name} = {self.value}"
self.variable_value.setText(f"{self.value}")

# The code execution part will be added when the execution flow is merged.
# We print for now
print(python_code)

kernel = get_main_kernel()
kernel.execution_queue.append((self, python_code))
if kernel.busy is False:
kernel.run_queue()


def reset_has_been_run(self):
pass
def reset_buttons(self):
pass
def handle_stdout(self):
pass
def handle_image(self):
pass

@property
def value(self):
Expand Down
15 changes: 13 additions & 2 deletions opencodeblocks/graphics/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import queue
from typing import Tuple
from PyQt5.QtCore import QThreadPool
from jupyter_client.manager import start_new_kernel

from opencodeblocks.graphics.worker import Worker
Expand Down Expand Up @@ -68,9 +69,9 @@ def run_block(self, block, code: str):
worker.signals.stdout.connect(block.handle_stdout)
worker.signals.image.connect(block.handle_image)
worker.signals.finished.connect(self.run_queue)
worker.signals.finished_block.connect(block.reset_buttons)
worker.signals.finished.connect(block.execution_finished)
worker.signals.error.connect(block.reset_has_been_run)
block.source_editor.threadpool.start(worker)
get_main_threadpool().start(worker)

def run_queue(self):
""" Runs the next code in the queue """
Expand Down Expand Up @@ -138,3 +139,13 @@ def __del__(self):
Shuts down the kernel
"""
self.kernel_manager.shutdown_kernel()


kernel = Kernel()
threadpool = QThreadPool()

def get_main_kernel():
return kernel

def get_main_threadpool():
return threadpool
11 changes: 4 additions & 7 deletions opencodeblocks/graphics/pyeditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
""" Module for OCB in block python editor. """

from typing import TYPE_CHECKING, List
from PyQt5.QtCore import QThreadPool, Qt
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFocusEvent, QFont, QFontMetrics, QColor, QMouseEvent, QWheelEvent
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
from opencodeblocks.graphics.theme_manager import theme_manager

from opencodeblocks.blocks.block import OCBBlock
from opencodeblocks.graphics.kernel import Kernel

kernel = Kernel()
threadpool = QThreadPool()
from opencodeblocks.graphics.kernel import get_main_kernel, get_main_threadpool

if TYPE_CHECKING:
from opencodeblocks.graphics.view import OCBView
Expand All @@ -33,8 +30,8 @@ def __init__(self, block: OCBBlock):
super().__init__(None)
self._mode = "NOOP"
self.block = block
self.kernel = kernel
self.threadpool = threadpool
self.kernel = get_main_kernel()
self.threadpool = get_main_threadpool()

self.update_theme()
theme_manager().themeChanged.connect(self.update_theme)
Expand Down
1 change: 0 additions & 1 deletion opencodeblocks/graphics/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ async def run_code(self):
elif output_type == 'error':
self.signals.error.emit()
self.signals.finished.emit()
self.signals.finished_block.emit()

def run(self):
""" Execute the run_code method asynchronously. """
Expand Down

0 comments on commit e3ae908

Please sign in to comment.