Got rid of object addresses, commands reference objects by ID.

The address of list elements change, if the list is modified, so the client side addresses of the object proxies can become invalid.
looper
Ben Niemann 2016-06-25 16:29:29 +02:00
parent fbaee0c241
commit 9e06d1466d
22 changed files with 215 additions and 243 deletions

View File

@ -1,11 +1,11 @@
from .state import (
StateBase,
StateBase, RootObject,
Property, ListProperty, DictProperty,
ObjectPropertyBase,
ObjectProperty, ObjectListProperty, ObjectReferenceProperty,
)
from .commands import (
CommandDispatcher, CommandTarget, Command,
Command,
)
from .process_manager import (
ProcessManager, ProcessImpl

View File

@ -30,40 +30,3 @@ class Command(StateBase):
if getattr(self, prop_name) != getattr(other, prop_name):
return False
return True
class CommandTarget(object):
def __init__(self):
self.__sub_targets = {}
self.__is_root = False
def set_root(self):
self.__is_root = True
def add_sub_target(self, name, obj):
self.__sub_targets[name] = obj
def get_sub_target(self, name):
try:
return self.__sub_targets[name]
except KeyError:
raise CommandError("Target '%s' not found" % name)
def get_object(self, address):
if address.startswith('/'):
assert self.__is_root
address = address[1:]
if address == '':
return self
parts = address.split('/', 1)
assert len(parts) >= 1
obj = self.get_sub_target(parts[0])
return obj.get_object(parts[1] if len(parts) > 1 else '')
class CommandDispatcher(CommandTarget):
def dispatch_command(self, target, cmd):
obj = self.get_object(target)
return cmd.run(obj)

View File

@ -4,51 +4,51 @@ import unittest
from . import commands
class TestCommand(commands.Command):
def __init__(self, name):
super().__init__()
self.name = name
# class TestCommand(commands.Command):
# def __init__(self, name):
# super().__init__()
# self.name = name
def run(self, obj):
obj.commands.append(self.name)
# def run(self, obj):
# obj.commands.append(self.name)
class TestObject(commands.CommandDispatcher):
def __init__(self):
super().__init__()
self.commands = []
# class TestObject(commands.CommandDispatcher):
# def __init__(self):
# super().__init__()
# self.commands = []
class CommandDispatcherTest(unittest.TestCase):
def setUp(self):
self.leaf_a = TestObject()
self.leaf_b = TestObject()
self.leaf_c = TestObject()
# class CommandDispatcherTest(unittest.TestCase):
# def setUp(self):
# self.leaf_a = TestObject()
# self.leaf_b = TestObject()
# self.leaf_c = TestObject()
self.inner_a = TestObject()
self.inner_a.add_sub_target('leaf_b', self.leaf_b)
self.inner_a.add_sub_target('leaf_c', self.leaf_c)
# self.inner_a = TestObject()
# self.inner_a.add_sub_target('leaf_b', self.leaf_b)
# self.inner_a.add_sub_target('leaf_c', self.leaf_c)
self.root = TestObject()
self.root.set_root()
self.root.add_sub_target('leaf_a', self.leaf_a)
self.root.add_sub_target('inner_a', self.inner_a)
# self.root = TestObject()
# self.root.set_root()
# self.root.add_sub_target('leaf_a', self.leaf_a)
# self.root.add_sub_target('inner_a', self.inner_a)
def testTargetRoot(self):
self.root.dispatch_command('/', TestCommand('cmd1'))
self.assertEqual(self.root.commands, ['cmd1'])
self.assertEqual(self.leaf_a.commands, [])
# def testTargetRoot(self):
# self.root.dispatch_command('/', TestCommand('cmd1'))
# self.assertEqual(self.root.commands, ['cmd1'])
# self.assertEqual(self.leaf_a.commands, [])
def testTargetLeafA(self):
self.root.dispatch_command('/leaf_a', TestCommand('cmd1'))
self.assertEqual(self.leaf_a.commands, ['cmd1'])
# def testTargetLeafA(self):
# self.root.dispatch_command('/leaf_a', TestCommand('cmd1'))
# self.assertEqual(self.leaf_a.commands, ['cmd1'])
def testTargetLeafB(self):
self.root.dispatch_command('/inner_a/leaf_b', TestCommand('cmd1'))
self.assertEqual(self.leaf_b.commands, ['cmd1'])
# def testTargetLeafB(self):
# self.root.dispatch_command('/inner_a/leaf_b', TestCommand('cmd1'))
# self.assertEqual(self.leaf_b.commands, ['cmd1'])
def testTargetInnerA(self):
self.root.dispatch_command('/inner_a', TestCommand('cmd1'))
self.assertEqual(self.inner_a.commands, ['cmd1'])
# def testTargetInnerA(self):
# self.root.dispatch_command('/inner_a', TestCommand('cmd1'))
# self.assertEqual(self.inner_a.commands, ['cmd1'])
if __name__ == '__main__':

View File

@ -4,7 +4,6 @@ import logging
import uuid
from .callbacks import CallbackRegistry
from .tree import TreeNode
logger = logging.getLogger(__name__)
@ -36,7 +35,8 @@ class PropertyBase(object):
old_value = instance.state.get(self.name, None)
instance.state[self.name] = value
if value != old_value:
instance.root.handle_mutation(('update_property', instance, self.name, old_value, value))
if instance.attached_to_root:
instance.root.handle_mutation(('update_property', instance, self.name, old_value, value))
instance.listeners.call(self.name, old_value, value)
def __delete__(self, instance):
@ -107,7 +107,8 @@ class SimpleObjectList(object):
def __delitem__(self, idx):
del self._objs[idx]
self._instance.root.handle_mutation(('update_list', self._instance, self._prop.name, 'delete', idx))
if self._instance.attached_to_root:
self._instance.root.handle_mutation(('update_list', self._instance, self._prop.name, 'delete', idx))
self._instance.listeners.call(self._prop.name, 'delete', idx)
def append(self, obj):
@ -116,12 +117,14 @@ class SimpleObjectList(object):
def insert(self, idx, obj):
self._check_type(obj)
self._objs.insert(idx, obj)
self._instance.root.handle_mutation(('update_list', self._instance, self._prop.name, 'insert', idx, obj))
if self._instance.attached_to_root:
self._instance.root.handle_mutation(('update_list', self._instance, self._prop.name, 'insert', idx, obj))
self._instance.listeners.call(self._prop.name, 'insert', idx, obj)
def clear(self):
self._objs.clear()
self._instance.root.handle_mutation(('update_list', self._instance, self._prop.name, 'clear'))
if self._instance.attached_to_root:
self._instance.root.handle_mutation(('update_list', self._instance, self._prop.name, 'clear'))
self._instance.listeners.call(self._prop.name, 'clear')
def extend(self, value):
@ -200,14 +203,21 @@ class ObjectProperty(ObjectPropertyBase):
', '.join(self.cls.get_valid_classes()),
value.__class__.__name__))
# TODO: emit Remove/AddNode mutations
current = self.__get__(instance, instance.__class__)
if current is not None:
current.detach()
current.clear_parent_container()
if instance.attached_to_root:
instance.root.remove_object(current)
super().__set__(instance, value)
if value is not None:
value.attach(instance)
value.set_parent_container(self)
if instance.attached_to_root:
instance.root.add_object(value)
def to_state(self, instance):
obj = instance.state.get(self.name, None)
@ -244,10 +254,13 @@ class ObjectList(object):
def __delitem__(self, idx):
self._objs[idx].detach()
self._objs[idx].clear_parent_container()
if self._instance.attached_to_root:
self._instance.root.remove_object(self._objs[idx])
del self._objs[idx]
for i in range(idx, len(self._objs)):
self._objs[i].set_index(i)
self._instance.root.handle_mutation(('update_objlist', self._instance, self._prop.name, 'delete', idx))
if self._instance.attached_to_root:
self._instance.root.handle_mutation(('update_objlist', self._instance, self._prop.name, 'delete', idx))
self._instance.listeners.call(self._prop.name, 'delete', idx)
def append(self, obj):
@ -256,18 +269,24 @@ class ObjectList(object):
def insert(self, idx, obj):
obj.attach(self._instance)
obj.set_parent_container(self)
if self._instance.attached_to_root:
self._instance.root.add_object(obj)
self._objs.insert(idx, obj)
for i in range(idx, len(self._objs)):
self._objs[i].set_index(i)
self._instance.root.handle_mutation(('update_objlist', self._instance, self._prop.name, 'insert', idx, obj))
if self._instance.attached_to_root:
self._instance.root.handle_mutation(('update_objlist', self._instance, self._prop.name, 'insert', idx, obj))
self._instance.listeners.call(self._prop.name, 'insert', idx, obj)
def clear(self):
for obj in self._objs:
obj.detach()
obj.clear_parent_container()
if self._instance.attached_to_root:
self._instance.root.remove_object(obj)
self._objs.clear()
self._instance.root.handle_mutation(('update_objlist', self._instance, self._prop.name, 'clear'))
if self._instance.attached_to_root:
self._instance.root.handle_mutation(('update_objlist', self._instance, self._prop.name, 'clear'))
self._instance.listeners.call(self._prop.name, 'clear')
@ -334,8 +353,8 @@ class StateMeta(type):
return super().__new__(mcs, name, parents, dct)
class StateBase(TreeNode, metaclass=StateMeta):
id = Property(str)
class StateBase(object, metaclass=StateMeta):
id = Property(str, allow_none=False)
_subclasses = {}
@ -343,6 +362,8 @@ class StateBase(TreeNode, metaclass=StateMeta):
super().__init__()
self.state = {}
self.listeners = CallbackRegistry()
self._is_root = False
self.parent = None
self.__parent_container = None
self.__index = None
@ -358,6 +379,28 @@ class StateBase(TreeNode, metaclass=StateMeta):
return False
return True
@property
def root(self):
if self.parent is None:
if self._is_root:
return self
raise ObjectNotAttachedError
return self.parent.root
@property
def attached_to_root(self):
if self.parent is None:
return self._is_root
return self.parent.attached_to_root
def attach(self, parent):
assert self.parent is None
self.parent = parent
def detach(self):
assert self.parent is not None
self.parent = None
@classmethod
def register_subclass(cls, subcls):
StateBase._subclasses.setdefault(cls, {})[subcls.__name__] = subcls
@ -477,7 +520,7 @@ class StateBase(TreeNode, metaclass=StateMeta):
obj.reset_state()
objs.clear()
self.state = {}
self.state = {'id': self.id} # Don't forget my ID.
def serialize(self):
d = {'__class__': self.__class__.__name__}
@ -499,11 +542,34 @@ class StateBase(TreeNode, metaclass=StateMeta):
return cls(state=state)
return cls.get_subclass(cls_name)(state=state)
class RootObject(StateBase):
def __init__(self, state=None):
super().__init__(state=state)
self.__obj_map = {}
self._is_root = True
def get_object(self, obj_id):
return self.__obj_map[obj_id]
def add_object(self, obj):
for o in obj.walk_children():
assert o.id is not None
assert o.id not in self.__obj_map
self.__obj_map[o.id] = o
def remove_object(self, obj):
for o in obj.walk_children():
assert o.id is not None
assert o.id in self.__obj_map, o
del self.__obj_map[o.id]
def init_references(self):
all_objects = {}
self.__obj_map.clear()
for node in self.walk_children():
assert node.id not in all_objects
all_objects[node.id] = node
assert node.id not in self.__obj_map
self.__obj_map[node.id] = node
for node in self.walk_children():
for prop in node.list_properties():
@ -513,8 +579,9 @@ class StateBase(TreeNode, metaclass=StateMeta):
assert isinstance(refid, tuple)
assert refid[0] == 'unresolved reference'
refid = refid[1]
refobj = all_objects[refid]
refobj = self.__obj_map[refid]
prop.__set__(node, refobj)
def handle_mutation(self, mutation):
pass

View File

@ -1,25 +0,0 @@
#!/usr/bin/python3
class TreeNode(object):
def __init__(self):
super().__init__()
self.parent = None
@property
def root(self):
if self.parent is None:
return self
return self.parent.root
def list_children(self):
yield from []
def attach(self, parent):
assert self.parent is None
self.parent = parent
def detach(self):
assert self.parent is not None
self.parent = None

View File

@ -10,8 +10,6 @@ from .runtime_settings import RuntimeSettings
from . import logging
from .core import process_manager
logger = logging.getLogger(__name__)
class Main(object):
def __init__(self):
@ -26,6 +24,7 @@ class Main(object):
self.parse_args(argv)
logging.init(self.runtime_settings)
self.logger = logging.getLogger(__name__)
if self.runtime_settings.dev_mode:
import pyximport
@ -60,7 +59,7 @@ class Main(object):
delay = next_retry - time.time()
if delay > 0:
logger.warning(
self.logger.warning(
"Sleeping %.1fsec before restarting.", delay)
await asyncio.sleep(delay)

View File

@ -21,7 +21,7 @@ class Error(Exception):
pass
class Instrument(core.StateBase, core.CommandTarget):
class Instrument(core.StateBase):
name = core.Property(str)
collection = core.ObjectReferenceProperty(allow_none=True)
@ -104,7 +104,7 @@ class SampleInstrument(Instrument):
Instrument.register_subclass(SampleInstrument)
class Collection(core.StateBase, core.CommandTarget):
class Collection(core.StateBase):
name = core.Property(str)
def __init__(self, name=None, state=None):
@ -131,7 +131,7 @@ class SoundFontCollection(Collection):
Collection.register_subclass(SoundFontCollection)
class InstrumentLibrary(core.StateBase, core.CommandDispatcher):
class InstrumentLibrary(core.StateBase):
ui_state = core.ObjectProperty(UIState)
instruments = core.ObjectListProperty(Instrument)
@ -147,7 +147,6 @@ class InstrumentLibrary(core.StateBase, core.CommandDispatcher):
self.ui_state = UIState()
self.set_root()
self.add_sub_target('ui_state', self.ui_state)
def add_instrument(self, instr):
self.instruments.append(instr)

View File

@ -13,6 +13,8 @@ def init(runtime_settings):
captureWarnings(True)
root_logger = getLogger()
for handler in root_logger.handlers:
root_logger.removeHandler(handler)
root_logger.setLevel(DEBUG)
log_level = {

View File

@ -22,7 +22,16 @@ class Mutation(object):
'objlist',
[v.id for v in value])
elif isinstance(prop, core.ListProperty):
return (prop.name, 'list', list(value))
else:
import pickle
try:
pickle.dumps(value)
except:
logger.exception('%s=%r', prop.name, value)
raise
return (prop.name, 'scalar', value)
@ -46,7 +55,6 @@ class AddObject(Mutation):
def __init__(self, obj):
self.id = obj.id
self.cls = obj.__class__.__name__
self.address = obj.address
self.properties = []
for prop in obj.list_properties():
if prop.name == 'id':
@ -54,8 +62,8 @@ class AddObject(Mutation):
self.properties.append(self._prop2tuple(obj, prop))
def __str__(self):
return '<AddObject id=%s address="%s" cls=%s %s>' % (
self.id, self.address, self.cls,
return '<AddObject id=%s cls=%s %s>' % (
self.id, self.cls,
' '.join(
'%s=%s:%r' % (p, t, v)
for p, t, v in self.properties))

View File

@ -274,7 +274,7 @@ class RemoveMeasure(core.Command):
core.Command.register_subclass(RemoveMeasure)
class Sheet(core.StateBase, core.CommandTarget):
class Sheet(core.StateBase):
name = core.Property(str, default="Sheet")
tracks = core.ObjectListProperty(Track)
property_track = core.ObjectProperty(SheetPropertyTrack)
@ -289,10 +289,6 @@ class Sheet(core.StateBase, core.CommandTarget):
for i in range(num_tracks):
self.tracks.append(ScoreTrack(name="Track %d" % i))
@property
def address(self):
return self.parent.address + 'sheet:' + self.name
@property
def project(self):
return self.parent
@ -341,26 +337,13 @@ class Sheet(core.StateBase, core.CommandTarget):
while len(track.measures) < max_length:
track.append_measure()
def get_sub_target(self, name):
if name.startswith('track:'):
return self.tracks[int(name[6:])]
if name == 'property_track':
return self.property_track
return super().get_sub_target(name)
class Metadata(core.StateBase, core.CommandTarget):
class Metadata(core.StateBase):
author = core.Property(str, allow_none=True)
license = core.Property(str, allow_none=True)
copyright = core.Property(str, allow_none=True)
created = core.Property(int, allow_none=True)
@property
def address(self):
return self.parent.address + 'metadata'
class JSONEncoder(json.JSONEncoder):
def default(self, obj): # pylint: disable=method-hidden
@ -401,7 +384,7 @@ class JSONDecoder(json.JSONDecoder):
return obj
class BaseProject(core.StateBase, core.CommandDispatcher):
class BaseProject(core.RootObject):
sheets = core.ObjectListProperty(cls=Sheet)
current_sheet = core.Property(int, default=0)
metadata = core.ObjectProperty(cls=Metadata)
@ -416,9 +399,6 @@ class BaseProject(core.StateBase, core.CommandDispatcher):
for i in range(1, num_sheets + 1):
self.sheets.append(Sheet(name="Sheet %d" % i))
self.address = '/'
self.set_root()
def set_mutation_callback(self, callback):
assert self._mutation_callback is None
self._mutation_callback = callback
@ -438,21 +418,10 @@ class BaseProject(core.StateBase, core.CommandDispatcher):
return idx
raise ValueError("No sheet %r" % name)
def get_sub_target(self, name):
if name.startswith('sheet:'):
sheet_name = name[6:]
for sheet in self.sheets:
if sheet.name == sheet_name:
return sheet
if name == 'metadata':
return self.metadata
return super().get_sub_target(name)
def dispatch_command(self, target, cmd):
result = super().dispatch_command(target, cmd)
logger.info("Executed command %s on %s", cmd, target)
def dispatch_command(self, obj_id, cmd):
obj = self.get_object(obj_id)
result = cmd.run(obj)
logger.info("Executed command %s on %s", cmd, obj_id)
return result
def handle_mutation(self, mutation):
@ -729,11 +698,11 @@ class Project(BaseProject):
if entry_type != b'C':
raise CorruptedProjectError(
"Unexpected log entry type %s" % entry_type)
target = headers['Target']
obj_id = headers['Target']
cmd_state = json.loads(serialized, cls=JSONDecoder)
cmd = core.Command.create_from_state(cmd_state)
logger.info("Replay command %s on %s", cmd, target)
super().dispatch_command(target, cmd)
logger.info("Replay command %s on %s", cmd, obj_id)
super().dispatch_command(obj_id, cmd)
def write_checkpoint(self, data_dir, sequence_number):
name_base = 'state.%d' % sequence_number
@ -764,13 +733,13 @@ class Project(BaseProject):
}
return state_data, command_log_fp
def dispatch_command(self, target, cmd):
def dispatch_command(self, obj_id, cmd):
if self.closed:
raise RuntimeError("Command %s executed on closed project." % cmd)
assert self.command_log_fp is not None
result = super().dispatch_command(target, cmd)
result = super().dispatch_command(obj_id, cmd)
serialized = json.dumps(
cmd.serialize(),
@ -782,7 +751,7 @@ class Project(BaseProject):
encoding='utf-8',
entry_type=b'C',
headers={
'Target': target,
'Target': obj_id,
'Time': time.ctime(now),
'Timestamp': '%d' % now,
})

View File

@ -12,10 +12,9 @@ logger = logging.getLogger(__name__)
class ObjectProxy(object):
def __init__(self, obj_id, cls, address):
def __init__(self, obj_id, cls):
self.id = obj_id
self.cls = cls
self.address = address
class ProjectClientBase(object):
@ -74,6 +73,8 @@ class ProjectClientMixin(object):
for prop_name, prop_type, value in mutation.properties:
if prop_type == 'scalar':
setattr(obj, prop_name, value)
elif prop_type == 'list':
setattr(obj, prop_name, value)
elif prop_type == 'obj':
setattr(obj, prop_name, self._object_map[value] if value is not None else None)
elif prop_type == 'objlist':
@ -84,11 +85,12 @@ class ProjectClientMixin(object):
"Property type %s not supported." % prop_type)
elif isinstance(mutation, mutations.AddObject):
obj = ObjectProxy(
mutation.id, mutation.cls, mutation.address)
obj = ObjectProxy(mutation.id, mutation.cls)
for prop_name, prop_type, value in mutation.properties:
if prop_type == 'scalar':
setattr(obj, prop_name, value)
elif prop_type == 'list':
setattr(obj, prop_name, value)
elif prop_type == 'obj':
setattr(obj, prop_name, self._object_map[value] if value is not None else None)
elif prop_type == 'objlist':

View File

@ -93,7 +93,7 @@ class ProjectProcessMixin(object):
for session in self.sessions.values():
tasks.append(self.event_loop.create_task(
session.publish_mutation(mutation)))
asyncio.wait(tasks, loop=self.event_loop)
await asyncio.wait(tasks, loop=self.event_loop)
async def handle_start_session(self, client_address):
client_stub = ipc.Stub(self.event_loop, client_address)

View File

@ -42,7 +42,7 @@ class AddSheetTest(unittest.TestCase):
def test_ok(self):
p = project.BaseProject()
cmd = project.AddSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
self.assertEqual(len(p.sheets), 2)
self.assertEqual(p.sheets[0].name, 'Sheet 1')
self.assertEqual(p.sheets[1].name, 'Sheet 2')
@ -51,27 +51,27 @@ class AddSheetTest(unittest.TestCase):
p = project.BaseProject()
cmd = project.AddSheet(name='Sheet 1')
with self.assertRaises(ValueError):
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
class DeleteSheetTest(unittest.TestCase):
def test_ok(self):
p = project.BaseProject()
cmd = project.AddSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
cmd = project.DeleteSheet(name='Sheet 1')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
self.assertEqual(len(p.sheets), 1)
self.assertEqual(p.sheets[0].name, 'Sheet 2')
def test_last_sheet(self):
p = project.BaseProject()
cmd = project.AddSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
cmd = project.SetCurrentSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
cmd = project.DeleteSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
self.assertEqual(len(p.sheets), 1)
self.assertEqual(p.sheets[0].name, 'Sheet 1')
self.assertEqual(p.current_sheet, 0)
@ -81,31 +81,31 @@ class RenameSheetTest(unittest.TestCase):
def test_ok(self):
p = project.BaseProject()
cmd = project.RenameSheet(name='Sheet 1', new_name='Foo')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
self.assertEqual(p.sheets[0].name, 'Foo')
def test_unchanged(self):
p = project.BaseProject()
cmd = project.RenameSheet(name='Sheet 1', new_name='Sheet 1')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
self.assertEqual(p.sheets[0].name, 'Sheet 1')
def test_duplicate_name(self):
p = project.BaseProject()
cmd = project.AddSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
cmd = project.RenameSheet(name='Sheet 1', new_name='Sheet 2')
with self.assertRaises(ValueError):
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
class SetCurrentSheetTest(unittest.TestCase):
def test_ok(self):
p = project.BaseProject()
cmd = project.AddSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
cmd = project.SetCurrentSheet(name='Sheet 2')
p.dispatch_command(p.address, cmd)
p.dispatch_command(p.id, cmd)
self.assertEqual(p.current_sheet, 1)

View File

@ -194,7 +194,7 @@ class SetAccidental(core.Command):
core.Command.register_subclass(SetAccidental)
class Note(core.StateBase, core.CommandTarget):
class Note(core.StateBase):
pitches = core.ListProperty(Pitch)
base_duration = core.Property(Duration)
dots = core.Property(int, default=0)
@ -312,7 +312,7 @@ class ScoreEventSource(EventSource):
pitch.name, timepos, note.duration)
yield NoteOnEvent(
timepos, pitch,
tags={(measure.address, 'noteon', idx)})
tags={(measure.id, 'noteon', idx)})
self._active_pitches.append(pitch)
t += note.duration.ticks

View File

@ -89,10 +89,6 @@ class SheetPropertyTrack(Track):
for _ in range(num_measures):
self.measures.append(SheetPropertyMeasure())
@property
def address(self):
return self.parent.address + '/property_track'
def create_empty_measure(self, ref):
measure = super().create_empty_measure(ref)

View File

@ -79,14 +79,10 @@ class SetInstrument(core.Command):
core.Command.register_subclass(SetInstrument)
class Measure(core.StateBase, core.CommandTarget):
class Measure(core.StateBase):
def __init__(self, state=None):
super().__init__(state)
@property
def address(self):
return self.parent.address + '/measure:%d' % self.index
@property
def track(self):
return self.parent
@ -114,7 +110,7 @@ class EventSource(object):
raise NotImplementedError
class Track(core.StateBase, core.CommandTarget):
class Track(core.StateBase):
measure_cls = None
name = core.Property(str)
@ -133,10 +129,6 @@ class Track(core.StateBase, core.CommandTarget):
self.name = name
self.instrument = instrument
@property
def address(self):
return self.parent.address + '/track:%d' % self.index
@property
def sheet(self):
return self.parent
@ -196,9 +188,3 @@ class Track(core.StateBase, core.CommandTarget):
def create_event_source(self):
raise NotImplementedError
def get_sub_target(self, name):
if name.startswith('measure:'):
return self.measures[int(name[8:])]
return super().get_sub_target(name)

View File

@ -193,7 +193,7 @@ class EditorApp(BaseEditorApp):
super().setup()
logger.info("Creating InstrumentLibrary.")
self.instrument_library = InstrumentLibrary()
self.instrument_library = None #InstrumentLibrary()
logger.info("Creating EditorWindow.")
self.win = EditorWindow(self)
@ -202,7 +202,13 @@ class EditorApp(BaseEditorApp):
if self.paths:
logger.info("Starting with projects from cmdline.")
for path in self.paths:
self.win.openProject(path)
if path.startswith('+'):
path = path[1:]
project = EditorProject(self)
project.create(path)
self.addProject(project)
else:
self.win.openProject(path)
else:
reopen_projects = self.settings.value('opened_projects', [])

View File

@ -84,8 +84,8 @@ class EditorWindow(QMainWindow):
self._docks = []
self._settings_dialog = SettingsDialog(self)
self._instrument_library_dialog = InstrumentLibraryDialog(
self, self._app, self._app.instrument_library)
# self._instrument_library_dialog = InstrumentLibraryDialog(
# self, self._app, self._app.instrument_library)
self._current_project_view = None

View File

@ -175,14 +175,14 @@ class MeasureItem(QGraphicsItem):
def onInsertMeasure(self):
self._project.dispatch_command(
self._sheet_view.sheet.address,
self._sheet_view.sheet.id,
InsertMeasure(
tracks=[self._measure.track.index],
pos=self._measure.index))
def onRemoveMeasure(self):
self._project.dispatch_command(
self._sheet_view.sheet.address,
self._sheet_view.sheet.id,
RemoveMeasure(
tracks=[self._measure.track.index],
pos=self._measure.index))
@ -314,7 +314,7 @@ class TrackItem(object):
def onRemoveTrack(self):
self._project.dispatch_command(
self._track.parent.address, RemoveTrack(track=self._track.index))
self._track.parent.id, RemoveTrack(track=self._track.index))
def onTrackInstrument(self):
if self._instrument_selector is None:
@ -331,10 +331,10 @@ class TrackItem(object):
def onSelectInstrument(self, instr):
if instr is None:
self._project.dispatch_command(
self._track.address, ClearInstrument())
self._track.id, ClearInstrument())
else:
self._project.dispatch_command(
self._track.address,
self._track.id,
SetInstrument(instr=instr.to_json()))
def onTrackProperties(self):
@ -362,7 +362,7 @@ class TrackItem(object):
ret = dialog.exec_()
self._project.dispatch_command(
self._track.address,
self._track.id,
UpdateTrackProperties(
name=name.text(),
))
@ -767,17 +767,17 @@ class ScoreMeasureItem(MeasureItem):
def onSetClef(self, clef):
self._project.dispatch_command(
self._measure.address, SetClef(clef=clef.value))
self._measure.id, SetClef(clef=clef.value))
self.recomputeLayout()
def onSetKeySignature(self, key_signature):
self._project.dispatch_command(
self._measure.address, SetKeySignature(key_signature=key_signature))
self._measure.id, SetKeySignature(key_signature=key_signature))
self.recomputeLayout()
def onSetTimeSignature(self, upper, lower):
self._project.dispatch_command(
self._sheet_view.sheet.property_track.measures[self._measure.index].address,
self._sheet_view.sheet.property_track.measures[self._measure.index].id,
SetTimeSignature(upper=upper, lower=lower))
self.recomputeLayout()
@ -916,7 +916,7 @@ class ScoreMeasureItem(MeasureItem):
def mousePressEvent(self, event):
if self._measure is None:
self._project.dispatch_command(
self._sheet_view.sheet.address,
self._sheet_view.sheet.id,
InsertMeasure(tracks=[], pos=-1))
event.accept()
return
@ -962,7 +962,7 @@ class ScoreMeasureItem(MeasureItem):
cmd = InsertNote(
idx=idx, pitch=pitch, duration=duration)
if cmd is not None:
self._project.dispatch_command(self._measure.address, cmd)
self._project.dispatch_command(self._measure.id, cmd)
self.recomputeLayout()
event.accept()
return
@ -983,7 +983,7 @@ class ScoreMeasureItem(MeasureItem):
if accidental in p.valid_accidentals:
if p.stave_line == stave_line:
cmd = SetAccidental(idx=idx, accidental=accidental, pitch_idx=pitch_idx)
self._project.dispatch_command(self._measure.address, cmd)
self._project.dispatch_command(self._measure.id, cmd)
self.recomputeLayout()
event.accept()
return
@ -1021,7 +1021,7 @@ class ScoreMeasureItem(MeasureItem):
cmd = ChangeNote(idx=idx, tuplet=5)
if cmd is not None:
self._project.dispatch_command(self._measure.address, cmd)
self._project.dispatch_command(self._measure.id, cmd)
self.recomputeLayout()
event.accept()
return
@ -1118,7 +1118,7 @@ class SheetPropertyMeasureItem(MeasureItem):
def mousePressEvent(self, event):
if self._measure is None:
self._project.dispatch_command(
self._sheet_view.sheet.address,
self._sheet_view.sheet.id,
InsertMeasure(tracks=[], pos=-1))
event.accept()
return
@ -1138,7 +1138,7 @@ class SheetPropertyMeasureItem(MeasureItem):
def onBPMEdited(self, value):
self._project.dispatch_command(
self._measure.address, SetBPM(bpm=value))
self._measure.id, SetBPM(bpm=value))
self.bpm.setText('%d bpm' % value)
def onBPMClose(self):
@ -1511,7 +1511,7 @@ class SheetView(QGraphicsView):
raise ValueError("Unknown action %r" % action)
def onAddTrack(self, track_type):
self._project.dispatch_command(self._sheet.address, AddTrack(
self._project.dispatch_command(self._sheet.id, AddTrack(
track_type=track_type))
def onPlaybackTags(self, tags):
@ -1520,8 +1520,8 @@ class SheetView(QGraphicsView):
self, PlaybackTagEvent(tag))
def onPlaybackTag(self, event):
address, event, idx = event.tag
measure = self._project.get_object(address)
id, event, idx = event.tag
measure = self._project.get_object(id)
self._tracks[measure.track.index].onPlaybackTag(
measure.index, event, idx)

View File

@ -174,7 +174,7 @@ class TrackPropertiesDockWidget(DockWidget):
if name != self._track.name:
self._track.project.dispatch_command(
self._track.address,
self._track.id,
UpdateTrackProperties(name=name))
def onVolumeChanged(self, old_volume, new_volume):
@ -186,7 +186,7 @@ class TrackPropertiesDockWidget(DockWidget):
if volume != self._track.volume:
self._track.project.dispatch_command(
self._track.address,
self._track.id,
UpdateTrackProperties(volume=volume))
def onMutedChanged(self, old_value, new_value):
@ -199,7 +199,7 @@ class TrackPropertiesDockWidget(DockWidget):
if muted != self._track.muted:
self._track.project.dispatch_command(
self._track.address,
self._track.id,
UpdateTrackProperties(muted=muted))
def onInstrumentChanged(self, old_instrument, new_instrument):
@ -228,10 +228,10 @@ class TrackPropertiesDockWidget(DockWidget):
if instr is None:
self._track.project.dispatch_command(
self._track.address, ClearInstrument())
self._track.id, ClearInstrument())
else:
self._track.project.dispatch_command(
self._track.address,
self._track.id,
SetInstrument(instr=instr.to_json()))
def onTransposeOctavesChanged(
@ -244,5 +244,5 @@ class TrackPropertiesDockWidget(DockWidget):
if transpose_octaves != self._track.transpose_octaves:
self._track.project.dispatch_command(
self._track.address,
self._track.id,
UpdateTrackProperties(transpose_octaves=transpose_octaves))

View File

@ -79,7 +79,7 @@ class TracksModel(QAbstractListModel):
track = index.internalPointer()
self._project.dispatch_command(
track.address,
track.id,
UpdateTrackProperties(name=value))
self.dataChanged.emit(index, index)
return True
@ -90,20 +90,20 @@ class TracksModel(QAbstractListModel):
def toggleTrackVisible(self, index):
track = index.internalPointer()
self._project.dispatch_command(
track.address,
track.id,
UpdateTrackProperties(visible=not track.visible))
self.dataChanged.emit(index, index)
def toggleTrackMute(self, index):
track = index.internalPointer()
self._project.dispatch_command(
track.address,
track.id,
UpdateTrackProperties(muted=not track.muted))
self.dataChanged.emit(index, index)
def addTrack(self, track_type):
track_idx = self._project.dispatch_command(
self._sheet.address,
self._sheet.id,
AddTrack(track_type=track_type))
self.dataChanged.emit(self.index(track_idx),
self.index(self.rowCount(None) - 1))
@ -112,7 +112,7 @@ class TracksModel(QAbstractListModel):
def removeTrack(self, index):
track = index.internalPointer()
self._project.dispatch_command(
self._sheet.address,
self._sheet.id,
RemoveTrack(track=track.index))
self.dataChanged.emit(self.index(0),
self.index(self.rowCount(None) - 1))
@ -120,7 +120,7 @@ class TracksModel(QAbstractListModel):
def moveTrack(self, index, direction):
track = index.internalPointer()
track_idx = self._project.dispatch_command(
self._sheet.address,
self._sheet.id,
MoveTrack(track=track.index, direction=direction))
self.dataChanged.emit(self.index(0),
self.index(self.rowCount(None) - 1))

View File

@ -49,7 +49,7 @@ class UpdateUIState(core.Command):
core.Command.register_subclass(UpdateUIState)
class UIState(core.StateBase, core.CommandDispatcher):
class UIState(core.StateBase):
ui_state = core.DictProperty()
def __init__(self, state=None):