Skip to content

Commit

Permalink
canvas/readwrite: Serialize window group presets to workflow file
Browse files Browse the repository at this point in the history
  • Loading branch information
ales-erjavec committed Jun 15, 2018
1 parent 565c584 commit 8d23609
Showing 1 changed file with 137 additions and 49 deletions.
186 changes: 137 additions & 49 deletions Orange/canvas/scheme/readwrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
Scheme save/load routines.
"""
import base64
import sys
import warnings
import base64
import binascii

from xml.etree.ElementTree import TreeBuilder, Element, ElementTree, parse

from collections import defaultdict, namedtuple
from collections import defaultdict
from itertools import chain, count

import pickle as pickle
Expand All @@ -20,6 +21,8 @@

import logging

from typing import NamedTuple, List, Dict, Tuple, Union

from . import SchemeNode, SchemeLink
from .annotations import SchemeTextAnnotation, SchemeArrowAnnotation
from .errors import IncompatibleChannelTypeError
Expand Down Expand Up @@ -316,7 +319,7 @@ def parse_scheme_v_2_0(etree, scheme, error_handler, widget_registry=None,
def parse_scheme_v_1_0(etree, scheme, error_handler, widget_registry=None,
allow_pickle_data=False):
"""
ElementTree Instance of an old .ows scheme format.
ElementTree Instance of an old .ows scheme format (Orange < 2.7).
"""
if widget_registry is None:
widget_registry = global_registry()
Expand Down Expand Up @@ -397,38 +400,86 @@ def parse_scheme_v_1_0(etree, scheme, error_handler, widget_registry=None,


# Intermediate scheme representation
_scheme = namedtuple(
"_scheme",
["title", "version", "description", "nodes", "links", "annotations"])

_node = namedtuple(
"_node",
["id", "title", "name", "position", "project_name", "qualified_name",
"version", "data"])

_data = namedtuple(
"_data",
["format", "data"])
_scheme = NamedTuple(
"_scheme", [
("title", str),
("version", str),
("description", str),
("nodes", 'List[_node]'),
("links", 'List[_link]'),
("annotations", 'List[_annotation]'),
("session_state", '_session_data')
]
)

_node = NamedTuple(
"_node", [
("id", str),
("title", str),
("name", str),
("position", 'Tuple[float, float]'),
("project_name", str),
("qualified_name", str),
("version", str),
("data", '_data')
]
)

_data = NamedTuple(
"_data", [
("format", str),
("data", bytes)
]
)

_link = NamedTuple(
"_link", [
("id", str),
("source_node_id", str),
("sink_node_id", str),
("source_channel", str),
("sink_channel", str),
("enabled", bool),
]
)

_annotation = NamedTuple(
"_annotation", [
("id", str),
("type", str),
("params", 'Union[_text_params, _arrow_params]'),
]
)

_text_params = NamedTuple(
"_text_params", [
("geometry", str),
("text", str),
("font", 'Dict[str, Any]'),
("content_type", str),
]
)

_arrow_params = NamedTuple(
"_arrow_params", [
("geometry", str),
("color", str),
])


_session_data = NamedTuple(
"_session_data", [
("groups", 'List[Tuple[str, List[Tuple[str, bytes]]]]')
]
)

_link = namedtuple(
"_link",
["id", "source_node_id", "sink_node_id", "source_channel", "sink_channel",
"enabled"])

_annotation = namedtuple(
"_annotation",
["id", "type", "params"])

_text_params = namedtuple(
"_text_params",
["geometry", "text", "font", "content_type"])

_arrow_params = namedtuple(
"_arrow_params",
["geometry", "color"])
def parse_ows_etree_v_2_0(tree):
return parse_ows_etree_v_2_0_1(tree)


def parse_ows_etree_v_2_0(tree):
def parse_ows_etree_v_2_0_1(tree):
# type: (ElementTree) -> _scheme
scheme = tree.getroot()
nodes, links, annotations = [], [], []

Expand Down Expand Up @@ -499,15 +550,36 @@ def parse_ows_etree_v_2_0(tree):
type="arrow",
params=_arrow_params((start, end), color)
)
else:
log.warn("Unknown annotation '%s'. Skipping.", annot.tag)
continue
annotations.append(annotation)

window_presets = []
for window_group in tree.findall("session_state/window_groups/group"):
name = window_group.get("name") # type: str
state = []
for state_ in window_group.findall("window_state"):
node_id = state_.get("node_id") # type: str
try:
data = base64.decodebytes(state_.text.encode("ascii"))
except binascii.Error:
data = b''
except UnicodeDecodeError:
data = b''
state.append((node_id, data))
window_presets.append((name, state))

session_state = _session_data(window_presets)

return _scheme(
version=scheme.get("version"),
title=scheme.get("title", ""),
description=scheme.get("description"),
nodes=nodes,
links=links,
annotations=annotations
annotations=annotations,
session_state=session_state,
)


Expand Down Expand Up @@ -568,6 +640,7 @@ def parse_ows_etree_v_1_0(tree):


def parse_ows_stream(stream):
# type: (...) -> _scheme
doc = parse(stream)
scheme_el = doc.getroot()
version = scheme_el.get("version", None)
Expand Down Expand Up @@ -659,7 +732,7 @@ def resolve_replaced(scheme_desc, registry):


def scheme_load(scheme, stream, registry=None, error_handler=None):
desc = parse_ows_stream(stream)
desc = parse_ows_stream(stream) # type: _scheme

if registry is None:
registry = global_registry()
Expand Down Expand Up @@ -735,6 +808,7 @@ def error_handler(exc):

else:
log.warning("Ignoring unknown annotation type: %r", annot_d.type)
continue
annotations.append(annot)

for node in nodes:
Expand All @@ -746,17 +820,17 @@ def error_handler(exc):
for annot in annotations:
scheme.add_annotation(annot)

if desc.session_state.groups:
# resolve node_id -> node
groups = []
for name, state in desc.session_state.groups:
state = [(nodes_by_id[node_id], data)
for node_id, data in state if node_id in nodes_by_id]
groups.append((name, state))
scheme.setProperty("_presets", groups)
return scheme


def inf_range(start=0, step=1):
"""Return an infinite range iterator.
"""
while True:
yield start
start += step


def scheme_to_etree(scheme, data_format="literal", pickle_fallback=False):
"""
Return an `xml.etree.ElementTree` representation of the `scheme.
Expand All @@ -766,10 +840,10 @@ def scheme_to_etree(scheme, data_format="literal", pickle_fallback=False):
"title": scheme.title or "",
"description": scheme.description or ""})

## Nodes
node_ids = defaultdict(inf_range().__next__)
# Nodes
node_ids = defaultdict(count().__next__)
builder.start("nodes", {})
for node in scheme.nodes:
for node in scheme.nodes: # type: SchemeNode
desc = node.description
attrs = {"id": str(node_ids[node]),
"name": desc.name,
Expand All @@ -789,10 +863,10 @@ def scheme_to_etree(scheme, data_format="literal", pickle_fallback=False):

builder.end("nodes")

## Links
link_ids = defaultdict(inf_range().__next__)
# Links
link_ids = defaultdict(count().__next__)
builder.start("links", {})
for link in scheme.links:
for link in scheme.links: # type: SchemeLink
source = link.source_node
sink = link.sink_node
source_id = node_ids[source]
Expand All @@ -809,8 +883,8 @@ def scheme_to_etree(scheme, data_format="literal", pickle_fallback=False):

builder.end("links")

## Annotations
annotation_ids = defaultdict(inf_range().__next__)
# Annotations
annotation_ids = defaultdict(count().__next__)
builder.start("annotations", {})
for annotation in scheme.annotations:
annot_id = annotation_ids[annotation]
Expand Down Expand Up @@ -867,6 +941,20 @@ def scheme_to_etree(scheme, data_format="literal", pickle_fallback=False):
builder.end("properties")

builder.end("node_properties")
builder.start("session_state", {})
builder.start("window_groups", {})

for name, state in scheme.property("_presets") or []:
builder.start("group", {"name": name})
for node, data in state:
if node not in node_ids:
continue
builder.start("window_state", {"node_id": str(node_ids[node])})
builder.data(base64.encodebytes(data).decode("ascii"))
builder.end("window_state")
builder.end("group")
builder.end("window_group")
builder.end("session_state")
builder.end("scheme")
root = builder.close()
tree = ElementTree(root)
Expand Down

0 comments on commit 8d23609

Please sign in to comment.