Skip to content

Commit

Permalink
AOC cleanups (#296)
Browse files Browse the repository at this point in the history
* Handle some edge cases post-delete of objects

* Add endswith and endswith: string operations

* Add [index] and [delete]

* Make port-specific tooltip backend-independent

* Move clutter_do_later to clutter dir

* Updates to tests for pynose and fix bugs

* Change test runner from nose-1 to pynose (py 3.11 compat)
  • Loading branch information
bgribble committed Jan 12, 2024
1 parent 9dbe207 commit e839587
Show file tree
Hide file tree
Showing 24 changed files with 453 additions and 166 deletions.
66 changes: 62 additions & 4 deletions mfp/builtins/pyfunc.py
Expand Up @@ -122,10 +122,10 @@ async def trigger(self):

for element in self.elements:
if isinstance(self.inlets[0], (list, tuple, str)):
idx = int(element)
if idx < len(self.inlets[0]):
values.append(self.inlets[0][int(element)])
else:
try:
val_at_loc = self.inlets[0][int(element)]
values.append(val_at_loc)
except IndexError:
if callable(self.default_element):
default = self.default_element()
else:
Expand Down Expand Up @@ -206,6 +206,43 @@ async def trigger(self):
self.outlets[0] = target


class DeleteElement(Processor):
doc_tooltip_obj = "Delete element or attribute from object"
doc_tooltip_inlet = ["Object to get from",
"Element to get (default: initarg 0)"]
doc_tooltip_outlet = ["Object after deletion"]

def __init__(self, init_type, init_args, patch, scope, name):
Processor.__init__(self, 2, 1, init_type, init_args, patch, scope, name)
initargs, kwargs = self.parse_args(init_args)

self.elements = None
if len(initargs):
self.elements = initargs

async def trigger(self):
if self.inlets[1] is not Uninit:
self.elements = self.inlets[1]
if not isinstance(self.inlets[1], list):
self.elements = [self.inlets[1]]

if self.elements is None:
return

value = self.inlets[0]

for element in self.elements:
if isinstance(value, (list, tuple, str)):
try:
idx = int(element)
value = value[:idx] + value[idx+1:]
except ValueError:
pass
elif isinstance(value, dict):
if element in value:
del value[element]
self.outlets[0] = value

class GetSlice(Processor):
doc_tooltip_obj = "Get a slice of list elements"
doc_tooltip_inlet = ["Object to get from",
Expand Down Expand Up @@ -471,9 +508,17 @@ def wrapped(args):
return func(*args)
return wrapped


def index(haystack, needle):
try:
return haystack.index(needle)
except ValueError:
return None

def register():
MFPApp().register("get", GetElement)
MFPApp().register("set!", SetElement)
MFPApp().register("delete", DeleteElement)
MFPApp().register("slice", GetSlice)
MFPApp().register("eval", PyEval)
MFPApp().register("apply", ApplyMethod)
Expand Down Expand Up @@ -554,6 +599,11 @@ def register():
lambda instr, splitstr=" ": instr.split(splitstr) if isinstance(instr, str) else instr,
"split", "Split a string into pieces")

mk_binary(
index,
"index", "Return the position of a target in an iterable, or None"
)

mk_binary(
lambda haystack, needle: (needle in haystack),
"in", "Check if an item is in an iterable"
Expand All @@ -576,6 +626,14 @@ def register():
lambda instr, initial: instr.startswith(initial) if isinstance(instr, str) else False,
"startswith:", "Route on whether string starts with another string")

mk_binary(
lambda instr, initial: instr.endswith(initial) if isinstance(instr, str) else False,
"endswith", "Test if string ends with another string")

mk_cmproute(
lambda instr, initial: instr.endswith(initial) if isinstance(instr, str) else False,
"endswith:", "Route on whether string ends with another string")

from datetime import datetime
mk_nullary(datetime.now, "now", "Current time-of-day")
mk_unary(applyargs(datetime), "datetime", "Create a datetime object")
Expand Down
2 changes: 1 addition & 1 deletion mfp/gui/app_window.py
Expand Up @@ -133,7 +133,7 @@ def active_layer(self):

return self.selected_layer

# FIXME Clutter
# FIXME clutter
def ready(self):
if self.window and self.window.get_realized():
return True
Expand Down
51 changes: 26 additions & 25 deletions mfp/gui/base_element.py
Expand Up @@ -10,8 +10,6 @@
from mfp.gui_main import MFPGUI
from mfp import log
from .colordb import ColorDB
from .backend_interfaces import BaseElementBackend
import math


class BaseElement (Store):
Expand Down Expand Up @@ -340,9 +338,14 @@ def port_center(self, port_dir, port_num):
return (pos_x + ppos[0] + 0.5 * self.get_style('porthole_width'),
pos_y + ppos[1] + 0.5 * self.get_style('porthole_height'))

def port_size(self):
return (self.get_style('porthole_width'), self.get_style('porthole_height'))

def port_position(self, port_dir, port_num):
w = self.width
h = self.height

# inlet
if port_dir == BaseElement.PORT_IN:
if self.num_inlets < 2:
spc = 0
Expand All @@ -353,17 +356,16 @@ def port_position(self, port_dir, port_num):
/ (self.num_inlets - 1.0)))
return (self.get_style('porthole_border') + spc * port_num, 0)

elif port_dir == BaseElement.PORT_OUT:
if self.num_outlets < 2:
spc = 0
else:
spc = max(self.get_style('porthole_minspace'),
((w - self.get_style('porthole_width')
- 2.0 * self.get_style('porthole_border'))
/ (self.num_outlets - 1.0)))
return (self.get_style('porthole_border') + spc * port_num,
h - self.get_style('porthole_height'))

# outlet
if self.num_outlets < 2:
spc = 0
else:
spc = max(self.get_style('porthole_minspace'),
((w - self.get_style('porthole_width')
- 2.0 * self.get_style('porthole_border'))
/ (self.num_outlets - 1.0)))
return (self.get_style('porthole_border') + spc * port_num,
h - self.get_style('porthole_height'))

def configure(self, params):
self.num_inlets = params.get("num_inlets", 0)
Expand Down Expand Up @@ -486,18 +488,17 @@ async def show_tip(self, xpos, ypos, details):
if self.obj_id is None:
return False

# FIXME per-port tooltips need backend support
"""
for (pid, pobj) in self.port_elements.items():
x, y = pobj.get_position()
x += orig_x - 1
y += orig_y - 1
w, h = pobj.get_size()
w += 2
h += 2
if (xpos >= x) and (xpos <= x+w) and (ypos >= y) and (ypos <= y+h):
tiptxt = await MFPGUI().mfp.get_tooltip(self.obj_id, pid[0], pid[1], details)
"""
for direction, num_ports in [(self.PORT_IN, self.num_inlets), (self.PORT_OUT, self.num_outlets)]:
for port_num in range(num_ports):
x, y = self.port_position(direction, port_num)
x += orig_x - 1
y += orig_y - 1
w, h = self.port_size()
w += 2
h += 2
if (xpos >= x) and (xpos <= x+w) and (ypos >= y) and (ypos <= y+h):
tiptxt = await MFPGUI().mfp.get_tooltip(self.obj_id, direction, port_num, details)

if tiptxt is None:
tiptxt = await MFPGUI().mfp.get_tooltip(self.obj_id, None, None, details)
self.app_window.hud_banner(tiptxt)
Expand Down
3 changes: 2 additions & 1 deletion mfp/gui/clutter/plot_element.py
Expand Up @@ -11,6 +11,7 @@
from mfp.mfp_app import MFPApp
from mfp import log

from .utils import clutter_do_later
from .base_element import ClutterBaseElementBackend
from ..plot_element import (
PlotElement,
Expand Down Expand Up @@ -113,7 +114,7 @@ def thunk():
if delta_msec > self.min_interval:
thunk()
else:
MFPGUI().clutter_do_later(self.min_interval-delta_msec, thunk)
clutter_do_later(self.min_interval-delta_msec, thunk)
else:
thunk()

Expand Down
14 changes: 14 additions & 0 deletions mfp/gui/clutter/utils.py
@@ -0,0 +1,14 @@
from mfp import log

def _callback_wrapper(self, thunk):
try:
return thunk()
except Exception as e:
log.debug("Exception in GUI operation:", e)
log.debug_traceback()
return False

def clutter_do_later(self, delay, thunk):
from gi.repository import GObject
GObject.timeout_add(int(delay), self._callback_wrapper, thunk)

5 changes: 3 additions & 2 deletions mfp/gui/message_element.py
Expand Up @@ -106,7 +106,8 @@ def port_position(self, port_dir, port_num):

def select(self):
BaseElement.select(self)
self.label.set_color(self.get_color('text-color'))
if self.label:
self.label.set_color(self.get_color('text-color'))
self.redraw()

def unselect(self):
Expand Down Expand Up @@ -151,7 +152,7 @@ def get_factory(cls):
def _make_connections(self):
for to in self.target_obj:
c = ConnectionElement.build(self.app_window, self, 0, to, self.target_port)
self.app_window.wrapper.active_layer().add(c)
self.app_window.active_layer().add(c)
self.app_window.register(c)
self.connections_out.append(c)
to.connections_in.append(c)
Expand Down
3 changes: 2 additions & 1 deletion mfp/gui/modes/global_mode.py
Expand Up @@ -111,13 +111,14 @@ def toggle_console(self):

# FIXME this is clutter-specific
def toggle_tree(self):
from mfp.gui.clutter.utils import clutter_do_later
oldpos = self.window.backend.tree_canvas_pane.get_position()

self.window.backend.tree_canvas_pane.set_position(self.next_tree_position)
self.next_tree_position = oldpos

# KLUDGE!
MFPGUI().clutter_do_later(100, self._refresh)
clutter_do_later(100, self._refresh)

return False

Expand Down
2 changes: 1 addition & 1 deletion mfp/gui_command.py
Expand Up @@ -177,7 +177,7 @@ async def select(self, obj_id):
from .gui_main import MFPGUI
from .gui.patch_display import PatchDisplay
obj = MFPGUI().recall(obj_id)
if isinstance(obj, PatchDisplay):
if isinstance(obj, PatchDisplay) and len(obj.layers) > 0:
MFPGUI().appwin.layer_select(obj.layers[0])
else:
await MFPGUI().appwin.select(obj)
Expand Down
27 changes: 1 addition & 26 deletions mfp/gui_main.py
Expand Up @@ -31,15 +31,6 @@
gi.require_version('Clutter', '1.0')


# decorator version
def clutter_do(func):
def wrapped(*args, **kwargs):
from mfp.gui_main import MFPGUI
MFPGUI().clutter_do(lambda: func(*args, **kwargs))

return wrapped


class MFPGUI (Singleton):
def __init__(self):
super().__init__()
Expand Down Expand Up @@ -72,23 +63,6 @@ def remember(self, obj):
def recall(self, obj_id):
return self.objects.get(obj_id)

def _callback_wrapper(self, thunk):
try:
return thunk()
except Exception as e:
log.debug("Exception in GUI operation:", e)
log.debug_traceback()
return False

# FIXME -- yooooooooo
def clutter_do_later(self, delay, thunk):
from gi.repository import GObject
GObject.timeout_add(int(delay), self._callback_wrapper, thunk)

def clutter_do(self, thunk):
from gi.repository import GObject
GObject.idle_add(self._callback_wrapper, thunk, priority=GObject.PRIORITY_DEFAULT)

def finish(self):
from gi.repository import Gtk
if self.debug:
Expand All @@ -102,6 +76,7 @@ def finish(self):
if self.appwin:
self.appwin.quit()
self.appwin = None
# FIXME clutter
Gtk.main_quit()


Expand Down
2 changes: 1 addition & 1 deletion mfp/processor.py
Expand Up @@ -521,7 +521,7 @@ async def delete(self):
self.midi_cbid = None

if hasattr(self, "dsp_obj") and self.dsp_obj is not None:
if self.patch.context is not None:
if self.patch and self.patch.context is not None:
await self.dsp_obj.delete()
self.dsp_obj = None

Expand Down

0 comments on commit e839587

Please sign in to comment.