Source code for psi.experiment.dock_layout_serializer

'''
Conversion between enaml's Atom-based dock layout tree and plain Python
data (dict/list/str/int/bool/None) that can be round-tripped through YAML
or JSON instead of pickle.

``enaml.widgets.dock_area.DockArea.save_layout()`` returns a small, closed
tree of ``enaml.layout.dock_layout.LayoutNode`` subclasses (``ItemLayout``,
``TabLayout``, ``SplitLayout``/``HSplitLayout``/``VSplitLayout``,
``DockBarLayout``, ``AreaLayout``, ``DockLayout``). These are Atom objects,
not plain containers, so they can't be handed directly to ``yaml.dump``.
The node grammar is fixed and small, so a hand-written recursive
converter is straightforward -- see ``enaml/qt/docking/layout_saver.py``
for the authoritative list of node types and fields this mirrors.

``HSplitLayout``/``VSplitLayout`` are reconstructed as plain
``SplitLayout`` -- they are only convenience constructors that pin the
``orientation`` field, which is what every consumer actually inspects (no
code in enaml isinstance-checks for the H/V subclasses specifically).
'''
from enaml.layout.dock_layout import (
    AreaLayout, DockBarLayout, DockLayout, ItemLayout, SplitLayout, TabLayout,
)
from enaml.layout.geometry import Rect


def _rect_to_list(rect):
    return [rect.x, rect.y, rect.width, rect.height]


def _rect_from_list(seq):
    return Rect(*seq)


[docs] def dock_layout_node_to_dict(node): '''Recursively convert a LayoutNode tree into plain dict/list data.''' if isinstance(node, ItemLayout): return { 'type': 'item', 'name': node.name, 'floating': node.floating, 'geometry': _rect_to_list(node.geometry), 'linked': node.linked, 'maximized': node.maximized, } if isinstance(node, TabLayout): return { 'type': 'tab', 'tab_position': node.tab_position, 'index': node.index, 'maximized': node.maximized, 'items': [dock_layout_node_to_dict(i) for i in node.items], } if isinstance(node, SplitLayout): return { 'type': 'split', 'orientation': node.orientation, 'sizes': list(node.sizes), 'items': [dock_layout_node_to_dict(i) for i in node.items], } if isinstance(node, DockBarLayout): return { 'type': 'dockbar', 'position': node.position, 'items': [dock_layout_node_to_dict(i) for i in node.items], } if isinstance(node, AreaLayout): return { 'type': 'area', 'item': dock_layout_node_to_dict(node.item) if node.item is not None else None, 'dock_bars': [dock_layout_node_to_dict(b) for b in node.dock_bars], 'floating': node.floating, 'geometry': _rect_to_list(node.geometry), 'linked': node.linked, 'maximized': node.maximized, } if isinstance(node, DockLayout): return { 'type': 'dock', 'items': [dock_layout_node_to_dict(i) for i in node.items], } raise TypeError(f'Unsupported dock layout node type: {type(node)!r}')
[docs] def dock_layout_node_from_dict(d): '''Reverse of dock_layout_node_to_dict.''' if d is None: return None kind = d['type'] if kind == 'item': return ItemLayout( d['name'], floating=d['floating'], geometry=_rect_from_list(d['geometry']), linked=d['linked'], maximized=d['maximized'], ) if kind == 'tab': items = [dock_layout_node_from_dict(i) for i in d['items']] return TabLayout( *items, tab_position=d['tab_position'], index=d['index'], maximized=d['maximized'], ) if kind == 'split': items = [dock_layout_node_from_dict(i) for i in d['items']] return SplitLayout( *items, orientation=d['orientation'], sizes=list(d['sizes']), ) if kind == 'dockbar': items = [dock_layout_node_from_dict(i) for i in d['items']] return DockBarLayout(*items, position=d['position']) if kind == 'area': item = dock_layout_node_from_dict(d['item']) return AreaLayout( item, dock_bars=[dock_layout_node_from_dict(b) for b in d['dock_bars']], floating=d['floating'], geometry=_rect_from_list(d['geometry']), linked=d['linked'], maximized=d['maximized'], ) if kind == 'dock': items = [dock_layout_node_from_dict(i) for i in d['items']] return DockLayout(*items) raise ValueError(f'Unknown dock layout node type: {kind!r}')
[docs] def workspace_layout_to_dict(layout): '''Convert the dict returned by ExperimentPlugin.get_layout() into plain dict/list/scalar data suitable for yaml.dump/json.dump.''' return { 'geometry': _rect_to_list(layout['geometry']), 'toolbars': layout['toolbars'], 'dock_layout': dock_layout_node_to_dict(layout['dock_layout']), }
[docs] def workspace_layout_from_dict(d): '''Reverse of workspace_layout_to_dict; returns a dict suitable for ExperimentPlugin.set_layout().''' return { 'geometry': _rect_from_list(d['geometry']), 'toolbars': d['toolbars'], 'dock_layout': dock_layout_node_from_dict(d['dock_layout']), }