Commit 28379b664a7167f5fa9625a9c1b24b5366b23d5d
1 parent
27f0d5bd
Exists in
better_forms
copied from amda_xml_manager project
Showing
22 changed files
with
1683 additions
and
294 deletions
Show diff stats
xsd2tkform/core/annotation.py
... | ... | @@ -7,6 +7,8 @@ class Annotation: |
7 | 7 | self.documentation = docs # dictionary where keys are language tags and values are documentation text |
8 | 8 | def languages(self): |
9 | 9 | return [k for k in self.documentation] |
10 | + def __eq__(self, e): | |
11 | + return self.docs==e.docs | |
10 | 12 | def __str__(self): |
11 | 13 | return "Annotation(languages={})".format(self.languages()) |
12 | 14 | ... | ... |
xsd2tkform/core/choice.py
xsd2tkform/core/element.py
1 | 1 | """Element definition |
2 | 2 | """ |
3 | +from lxml import etree | |
4 | + | |
5 | +class Attribute: | |
6 | + def __init__(self, name=None, type="string", use=None, default=None): | |
7 | + self.name=name | |
8 | + self.type=type | |
9 | + self.use=use | |
10 | + self.default=default | |
11 | + def mandatory(self): | |
12 | + return self.use=="required" | |
13 | + def __eq__(self, e): | |
14 | + if not isinstance(e, Attribute): | |
15 | + return False | |
16 | + return self.name==e.name and self.type==e.type and self.use==e.use and self.default==e.default | |
17 | + def __str__(self): | |
18 | + return "Attribute (name={}, type={}, use={}, default={})".format(self.name, self.type, self.use, self.default) | |
19 | + @staticmethod | |
20 | + def from_element(element): | |
21 | + att=dict(element.attrib) | |
22 | + return Attribute(**att) | |
23 | + | |
24 | +class AnyElement: | |
25 | + def __init__(self, **kwargs): | |
26 | + self.attributes=kwargs | |
27 | + def __str__(self, *args, **kwargs): | |
28 | + return "AnyElement" | |
3 | 29 | |
4 | 30 | class Element: |
5 | - def __init__(self, name=None, etype=None, min_occurs=0, max_occurs=0): | |
31 | + def __init__(self, name=None, etype=None, min_occurs=1, max_occurs=1, abstract=False, typedef=None, ref=None, substitution_group=None,**kwargs): | |
6 | 32 | self.name=name |
7 | 33 | self.type=etype |
8 | 34 | self.min_occurs=min_occurs |
9 | 35 | self.max_occurs=max_occurs |
36 | + self.typedef = typedef | |
37 | + self.abstract=abstract | |
38 | + self.ref=ref | |
39 | + self.substitution_group=substitution_group | |
40 | + if self.type is None: | |
41 | + if self.typedef is None: | |
42 | + self.type = self.name | |
43 | + else: | |
44 | + self.type = self.typedef.name | |
45 | + def __eq__(self, e): | |
46 | + return self.name==e.name and self.type==e.type and self.min_occurs==e.min_occurs and \ | |
47 | + self.max_occurs==e.max_occurs and self.typedef==e.typedef and self.abstract==e.abstract and \ | |
48 | + self.ref==e.ref and self.substitution_group==e.substitution_group | |
10 | 49 | def __str__(self, tab_n=0): |
11 | - return "\t"*tab_n+"Element(name={}, type={}, min_occurs={}, max_occurs={})".format(self.name, | |
12 | - self.type,self.min_occurs, self.max_occurs) | |
50 | + return "\t"*tab_n+"Element(name={}, type={}, min_occurs={}, max_occurs={}, abstract={}, typedef={}, ref={})".format(self.name, | |
51 | + self.type,self.min_occurs, self.max_occurs, self.abstract, self.typedef, self.ref) | |
13 | 52 | @staticmethod |
14 | 53 | def from_element(element): |
15 | 54 | att=dict(element.attrib) |
... | ... | @@ -23,4 +62,44 @@ class Element: |
23 | 62 | att["max_occurs"]=att.pop("maxOccurs") |
24 | 63 | if att["max_occurs"].isdigit(): |
25 | 64 | att["max_occurs"]=int(att["max_occurs"]) |
26 | - return Element(**att) | |
65 | + if "substitutionGroup" in att: | |
66 | + att["substitution_group"]=att.pop("substitutionGroup") | |
67 | + if "abstract" in att: | |
68 | + print("ABSTRACT val ", att["abstract"], type(att["abstract"])) | |
69 | + if att["abstract"]=="true": | |
70 | + att["abstract"]=True | |
71 | + if att["abstract"]=="false": | |
72 | + att["abstract"]=False | |
73 | + # check if element contains type definition | |
74 | + ct=None | |
75 | + for child in element: | |
76 | + if child.tag.endswith("complexType"): | |
77 | + from .type import ComplexType, SimpleType | |
78 | + from .utils import get_sequence, get_extension | |
79 | + from .sequence import Sequence | |
80 | + sequence = get_sequence(child) | |
81 | + attributes= get_attributes(child) | |
82 | + extension=get_extension(child) | |
83 | + if extension is None: | |
84 | + ct=ComplexType(att["name"], annotation=None, sequence=sequence, attributes=attributes, extension=extension) | |
85 | + elif isinstance(extension[1], Sequence): | |
86 | + ct=ComplexType(att["name"], annotation=None, sequence=sequence, attributes=attributes, extension=extension) | |
87 | + elif isinstance(extension[1], list): | |
88 | + from .restriction import Restriction | |
89 | + ct=SimpleType(att["name"], restriction=Restriction(base=extension[0]), attributes=extension[1]) | |
90 | + else: | |
91 | + ct=ComplexType(att["name"], annotation=None, sequence=sequence, attributes=attributes, extension=extension) | |
92 | + continue | |
93 | + | |
94 | + | |
95 | + | |
96 | + return Element(typedef = ct, **att) | |
97 | + | |
98 | +def get_attributes(element): | |
99 | + attributes=[] | |
100 | + for child in element: | |
101 | + if isinstance(child, etree._Comment): | |
102 | + continue | |
103 | + if child.tag.endswith("}attribute"): | |
104 | + attributes.append(Attribute.from_element(child)) | |
105 | + return attributes | ... | ... |
xsd2tkform/core/parser.py
... | ... | @@ -4,9 +4,9 @@ For now we suppose the file contains a single schema |
4 | 4 | """ |
5 | 5 | from lxml import etree |
6 | 6 | |
7 | -from xsd2tkform.core.type import SimpleType, ComplexType, Group, from_element | |
8 | -from xsd2tkform.core.annotation import Annotation | |
9 | -from xsd2tkform.core.schema import Schema | |
7 | +from .type import SimpleType, ComplexType, Group, from_element | |
8 | +from .annotation import Annotation | |
9 | +from .schema import Schema | |
10 | 10 | |
11 | 11 | class XSDParser: |
12 | 12 | def __init__(self, filename=None): |
... | ... | @@ -21,7 +21,10 @@ class XSDParser: |
21 | 21 | ns=root.nsmap |
22 | 22 | # if root is a schema |
23 | 23 | if root.tag.endswith("schema"): |
24 | - self.schemas.append(Schema.from_element(root)) | |
24 | + self.schemas.append(Schema.from_element(root, filename)) | |
25 | + # apply substitutions | |
26 | + self.schemas[-1].apply_substitutions() | |
27 | + self.schemas[-1].apply_extensions() | |
25 | 28 | return |
26 | 29 | def get(self, typename): |
27 | 30 | for schema in self.schemas: | ... | ... |
xsd2tkform/core/restriction.py
... | ... | @@ -6,6 +6,8 @@ class Restriction: |
6 | 6 | def __init__(self, base=None, enum=[]): |
7 | 7 | self.base=base |
8 | 8 | self.enum=enum |
9 | + def __eq__(self, e): | |
10 | + return self.base==e.base and self.enum==e.enum | |
9 | 11 | def __str__(self): |
10 | 12 | return "Restriction(base={}, enum={})".format(self.base, self.enum) |
11 | 13 | def possible_values(self): | ... | ... |
xsd2tkform/core/schema.py
... | ... | @@ -2,27 +2,116 @@ |
2 | 2 | """ |
3 | 3 | from lxml import etree |
4 | 4 | |
5 | -from xsd2tkform.core.type import SimpleType, ComplexType, Group, from_element | |
6 | -from xsd2tkform.core.element import Element | |
5 | +from .type import SimpleType, ComplexType, Group, from_element | |
6 | +from .element import Element | |
7 | 7 | |
8 | 8 | class Schema: |
9 | - def __init__(self): | |
9 | + def __init__(self, filename): | |
10 | + self.filename=filename # need filename to manage include statements | |
11 | + self.root_items = [] | |
10 | 12 | self.items=[] |
13 | + #self.elements={} # map element names to their type | |
11 | 14 | self.simple_types={} |
12 | 15 | self.complex_types={} |
13 | 16 | self.groups={} |
14 | - def parse_element(self, element): | |
17 | + def find_ref(self, name): | |
18 | + for i in self.items: | |
19 | + if i.name==name: | |
20 | + return i | |
21 | + def find_substitutions(self, name): | |
22 | + return [e for e in self.items if e.substitution_group==name] | |
23 | + def include_schema(self, element): | |
24 | + schema_file = element.attrib["schemaLocation"] | |
25 | + from .parser import XSDParser | |
26 | + import os | |
27 | + path = os.path.join(os.path.dirname(self.filename), schema_file) | |
28 | + parser = XSDParser(path) | |
29 | + for schema in parser.schemas: | |
30 | + self.add_schema(schema) | |
31 | + def add_schema(self, schema): | |
32 | + for i in schema.items: | |
33 | + if not i in self.items: | |
34 | + self.items.append(i) | |
35 | + for ri in schema.root_items: | |
36 | + if not ri in self.root_items: | |
37 | + self.root_items.append(ri) | |
38 | + # simple types | |
39 | + for k in schema.simple_types: | |
40 | + self.simple_types[k]=schema.simple_types[k] | |
41 | + # complex types | |
42 | + for k in schema.complex_types: | |
43 | + self.complex_types[k]=schema.complex_types[k] | |
44 | + # groups | |
45 | + for k in schema.groups: | |
46 | + self.groups[k]=schema.groups[k] | |
47 | + | |
48 | + | |
49 | + | |
50 | + def parse_element(self, element, root=True, name=None): | |
51 | + # if element is an include statement | |
52 | + if element.tag.endswith("}include"): | |
53 | + self.include_schema(element) | |
15 | 54 | td = from_element(element) |
55 | + if td is not None: | |
56 | + if td.name=="get": | |
57 | + pass | |
16 | 58 | if isinstance(td, SimpleType): |
17 | 59 | self.simple_types[td.name]=td |
18 | 60 | elif isinstance(td, ComplexType): |
19 | 61 | self.complex_types[td.name]=td |
20 | 62 | elif isinstance(td, Group): |
21 | 63 | self.groups[td.name]=td |
64 | + | |
22 | 65 | elif isinstance(td, Element): |
66 | + if td.type is None: | |
67 | + # get type definition in this element | |
68 | + td.type=td.name | |
69 | + ct = td.typedef | |
70 | + self.complex_types[ct.name]=ct | |
71 | + #for child in element: | |
72 | + # self.parse_element(child, name=td.name, root=False) | |
73 | + #td.type = td.name | |
74 | + | |
23 | 75 | self.items.append(td) |
76 | + if root: | |
77 | + self.root_items.append(td) | |
24 | 78 | else: |
25 | - print("Unknown type defined : ", element, element.attrib, element.tag) | |
79 | + pass | |
80 | + | |
81 | + # WARNING : should not do this | |
82 | + for child in element: | |
83 | + if isinstance(child, etree._Comment): | |
84 | + continue | |
85 | + self.parse_element(child, root=False) | |
86 | + def apply_substitutions(self): | |
87 | + for i in self.items: | |
88 | + if i.substitution_group is not None: | |
89 | + # get the current object type definition | |
90 | + td = self.get(i.type) | |
91 | + # get the base type | |
92 | + bases = [e for e in self.items if e.name==i.substitution_group] | |
93 | + if len(bases): | |
94 | + # get the corresponding type | |
95 | + base_t = self.get(bases[0].type) | |
96 | + for att in base_t.attributes: | |
97 | + if not att in td.attributes: | |
98 | + td.attributes.append(att) | |
99 | + self.complex_types[td.name]=td | |
100 | + def apply_extensions(self): | |
101 | + for k in self.complex_types: | |
102 | + t = self.complex_types[k] | |
103 | + if t.extension is not None: | |
104 | + # get base type | |
105 | + base_type = self.get(t.extension[0]) | |
106 | + # add attributes and sequence element | |
107 | + for att in base_type.attributes: | |
108 | + if not att in t.attributes: | |
109 | + t.attributes.append(att) | |
110 | + for item in base_type.sequence.items: | |
111 | + if not item in t.sequence.items: | |
112 | + t.sequence.add(item) | |
113 | + t.sequence = t.extension[1] | |
114 | + t.extension=None | |
26 | 115 | def get(self, typename): |
27 | 116 | if ":" in typename: |
28 | 117 | typename = typename.split(":")[-1] |
... | ... | @@ -32,6 +121,10 @@ class Schema: |
32 | 121 | return self.complex_types[typename] |
33 | 122 | if typename in self.groups: |
34 | 123 | return self.groups[typename] |
124 | + for item in self.items: | |
125 | + if typename==item.name: | |
126 | + return item | |
127 | + | |
35 | 128 | def __contains__(self, typename): |
36 | 129 | if typename in self.simple_types: |
37 | 130 | return True |
... | ... | @@ -39,13 +132,16 @@ class Schema: |
39 | 132 | return True |
40 | 133 | if typename in self.groups: |
41 | 134 | return True |
135 | + item_names = [i.name for i in self.items] | |
136 | + if typename in item_names: | |
137 | + return True | |
42 | 138 | return False |
43 | 139 | |
44 | 140 | @staticmethod |
45 | - def from_element(element): | |
141 | + def from_element(element, filename): | |
46 | 142 | if not element.tag.endswith("schema"): |
47 | 143 | raise Exception("Cannot initialize Schema from {}".format(element)) |
48 | - schema = Schema() | |
144 | + schema = Schema(filename=filename) | |
49 | 145 | # load type definitions and components |
50 | 146 | for child in element: |
51 | 147 | # ignore comments | ... | ... |
xsd2tkform/core/sequence.py
... | ... | @@ -2,14 +2,16 @@ |
2 | 2 | |
3 | 3 | """ |
4 | 4 | |
5 | -from xsd2tkform.core.element import Element | |
6 | -from xsd2tkform.core.choice import Choice | |
5 | +from .element import Element | |
6 | +from .choice import Choice | |
7 | 7 | |
8 | 8 | class Sequence: |
9 | 9 | def __init__(self, items=[]): |
10 | 10 | self.items = items |
11 | 11 | def add(self, i): |
12 | 12 | self.items.append(i) |
13 | + def __eq__(self, e): | |
14 | + return self.items==e.items | |
13 | 15 | def __str__(self, tab_n=0): |
14 | 16 | r="\t"*tab_n+"Sequence" |
15 | 17 | if len(self.items): |
... | ... | @@ -28,6 +30,8 @@ class Sequence: |
28 | 30 | s.add(Element.from_element(child)) |
29 | 31 | elif child.tag.endswith("choice"): |
30 | 32 | s.add(Choice.from_element(child)) |
33 | + elif child.tag.endswith("any"): | |
34 | + s.add(AnyElement(**dict(child.attrib))) | |
31 | 35 | else: |
32 | 36 | pass |
33 | 37 | return s | ... | ... |
xsd2tkform/core/type.py
... | ... | @@ -2,47 +2,68 @@ |
2 | 2 | |
3 | 3 | XSDType class is the base class for all XSD types, inherited by the XSDSimpleType, XSDComplexType classes""" |
4 | 4 | |
5 | -from xsd2tkform.core.element import Element | |
6 | -from xsd2tkform.core.annotation import Annotation | |
7 | -from xsd2tkform.core.restriction import Restriction | |
8 | -from xsd2tkform.core.list import List | |
9 | -from xsd2tkform.core.utils import get_annotation, get_restriction, get_list, get_sequence | |
5 | +from .element import Element, get_attributes | |
6 | +from .annotation import Annotation | |
7 | +from .restriction import Restriction | |
8 | +from .list import List | |
9 | +from .utils import get_annotation, get_restriction, get_list, get_sequence, get_extension | |
10 | 10 | |
11 | 11 | class XSDType: |
12 | 12 | def __init__(self, name=None, annotation=None): |
13 | 13 | self.name = name |
14 | 14 | self.annotation = annotation |
15 | + def __eq__(self, e): | |
16 | + if not isinstance(e, XSDType): | |
17 | + return False | |
18 | + return self.name==e.name and self.annotation==e.annotation | |
15 | 19 | def __str__(self): |
16 | 20 | return "XSDType(name={})".format(self.name) |
17 | 21 | |
18 | 22 | class SimpleType(XSDType): |
19 | - def __init__(self, name=None, restriction=None, item_list=None, *args, **kwargs): | |
23 | + def __init__(self, name=None, restriction=None, item_list=None, attributes=[], *args, **kwargs): | |
20 | 24 | XSDType.__init__(self, name=name, *args, **kwargs) |
21 | 25 | self.restriction=restriction |
22 | 26 | self.item_list = item_list |
27 | + self.attributes=attributes | |
28 | + def __eq__(self, e): | |
29 | + if not isinstance(e, SimpleType): | |
30 | + return False | |
31 | + return super().__eq__(e) and self.restriction==e.restriction and self.item_list==e.item_list and self.attributes==e.attributes | |
23 | 32 | def __str__(self): |
24 | 33 | att=["name={})".format(self.name)] |
25 | 34 | if self.restriction is not None: |
26 | 35 | att.append("\n\trestriction={}".format(str(self.restriction))) |
27 | 36 | if self.item_list is not None: |
28 | 37 | att.append("\n\tlist={}".format(self.item_list)) |
38 | + if len(self.attributes): | |
39 | + att.append("\n\tattributes={}".format(self.attributes)) | |
29 | 40 | a = "SimpleType({}".format(", ".join(att)) |
30 | 41 | return a |
31 | 42 | |
32 | 43 | class ComplexType(XSDType): |
33 | - def __init__(self, name=None, sequence=None, *args, **kwargs): | |
44 | + def __init__(self, name=None, sequence=None, attributes=[], extension=None, *args, **kwargs): | |
34 | 45 | XSDType.__init__(self, name=name, *args, **kwargs) |
35 | 46 | self.sequence=sequence |
47 | + self.attributes=attributes | |
48 | + self.extension=extension | |
49 | + def __eq__(self, e): | |
50 | + if not isinstance(e, ComplexType): | |
51 | + return False | |
52 | + return super().__eq__(e) and self.sequence==e.sequence | |
36 | 53 | def __str__(self, tab_n=0): |
37 | 54 | r = "ComplexType(name={})".format(self.name) |
38 | 55 | if len(self.sequence): |
39 | 56 | r+="\n"+self.sequence.__str__(tab_n=tab_n+1) |
57 | + if len(self.attributes): | |
58 | + r+="\nAttributes {}".format(self.attributes) | |
40 | 59 | return r |
41 | 60 | |
42 | 61 | class Group(XSDType): |
43 | 62 | def __init__(self, name=None, sequence=None, *args, **kwargs): |
44 | 63 | self.name = name |
45 | 64 | self.sequence=sequence |
65 | + def __eq__(self, e): | |
66 | + return super().__eq__(e) and self.name==e.name and self.sequence==e.sequence | |
46 | 67 | def __str__(self, tab_n=0): |
47 | 68 | r="Group (name={})".format(self.name) |
48 | 69 | if len(self.sequence): |
... | ... | @@ -53,19 +74,31 @@ class Group(XSDType): |
53 | 74 | def from_element(element): |
54 | 75 | """Parse a XML element to XSDType object |
55 | 76 | """ |
77 | + if "name" not in element.attrib: | |
78 | + return | |
56 | 79 | name = element.attrib["name"] |
57 | 80 | annotation = get_annotation(element) |
81 | + attributes = get_attributes(element) | |
58 | 82 | if element.tag.endswith("simpleType"): |
59 | 83 | restriction = get_restriction(element) |
60 | 84 | l= get_list(element) |
61 | - return SimpleType(name=name, annotation=annotation, restriction=restriction, item_list=l) | |
85 | + return SimpleType(name=name, annotation=annotation, restriction=restriction, item_list=l, attributes=attributes) | |
62 | 86 | elif element.tag.endswith("complexType"): |
63 | 87 | sequence = get_sequence(element) |
64 | - return ComplexType(name, annotation=annotation, sequence=sequence) | |
88 | + extension = get_extension(element) | |
89 | + if extension is None: | |
90 | + return ComplexType(name, annotation=annotation, sequence=sequence, attributes=attributes) | |
91 | + from .sequence import Sequence | |
92 | + if isinstance(extension[1], Sequence): | |
93 | + return ComplexType(name, annotation=annotation, sequence=sequence, attributes=attributes, extension=extension) | |
94 | + if isinstance(extension[1], list): | |
95 | + r=SimpleType(name, type=extensions[0], attributes=extension[1]) | |
96 | + return r | |
65 | 97 | elif element.tag.endswith("group"): |
66 | 98 | sequence = get_sequence(element) |
67 | 99 | return Group(name, annotation=annotation, sequence=sequence) |
68 | 100 | elif element.tag.endswith("element"): |
69 | - return Element.from_element(element) | |
101 | + e= Element.from_element(element) | |
102 | + return e | |
70 | 103 | else: |
71 | 104 | return None | ... | ... |
xsd2tkform/core/utils.py
1 | -from xsd2tkform.core.annotation import Annotation | |
2 | -from xsd2tkform.core.restriction import Restriction | |
3 | -from xsd2tkform.core.list import List | |
4 | -from xsd2tkform.core.sequence import Sequence | |
5 | -from xsd2tkform.core.choice import Choice | |
6 | -from xsd2tkform.core.element import Element | |
1 | +from lxml import etree | |
2 | + | |
3 | +from .annotation import Annotation | |
4 | +from .restriction import Restriction | |
5 | +from .list import List | |
6 | +from .sequence import Sequence | |
7 | +from .choice import Choice | |
8 | +from .element import Element, AnyElement | |
7 | 9 | |
8 | 10 | def get_annotation(element): |
9 | 11 | """Get annotations from a type definition element |
... | ... | @@ -39,6 +41,8 @@ def get_list(element): |
39 | 41 | def get_sequence(element): |
40 | 42 | sequence = [] |
41 | 43 | for child in element: |
44 | + if isinstance(child, etree._Comment): | |
45 | + continue | |
42 | 46 | if child.tag.endswith("sequence"): |
43 | 47 | # children should be elements of choices |
44 | 48 | for gchild in child: |
... | ... | @@ -46,8 +50,28 @@ def get_sequence(element): |
46 | 50 | sequence.append(Element.from_element(gchild)) |
47 | 51 | elif gchild.tag.endswith("choice"): |
48 | 52 | sequence.append(Choice.from_element(gchild)) |
53 | + elif gchild.tag.endswith("any"): | |
54 | + sequence.append(AnyElement(**dict(gchild.attrib))) | |
49 | 55 | else: |
50 | 56 | pass |
51 | 57 | return Sequence(sequence) |
52 | 58 | |
59 | +def get_extension(element): | |
60 | + from .element import get_attributes | |
61 | + for child in element: | |
62 | + if isinstance(child, etree._Comment): | |
63 | + continue | |
64 | + if child.tag.endswith("}complexContent"): | |
65 | + # extension | |
66 | + if child[0].tag.endswith("}extension"): | |
67 | + base_type = child[0].attrib["base"] | |
68 | + sequence = get_sequence(child[0]) | |
69 | + return (base_type, sequence) | |
70 | + if child.tag.endswith("}simpleContent"): | |
71 | + # extension | |
72 | + if child[0].tag.endswith("}extension"): | |
73 | + base_type = child[0].attrib["base"].split(":")[-1] | |
74 | + attributes = get_attributes(child[0]) | |
75 | + return (base_type, attributes) | |
53 | 76 | |
77 | + return None | ... | ... |
xsd2tkform/factory.py
... | ... | @@ -2,25 +2,41 @@ |
2 | 2 | """ |
3 | 3 | import tkinter as tk |
4 | 4 | |
5 | -from xsd2tkform.form import XSDForm | |
6 | -from xsd2tkform.core.parser import XSDParser | |
7 | -from xsd2tkform.core.type import SimpleType, ComplexType | |
5 | +from .form import XSDForm | |
6 | +from .core.parser import XSDParser | |
7 | +from .core.type import SimpleType, ComplexType | |
8 | 8 | |
9 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | |
10 | -from xsd2tkform.ui.complextype import XSDComplexTypeFrame | |
11 | -from xsd2tkform.ui.scrollframe import ScrollFrame | |
9 | +from .ui.simpletype import XSDSimpleTypeFrame | |
10 | +from .ui.complextype import XSDComplexTypeFrame | |
11 | +from .ui.scrollframe import ScrollFrame | |
12 | 12 | class XSDFormFactory: |
13 | - def __init__(self, parser=None): | |
13 | + def __init__(self, parser=None, widget_config={}): | |
14 | + self.widget_config = widget_config | |
14 | 15 | self.parser = parser |
15 | - | |
16 | - def get_form(self, typename, parent): | |
16 | + def get_schema_form(self, parent): | |
17 | + # get form for schema | |
18 | + schema = self.parser.schemas[0] | |
19 | + root_frame = tk.Frame(parent) | |
20 | + for item in schema.root_items: | |
21 | + item_form = self.get_form(item.name, parent=root_frame) | |
22 | + item_form.pack(side=tk.TOP, fill=tk.X, expand=1) | |
23 | + return root_frame | |
24 | + def get_form(self, typename, parent, filename=None): | |
17 | 25 | if not typename in self.parser.schemas[0]: |
18 | - raise Exception("Type {} not found") | |
26 | + raise Exception("Type '{}' not found".format(typename)) | |
19 | 27 | typedef = self.parser.get(typename) |
20 | 28 | if isinstance(typedef, SimpleType): |
21 | - return XSDSimpleTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False) | |
29 | + return XSDSimpleTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config) | |
22 | 30 | if isinstance(typedef, ComplexType): |
23 | - return XSDComplexTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False) | |
31 | + return XSDComplexTypeFrame(typename, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config) | |
32 | + from .core.element import Element | |
33 | + if isinstance(typedef, Element): | |
34 | + if typedef.typedef is None: | |
35 | + return self.get_form(typedef.type, parent=parent, filename=filename) | |
36 | + if isinstance(typedef.typedef, ComplexType): | |
37 | + return XSDComplexTypeFrame(typedef.name, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config, typedef=typedef) | |
38 | + if isinstance(typedef.typedef, SimpleType): | |
39 | + return XSDSimpleTypeFrame(typedef.name, schema=self.parser.schemas[0], parent=parent, delete_button=False, filename=filename, widget_config=self.widget_config, typedef=typedef.typedef) | |
24 | 40 | |
25 | 41 | |
26 | 42 | # example app |
... | ... | @@ -42,28 +58,46 @@ if __name__=="__main__": |
42 | 58 | parser = XSDParser(xsd_filename) |
43 | 59 | # list complex types |
44 | 60 | typenames = [k for k in parser.schemas[0].complex_types] |
61 | + print("Simple types : \n", [k for k in parser.schemas[0].simple_types]) | |
62 | + print("Complex types : \n", [k for k in parser.schemas[0].complex_types]) | |
63 | + print("Elements : \n", [e.type for e in parser.schemas[0].items]) | |
64 | + print("Root elements : \n", parser.schemas[0].root_items) | |
45 | 65 | print("Creating form for type : {}".format(typenames[0])) |
46 | - print(typenames) | |
47 | - | |
66 | + print() | |
67 | + for e in parser.schemas[0].items: | |
68 | + if e.type=="get": | |
69 | + print(e) | |
70 | + print() | |
71 | + print(parser.schemas[0].complex_types["GetterType"]) | |
72 | + #exit() | |
48 | 73 | |
49 | 74 | # form factory |
50 | 75 | factory=XSDFormFactory(parser) |
51 | 76 | |
77 | + | |
52 | 78 | app = ExampleApp() |
53 | 79 | mainframe = tk.Frame(app) |
54 | 80 | scrollframe = ScrollFrame(mainframe) # add a new scrollable frame. |
55 | 81 | |
56 | 82 | # populate window with all simpleTypes found in the XSD schema |
83 | + #form_frame = factory.get_schema_form(parent = scrollframe.viewPort) | |
84 | + #form_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | |
85 | + | |
86 | + tk.Label(scrollframe.viewPort, text="FILLED").pack(side=tk.TOP, fill=tk.X, expand=1) | |
87 | + | |
88 | + filled_form = factory.get_form("param", parent=scrollframe.viewPort, filename="ace_r.xml") | |
89 | + filled_form.pack(side=tk.TOP, fill=tk.X, expand=1) | |
57 | 90 | #for t in typenames: |
58 | - form_frame = factory.get_form(typename = typenames[0], parent = scrollframe.viewPort) | |
59 | - form_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | |
91 | + # #form_frame = factory.get_form(typename = t, parent = scrollframe.viewPort) | |
92 | + # form_frame = factory.get_schema_form(parent = scrollframe.viewPort) | |
93 | + # form_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | |
60 | 94 | |
61 | 95 | # add a submit and cancel button |
62 | 96 | def save_form(): |
63 | - tree = etree.ElementTree(form_frame.get_content()) | |
97 | + tree = etree.ElementTree(filled_form.get_content()) | |
64 | 98 | from tkinter.filedialog import askopenfilename |
65 | 99 | filename = askopenfilename() |
66 | - tree.write(filename, pretty_print=True) | |
100 | + tree.write(filename, pretty_print=True, xml_declaration=True, encoding="utf-8") | |
67 | 101 | submit_button = tk.Button(scrollframe.viewPort, text="Submit", command=save_form) |
68 | 102 | cancel_button = tk.Button(scrollframe.viewPort, text="Cancel", command=app.quit) |
69 | 103 | submit_button.pack(side=tk.LEFT, fill=tk.X, expand=1) | ... | ... |
... | ... | @@ -0,0 +1,34 @@ |
1 | +"""Text input widget with size adaptive height | |
2 | +""" | |
3 | + | |
4 | +import tkinter as tk | |
5 | +from tkinter import ttk | |
6 | + | |
7 | +class AdaptiveHeightText(tk.Text): | |
8 | + def __init__(self, parent, height=None, width=None, *args, **kwargs): | |
9 | + tk.Text.__init__(self, parent, height=height, width=width, *args, **kwargs) | |
10 | + self.bind("<KeyRelease>", self.update_height) | |
11 | + def update_height(self, event=None): | |
12 | + text = self.get("1.0", tk.END) | |
13 | + n_lines = len(text.split("\n"))+1 | |
14 | + self.configure(height=n_lines) | |
15 | + def insert(self, *args, **kwargs): | |
16 | + super().insert(*args, **kwargs) | |
17 | + self.update_height() | |
18 | + def delete(self, *args, **kwargs): | |
19 | + super().delete(*args, **kwargs) | |
20 | + self.update_height() | |
21 | + | |
22 | +if __name__=="__main__": | |
23 | + root = tk.Tk() | |
24 | + mainframe = tk.Frame(root) | |
25 | + | |
26 | + text=AdaptiveHeightText(mainframe, height=2) | |
27 | + text.pack(side=tk.TOP, fill=tk.X, expand=1) | |
28 | + | |
29 | + text.insert("1.0", "a\nb\nc\n") | |
30 | + | |
31 | + mainframe.pack(side=tk.TOP, fill=tk.BOTH, expand=1) | |
32 | + | |
33 | + root.mainloop() | |
34 | + | ... | ... |
... | ... | @@ -0,0 +1,126 @@ |
1 | +"""Frame for containing list of attributes | |
2 | +""" | |
3 | + | |
4 | +import tkinter as tk | |
5 | +from tkinter import ttk | |
6 | + | |
7 | +from .simpletype import XSDAttributeFrame | |
8 | +from .entrywidgets import FloatEntry, IntEntry, BoolEntry | |
9 | +from amda_xml_manager import collapse_image_file, expand_image_file | |
10 | + | |
11 | +class AttributeButtonFrame(tk.Frame): | |
12 | + def __init__(self, parent): | |
13 | + tk.Frame.__init__(self, parent) | |
14 | + tk.Label(self, text="Add :", anchor=tk.W).grid(row=0, sticky=tk.W) | |
15 | + def count_visible_children(self): | |
16 | + c=0 | |
17 | + for child in self.winfo_children(): | |
18 | + ginfo=child.grid_info() | |
19 | + if "row" in ginfo: | |
20 | + c+=1 | |
21 | + return c | |
22 | + | |
23 | +class AttributeFrame(tk.Frame): | |
24 | + def __init__(self, parent, attributes=[]): | |
25 | + tk.Frame.__init__(self, parent, highlightbackground="black", highlightthickness=1) | |
26 | + | |
27 | + #tk.Label(self, text="Attributes").grid(row=0, columnspan=2, sticky=tk.W) | |
28 | + self.grid_columnconfigure(0, weight=0) | |
29 | + self.grid_columnconfigure(1, weight=1) | |
30 | + | |
31 | + #images | |
32 | + self.collapse_img = tk.PhotoImage(file=collapse_image_file) | |
33 | + self.expand_img = tk.PhotoImage(file=expand_image_file) | |
34 | + | |
35 | + # add header | |
36 | + self.get_header_frame() | |
37 | + | |
38 | + # content frame : content that is hidden when collapsed | |
39 | + self.content_frame = tk.Frame(self) | |
40 | + self.content_frame.grid_columnconfigure(0, weight=0) | |
41 | + self.content_frame.grid_columnconfigure(1, weight=1) | |
42 | + | |
43 | + | |
44 | + self.attributes=attributes | |
45 | + # attribute counts | |
46 | + self.counts={a.name:0 for a in self.attributes} | |
47 | + | |
48 | + # button container | |
49 | + self.button_frame = AttributeButtonFrame(self.content_frame) | |
50 | + | |
51 | + self.attribute_inputs=[XSDAttributeFrame(a, parent=self.content_frame,\ | |
52 | + on_delete=lambda t=a.name: self.delete_attribute(t),\ | |
53 | + on_add=self.update_grid) for a in self.attributes] | |
54 | + | |
55 | + self.add_attribute_widgets() | |
56 | + | |
57 | + self.content_frame.grid(row=1, columnspan=2, sticky=tk.EW) | |
58 | + def set_attribute_content(self, name, value): | |
59 | + if "}" in name: | |
60 | + name = name.split("}")[-1] | |
61 | + print("attrib Setting {} : {}".format(name, value)) | |
62 | + for i in range(len(self.attributes)): | |
63 | + if name==self.attributes[i].name: | |
64 | + A=self.attribute_inputs[i] | |
65 | + A.set(value) | |
66 | + | |
67 | + ginfo=self.attribute_inputs[i].grid_info() | |
68 | + if not "row" in ginfo: | |
69 | + self.attribute_inputs[i].grid() | |
70 | + def get_attribute_values(self): | |
71 | + r={} | |
72 | + for inp in self.attribute_inputs: | |
73 | + if inp.is_visible(): | |
74 | + v=inp.get() | |
75 | + if len(v): | |
76 | + r[inp.name]=v | |
77 | + return r | |
78 | + def add_attribute_widgets(self): | |
79 | + # add button for each non required attributes | |
80 | + row,col=0,1 | |
81 | + for att in self.attribute_inputs: | |
82 | + att.grid(row=row, column=1, sticky=tk.EW) | |
83 | + l=att.get_label(self.content_frame) | |
84 | + l.grid(row=row, column=0, sticky=tk.W) | |
85 | + if not att.mandatory: | |
86 | + att.grid_remove() | |
87 | + l.grid_remove() | |
88 | + | |
89 | + b=att.get_add_button(parent=self.button_frame) | |
90 | + b.grid(row=0, column=col) | |
91 | + row+=1 | |
92 | + col+=1 | |
93 | + | |
94 | + def get_header_frame(self): | |
95 | + self.header_frame=tk.Frame(self) | |
96 | + self.header_label=tk.Label(self.header_frame, text="Attributes", anchor=tk.W) | |
97 | + self.header_label.pack(side=tk.LEFT, fill=tk.X, expand=1) | |
98 | + self.collapse_button=tk.Button(self.header_frame, image=self.collapse_img, command=self.collapse) | |
99 | + self.collapse_button.pack(side=tk.RIGHT, expand=0) | |
100 | + | |
101 | + self.header_frame.grid(row=0, columnspan=2, sticky=tk.EW) | |
102 | + def collapse(self): | |
103 | + self.content_frame.grid_remove() | |
104 | + #for c in self.winfo_children(): | |
105 | + # c.grid_remove() | |
106 | + #self.header_frame.grid() | |
107 | + self.collapse_button.configure(image=self.expand_img, command=self.expand) | |
108 | + def expand(self): | |
109 | + self.content_frame.grid() | |
110 | + self.collapse_button.configure(image=self.collapse_img, command=self.collapse) | |
111 | + def delete_attribute(self, name): | |
112 | + self.counts[name]-=1 | |
113 | + #self.update_grid() | |
114 | + def update_grid(self): | |
115 | + row=1 | |
116 | + for f in self.attribute_inputs: | |
117 | + row+=1 | |
118 | + ttk.Separator(self, orient="horizontal").grid(row=row, columnspan=2, sticky=tk.EW) | |
119 | + row+=1 | |
120 | + if self.button_frame.count_visible_children()>1: | |
121 | + self.button_frame.grid(row=row, columnspan=2, sticky=tk.EW) | |
122 | + else: | |
123 | + self.button_frame.grid_remove() | |
124 | + | |
125 | + def delete_attribute(self, name): | |
126 | + self.update_grid() | ... | ... |
... | ... | @@ -0,0 +1,152 @@ |
1 | +""" | |
2 | +tkentrycomplete.py | |
3 | + | |
4 | +A Tkinter widget that features autocompletion. | |
5 | + | |
6 | +Created by Mitja Martini on 2008-11-29. | |
7 | +Updated by Russell Adams, 2011/01/24 to support Python 3 and Combobox. | |
8 | +Updated by Dominic Kexel to use Tkinter and ttk instead of tkinter and tkinter.ttk | |
9 | + Licensed same as original (not specified?), or public domain, whichever is less restrictive. | |
10 | + | |
11 | +""" | |
12 | +import sys | |
13 | +import os | |
14 | +import tkinter | |
15 | +from tkinter import ttk | |
16 | + | |
17 | +__version__ = "1.1" | |
18 | + | |
19 | +# I may have broken the unicode... | |
20 | +tkinter_umlauts=['odiaeresis', 'adiaeresis', 'udiaeresis', 'Odiaeresis', 'Adiaeresis', 'Udiaeresis', 'ssharp'] | |
21 | + | |
22 | +class AutocompleteEntry(tkinter.Entry): | |
23 | + """ | |
24 | + Subclass of Tkinter.Entry that features autocompletion. | |
25 | + | |
26 | + To enable autocompletion use set_completion_list(list) to define | |
27 | + a list of possible strings to hit. | |
28 | + To cycle through hits use down and up arrow keys. | |
29 | + """ | |
30 | + def set_completion_list(self, completion_list): | |
31 | + self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list | |
32 | + self._hits = [] | |
33 | + self._hit_index = 0 | |
34 | + self.position = 0 | |
35 | + self.bind('<KeyRelease>', self.handle_keyrelease) | |
36 | + | |
37 | + def autocomplete(self, delta=0): | |
38 | + """autocomplete the Entry, delta may be 0/1/-1 to cycle through possible hits""" | |
39 | + if delta: # need to delete selection otherwise we would fix the current position | |
40 | + self.delete(self.position, tkinter.END) | |
41 | + else: # set position to end so selection starts where textentry ended | |
42 | + self.position = len(self.get()) | |
43 | + # collect hits | |
44 | + _hits = [] | |
45 | + for element in self._completion_list: | |
46 | + if element.lower().startswith(self.get().lower()): # Match case-insensitively | |
47 | + _hits.append(element) | |
48 | + # if we have a new hit list, keep this in mind | |
49 | + if _hits != self._hits: | |
50 | + self._hit_index = 0 | |
51 | + self._hits=_hits | |
52 | + # only allow cycling if we are in a known hit list | |
53 | + if _hits == self._hits and self._hits: | |
54 | + self._hit_index = (self._hit_index + delta) % len(self._hits) | |
55 | + # now finally perform the auto completion | |
56 | + if self._hits: | |
57 | + self.delete(0,tkinter.END) | |
58 | + self.insert(0,self._hits[self._hit_index]) | |
59 | + self.select_range(self.position,tkinter.END) | |
60 | + | |
61 | + def handle_keyrelease(self, event): | |
62 | + """event handler for the keyrelease event on this widget""" | |
63 | + if event.keysym == "BackSpace": | |
64 | + self.delete(self.index(tkinter.INSERT), tkinter.END) | |
65 | + self.position = self.index(tkinter.END) | |
66 | + if event.keysym == "Left": | |
67 | + if self.position < self.index(tkinter.END): # delete the selection | |
68 | + self.delete(self.position, tkinter.END) | |
69 | + else: | |
70 | + self.position = self.position-1 # delete one character | |
71 | + self.delete(self.position, tkinter.END) | |
72 | + if event.keysym == "Right": | |
73 | + self.position = self.index(tkinter.END) # go to end (no selection) | |
74 | + if event.keysym == "Down": | |
75 | + self.autocomplete(1) # cycle to next hit | |
76 | + if event.keysym == "Up": | |
77 | + self.autocomplete(-1) # cycle to previous hit | |
78 | + if len(event.keysym) == 1 or event.keysym in tkinter_umlauts: | |
79 | + self.autocomplete() | |
80 | + | |
81 | +class AutocompleteCombobox(ttk.Combobox): | |
82 | + | |
83 | + def set_completion_list(self, completion_list): | |
84 | + """Use our completion list as our drop down selection menu, arrows move through menu.""" | |
85 | + self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list | |
86 | + self._hits = [] | |
87 | + self._hit_index = 0 | |
88 | + self.position = 0 | |
89 | + self.bind('<KeyRelease>', self.handle_keyrelease) | |
90 | + self['values'] = self._completion_list # Setup our popup menu | |
91 | + | |
92 | + def autocomplete(self, delta=0): | |
93 | + """autocomplete the Combobox, delta may be 0/1/-1 to cycle through possible hits""" | |
94 | + if delta: # need to delete selection otherwise we would fix the current position | |
95 | + self.delete(self.position, tkinter.END) | |
96 | + else: # set position to end so selection starts where textentry ended | |
97 | + self.position = len(self.get()) | |
98 | + # collect hits | |
99 | + _hits = [] | |
100 | + for element in self._completion_list: | |
101 | + if element.lower().startswith(self.get().lower()): # Match case insensitively | |
102 | + _hits.append(element) | |
103 | + # if we have a new hit list, keep this in mind | |
104 | + if _hits != self._hits: | |
105 | + self._hit_index = 0 | |
106 | + self._hits=_hits | |
107 | + # only allow cycling if we are in a known hit list | |
108 | + if _hits == self._hits and self._hits: | |
109 | + self._hit_index = (self._hit_index + delta) % len(self._hits) | |
110 | + # now finally perform the auto completion | |
111 | + if self._hits: | |
112 | + self.delete(0,tkinter.END) | |
113 | + self.insert(0,self._hits[self._hit_index]) | |
114 | + self.select_range(self.position,tkinter.END) | |
115 | + | |
116 | + def handle_keyrelease(self, event): | |
117 | + """event handler for the keyrelease event on this widget""" | |
118 | + if event.keysym == "BackSpace": | |
119 | + self.delete(self.index(tkinter.INSERT), tkinter.END) | |
120 | + self.position = self.index(tkinter.END) | |
121 | + if event.keysym == "Left": | |
122 | + if self.position < self.index(tkinter.END): # delete the selection | |
123 | + self.delete(self.position, tkinter.END) | |
124 | + else: | |
125 | + self.position = self.position-1 # delete one character | |
126 | + self.delete(self.position, tkinter.END) | |
127 | + if event.keysym == "Right": | |
128 | + self.position = self.index(tkinter.END) # go to end (no selection) | |
129 | + if len(event.keysym) == 1: | |
130 | + self.autocomplete() | |
131 | + # No need for up/down, we'll jump to the popup | |
132 | + # list at the position of the autocompletion | |
133 | + | |
134 | +def test(test_list): | |
135 | + """Run a mini application to test the AutocompleteEntry Widget.""" | |
136 | + root = tkinter.Tk(className=' AutocompleteEntry demo') | |
137 | + entry = AutocompleteEntry(root) | |
138 | + entry.set_completion_list(test_list) | |
139 | + entry.pack() | |
140 | + entry.focus_set() | |
141 | + combo = AutocompleteCombobox(root) | |
142 | + combo.set_completion_list(test_list) | |
143 | + combo.pack() | |
144 | + combo.focus_set() | |
145 | + # I used a tiling WM with no controls, added a shortcut to quit | |
146 | + root.bind('<Control-Q>', lambda event=None: root.destroy()) | |
147 | + root.bind('<Control-q>', lambda event=None: root.destroy()) | |
148 | + root.mainloop() | |
149 | + | |
150 | +if __name__ == '__main__': | |
151 | + test_list = ('apple', 'banana', 'CranBerry', 'dogwood', 'alpha', 'Acorn', 'Anise' ) | |
152 | + test(test_list) | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +"""Frame for containing optional field buttons | |
2 | +""" | |
3 | +import tkinter as tk | |
4 | + | |
5 | +class ButtonContainer(tk.Frame): | |
6 | + def __init__(self, parent, *args, **kwargs): | |
7 | + #tk.Frame.__init__(self, parent, highlightbackground="magenta", highlightthickness=3, *args, **kwargs) | |
8 | + tk.Frame.__init__(self, parent, highlightbackground=None, highlightthickness=None, *args, **kwargs) | |
9 | + | |
10 | + self.buttons = [] | |
11 | + | |
12 | + self.bind("<Configure>", self.update_layout) | |
13 | + def add_button(self, button): | |
14 | + self.buttons.append(button) | |
15 | + def update_layout(self, event): | |
16 | + pass | |
17 | + #print("Update layout {}".format(event)) | |
18 | + | |
19 | + | |
20 | + | ... | ... |
xsd2tkform/ui/choice.py
1 | 1 | import tkinter as tk |
2 | 2 | from tkinter import ttk |
3 | 3 | |
4 | -from xsd2tkform.core.type import SimpleType, ComplexType | |
4 | +from ..core.type import SimpleType, ComplexType | |
5 | 5 | |
6 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | |
7 | -from xsd2tkform.ui.complextype import XSDComplexTypeFrame | |
6 | +from .simpletype import XSDSimpleTypeFrame | |
7 | +from .complextype import XSDComplexTypeFrame | |
8 | 8 | |
9 | -from xsd2tkform.ui.field import XSDInputField | |
9 | +from .field import XSDInputField | |
10 | 10 | |
11 | 11 | class ChoiceInput(XSDInputField): |
12 | 12 | def __init__(self, parent, choice, schema, *args, **kwargs): |
13 | - tk.Frame.__init__(self, parent, *args, **kwargs) | |
13 | + XSDInputField.__init__(self, parent, highlightbackground="green", highlightthickness=2, *args, **kwargs) | |
14 | 14 | self.schema=schema |
15 | 15 | # get the choice types |
16 | 16 | self.choice_inputs=[] |
17 | 17 | # get the list of choice type |
18 | - choice_types = [t.type for t in choice.elements] | |
18 | + self.choice_types = [t.type for t in choice.elements] | |
19 | + self.choice_names = [t.name for t in choice.elements] | |
19 | 20 | # get occurence bounds for choices |
20 | 21 | #choice_occ_bounds = self.get_element_occurence_limits(item) |
21 | 22 | choice_occ_bounds = choice.min_occurs, choice.max_occurs |
... | ... | @@ -23,7 +24,7 @@ class ChoiceInput(XSDInputField): |
23 | 24 | self.field_counts={} |
24 | 25 | self.field_min_counts={} |
25 | 26 | self.field_max_counts={} |
26 | - for _type in choice_types: | |
27 | + for _type in self.choice_types: | |
27 | 28 | # TODO : check if bounds are correct, if choice type is present somewhere else in the type definition then the bounds are overwritten here, which can be bad |
28 | 29 | if _type in self.field_min_counts: |
29 | 30 | print("Not good if you see this") |
... | ... | @@ -38,9 +39,13 @@ class ChoiceInput(XSDInputField): |
38 | 39 | choice_select_frame = tk.Frame(self) |
39 | 40 | |
40 | 41 | # add a choice selector : combobox and button |
41 | - self.choice_type = ttk.Combobox(choice_select_frame, values=choice_types, state="readonly") | |
42 | + #self.choice_type = ttk.Combobox(choice_select_frame, values=self.choice_types, state="readonly") | |
43 | + self.choice_type = ttk.Combobox(choice_select_frame, values=self.choice_names, state="readonly") | |
44 | + | |
42 | 45 | self.choice_type.current(0) |
43 | - choice_add = tk.Button(choice_select_frame, text="Add", command=lambda: self.add_field()) | |
46 | + from amda_xml_manager import add_image_file | |
47 | + self.add_img = tk.PhotoImage(file=add_image_file) | |
48 | + choice_add = tk.Button(choice_select_frame, image=self.add_img, command=lambda: self.add_field()) | |
44 | 49 | |
45 | 50 | self.choice_type.pack(side=tk.LEFT, fill=tk.X, expand=1) |
46 | 51 | choice_add.pack(side=tk.RIGHT, expand=0) |
... | ... | @@ -48,16 +53,40 @@ class ChoiceInput(XSDInputField): |
48 | 53 | ##choice_frame.pack(side=tk.TOP, fill=tk.X, expand=1) |
49 | 54 | #sequence_inputs.append(choice_inputs) |
50 | 55 | #return choice_frame |
51 | - def add_field(self): | |
52 | - _type = self.choice_type.get() | |
56 | + def has_type(self, t): | |
57 | + # TODO watch out | |
58 | + if t in self.choice_names: | |
59 | + return True | |
60 | + for _type in self.choice_types: | |
61 | + if t==_type or t==_type.split(":")[-1]: | |
62 | + return True | |
63 | + return False | |
64 | + def set_content(self, content, update_grid=True): | |
65 | + # get the name of the item | |
66 | + ct=content.tag.split("}")[-1] | |
67 | + | |
68 | + for c in self.choice_names: | |
69 | + #for c in self.choice_types: | |
70 | + ctt=c.split(":")[-1] | |
71 | + if ct==ctt: | |
72 | + self.choice_type.set(c) | |
73 | + self.add_field(content) | |
74 | + | |
75 | + #self.add_field(content) | |
76 | + | |
77 | + def add_field(self, content=None): | |
78 | + _name = self.choice_type.get() | |
79 | + #_type = self.choice_type.get() | |
80 | + _type = self.choice_types[self.choice_names.index(_name)] | |
53 | 81 | |
54 | 82 | # add the frame |
55 | - self.add_frame_by_type(_type) | |
56 | - def add_frame_by_type(self, t): | |
83 | + self.add_frame_by_type(_type, content=content) | |
84 | + def add_frame_by_type(self, t, content=None): | |
57 | 85 | # check if the maximum occurences of this field is achieved |
58 | 86 | |
59 | 87 | if self.field_max_counts[t]=="unbounded": |
60 | - print("No limit on {} fields".format(t)) | |
88 | + pass | |
89 | + #print("No limit on {} fields".format(t)) | |
61 | 90 | elif t in self.field_counts: |
62 | 91 | if self.get_field_count_by_type(t)==self.field_max_counts[t]: |
63 | 92 | showerror(title="{} maximum occurences reached".format(t), message="Type {} supports a maximum of {} occurences of type {}".format(self.type, self.field_max_counts[t], t)) |
... | ... | @@ -67,27 +96,38 @@ class ChoiceInput(XSDInputField): |
67 | 96 | |
68 | 97 | self.increment_field_count_by_type(t) |
69 | 98 | |
70 | - f = self.get_frame_by_type(t, parent=self, delete_button=True) | |
99 | + f = self.get_frame_by_type(t, parent=self, delete_button=True, content=content) | |
100 | + f.configure(highlightbackground="blue", highlightthickness=2) | |
71 | 101 | f.pack(side=tk.TOP, fill=tk.X, expand=1) |
72 | - #self.choice_inputs.append(f.winfo_children()[0]) | |
73 | - print("in choice_input append : {}".format(type(f))) | |
74 | 102 | self.choice_inputs.append(f) |
75 | 103 | #if container is not None: |
76 | 104 | # container.append(f.winfo_children()[0]) |
77 | - def get_frame_by_type(self, t, parent=None, delete_button=False): | |
105 | + #self.master.update_grid() | |
106 | + | |
107 | + def get_frame_by_type(self, t, parent=None, delete_button=False, content=None): | |
78 | 108 | if parent is None: |
79 | 109 | parent = self |
80 | 110 | |
81 | 111 | td=self.schema.get(t) |
112 | + nn=self.choice_names[self.choice_types.index(t)] | |
113 | + print("IN CHOICE type={}, name={}".format(t, nn)) | |
82 | 114 | if isinstance(td, SimpleType):# in self.simple_types: |
83 | 115 | return XSDSimpleTypeFrame(t, parent=parent,\ |
116 | + name=nn, | |
84 | 117 | schema=self.schema, |
85 | - delete_button=delete_button) | |
118 | + delete_button=delete_button, | |
119 | + content=content, | |
120 | + widget_config=self.widget_config, | |
121 | + on_delete=lambda x=t: self.delete_field(x)) | |
86 | 122 | elif isinstance(td, ComplexType): |
87 | 123 | return XSDComplexTypeFrame(t, parent=parent,\ |
124 | + name=nn, | |
88 | 125 | schema=self.schema, |
89 | 126 | delete_button=delete_button, |
90 | - collapsable=True) | |
127 | + collapsable=True, | |
128 | + content=content, | |
129 | + widget_config=self.widget_config, | |
130 | + on_delete=lambda x=t: self.delete_field(x)) | |
91 | 131 | else: |
92 | 132 | # TODO : add Group support |
93 | 133 | print("Group support not yet implemented") |
... | ... | @@ -102,20 +142,10 @@ class ChoiceInput(XSDInputField): |
102 | 142 | def decrement_field_count_by_type(self, t): |
103 | 143 | self.field_counts[t]=self.get_field_count_by_type(t)-1 |
104 | 144 | |
105 | - def delete_field(self, t, field, container=None): | |
106 | - field_dims = (field.winfo_width(), field.winfo_height()) | |
107 | - field.destroy() | |
145 | + def delete_field(self, t, field=None, container=None): | |
108 | 146 | self.decrement_field_count_by_type(t) |
109 | - current_scroll_region=self.master.master.bbox("all") | |
110 | - new_scrollregion= (current_scroll_region[0], | |
111 | - current_scroll_region[1], | |
112 | - current_scroll_region[2]-field_dims[0], | |
113 | - current_scroll_region[3]-field_dims[1]) | |
114 | - | |
115 | - # WORKS | |
116 | - self.master.master.master.configure(scrollregion=new_scrollregion) | |
117 | - | |
118 | - | |
147 | + self.choice_inputs=[w for w in self.choice_inputs if w.winfo_exists()] | |
148 | + | |
119 | 149 | def add_content(self, root, content): |
120 | 150 | if isinstance(content, list): |
121 | 151 | for item in content: |
... | ... | @@ -123,5 +153,6 @@ class ChoiceInput(XSDInputField): |
123 | 153 | else: |
124 | 154 | if content is not None: |
125 | 155 | root.append(content) |
126 | - def get_content(self): | |
127 | - return [i.get_content(i) for i in self.choice_inputs] | |
156 | + def get_content(self, nsmap=None, qname_attrs=None): | |
157 | + return [i.get_content(nsmap=nsmap, qname_attrs=qname_attrs) for i in self.choice_inputs] | |
158 | + #return [i.get_content(i, nsmap=nsmap, qname_attrs=qname_attrs) for i in self.choice_inputs] | ... | ... |
xsd2tkform/ui/complextype.py
... | ... | @@ -4,173 +4,488 @@ from tkinter.messagebox import showerror |
4 | 4 | |
5 | 5 | from lxml import etree |
6 | 6 | |
7 | -from xsd2tkform.core.type import SimpleType, ComplexType | |
8 | -from xsd2tkform.core.element import Element | |
9 | -from xsd2tkform.core.choice import Choice | |
7 | +from ..core.type import SimpleType, ComplexType | |
8 | +from ..core.element import Element, AnyElement | |
9 | +from ..core.choice import Choice | |
10 | 10 | |
11 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | |
12 | -from xsd2tkform.ui.tooltip import ToolTip | |
11 | +from .simpletype import XSDSimpleTypeFrame, XSDAttributeFrame, XSDAnyInputFrame | |
12 | +from .tooltip import ToolTip | |
13 | 13 | |
14 | 14 | from .field import XSDInputField |
15 | 15 | |
16 | +from amda_xml_manager import add_image_file, delete_image_file, collapse_image_file, expand_image_file | |
17 | + | |
18 | +from .attributeframe import AttributeFrame | |
19 | + | |
16 | 20 | class XSDComplexTypeFrame(XSDInputField): |
17 | - def __init__(self, typename, parent=None, schema=None, delete_button=False, collapsable=False, *args, **kwargs): | |
21 | + def __init__(self, typename, parent=None, schema=None, delete_button=False, collapsable=False, filename=None, content=None, name=None, widget_config={}, typedef=None, *args, **kwargs): | |
18 | 22 | XSDInputField.__init__(self, parent, borderwidth=1,\ |
19 | 23 | highlightbackground="black",\ |
20 | - highlightthickness=2,\ | |
24 | + highlightthickness=1,\ | |
25 | + widget_config=widget_config,\ | |
21 | 26 | *args, **kwargs) |
27 | + # flag indicating that the fields content has been set | |
28 | + self.content_has_been_set = False | |
29 | + | |
30 | + self.grid_columnconfigure(0, weight=1) | |
31 | + #self.grid_columnconfigure(1, weight=1) | |
32 | + | |
33 | + # collapsed state | |
34 | + self.collapsed = False | |
35 | + | |
22 | 36 | # store type of the current field |
37 | + self.name = name | |
23 | 38 | self.type = typename |
24 | 39 | self.schema = schema |
25 | - | |
40 | + | |
41 | + # keep reference to the label | |
42 | + self.label=None | |
26 | 43 | # store the number of fields of each type, and min and max |
27 | 44 | self.field_counts={} |
28 | 45 | self.field_min_counts={} |
29 | 46 | self.field_max_counts={} |
30 | 47 | |
31 | - # storage for optional frame | |
32 | - self.subframes=[] | |
33 | - | |
34 | 48 | # frame for containing label, collapse and delete buttons |
35 | - lf = tk.Frame(self) | |
36 | - self.label=tk.Label(lf, text=self.sanitize_type(typename), font="bold") | |
37 | - self.label.pack(side=tk.LEFT, fill=tk.X, expand=1) | |
38 | - | |
49 | + self.header_frame = self.get_header_frame() | |
39 | 50 | |
51 | + self.delete_img = None | |
40 | 52 | if delete_button: |
41 | - tk.Button(lf, text="X", command=self.delete).pack(side=tk.RIGHT) | |
53 | + #tk.Button(self.header_frame, text="X", command=self.delete).pack(side=tk.RIGHT) | |
54 | + self.delete_img = tk.PhotoImage(file=delete_image_file) | |
55 | + tk.Button(self.header_frame, image=self.delete_img, command=self.delete).pack(side=tk.RIGHT) | |
56 | + | |
57 | + | |
42 | 58 | # add a collapse button |
59 | + self.expand_img = None | |
60 | + self.collapse_img = None | |
43 | 61 | self.collapse_button=None |
44 | 62 | if collapsable: |
45 | - self.collapse_button = tk.Button(lf, text="_", command=self.collapse) | |
63 | + self.expand_img = tk.PhotoImage(file=expand_image_file) | |
64 | + self.collapse_img = tk.PhotoImage(file=collapse_image_file) | |
65 | + #self.collapse_button = tk.Button(self.header_frame, text="_", command=self.collapse) | |
66 | + self.collapse_button = tk.Button(self.header_frame, image=self.collapse_img, command=self.collapse) | |
46 | 67 | self.collapse_button.pack(side=tk.RIGHT) |
47 | 68 | |
48 | - | |
49 | - lf.pack(side=tk.TOP, fill=tk.X, expand=1) | |
50 | 69 | |
51 | 70 | self.inputs=[] # list of inputs used for constructing the tree |
52 | 71 | |
72 | + # option button frame | |
73 | + #self.option_button_frame = tk.Frame(self) | |
74 | + from .buttoncontainer import ButtonContainer | |
75 | + self.option_button_frame2 = ButtonContainer(self) | |
76 | + | |
77 | + | |
78 | + | |
79 | + #tk.Label(self.option_button_frame, text="Add :").grid(row=0, column=0) | |
80 | + tk.Label(self.option_button_frame2, text="Add :").grid(row=0, column=0) | |
81 | + | |
82 | + | |
53 | 83 | # get type definition |
54 | - self.typedef = schema.get(typename) | |
84 | + if typedef is None: | |
85 | + self.typedef = schema.get(typename) | |
86 | + else: | |
87 | + self.typedef = typedef | |
88 | + #from ..core.element import Element | |
89 | + if isinstance(typedef, Element): | |
90 | + if typedef.typedef is not None: | |
91 | + self.typedef = typedef.typedef | |
55 | 92 | if self.typedef is None: |
56 | 93 | raise Exception("Typename {} not found".format(typename)) |
94 | + | |
95 | + # attribute container | |
96 | + if len(self.typedef.attributes): | |
97 | + self.attribute_frame = AttributeFrame(self, self.typedef.attributes) | |
98 | + self.attribute_frame.collapse() | |
99 | + else: | |
100 | + self.attribute_frame = None | |
101 | + # field container | |
102 | + #self.field_frame = tk.Frame(self, highlightbackground="red", highlightthickness=2) | |
103 | + self.field_frame = tk.Frame(self, highlightbackground=None, highlightthickness=None) | |
104 | + self.field_frame.grid_columnconfigure(0, weight=0) | |
105 | + self.field_frame.grid_columnconfigure(1, weight=1) | |
106 | + | |
107 | + | |
108 | + | |
57 | 109 | self.set_tooltip() |
58 | - sequence_inputs=[] | |
110 | + | |
111 | + # grid content | |
112 | + #c=0 | |
113 | + #self.next_row = 1 | |
114 | + option_button_column=1 | |
115 | + # attributes first | |
116 | + | |
59 | 117 | for item in self.typedef.sequence.items: |
118 | + #print("item type {} {}".format(type(item), type(item.typedef))) | |
60 | 119 | if isinstance(item, Element): |
120 | + if item.ref is not None: | |
121 | + # there can be only ONE | |
122 | + refered_element = self.schema.find_ref(item.ref) | |
123 | + print("Refered element : {}".format(refered_element)) | |
124 | + if refered_element.abstract: | |
125 | + # find substitutions | |
126 | + substitutions = self.schema.find_substitutions(refered_element.name) | |
127 | + print("Substitutions : {}".format(substitutions)) | |
128 | + print("\tnames : {}".format([s.name for s in substitutions])) | |
129 | + if len(substitutions)==1: | |
130 | + item = substitutions[0] | |
131 | + else: | |
132 | + #create a choice item | |
133 | + item = Choice() | |
134 | + for s in substitutions: | |
135 | + item.add(s) | |
136 | + from .choice import ChoiceInput | |
137 | + chh = ChoiceInput(self.field_frame, item, self.schema, widget_config=self.widget_config) | |
138 | + #self.grid_contents.append(chh) | |
139 | + self.inputs.append(chh) | |
140 | + continue | |
141 | + | |
61 | 142 | self.set_occurrence_bounds(item) |
62 | - element_field = self.get_element_field(item, sequence_inputs) | |
63 | - element_field.pack(side=tk.TOP, fill=tk.X, expand=1) | |
143 | + | |
144 | + for element_field in self.get_element_field(item, self.inputs): | |
145 | + # add to grid contents | |
146 | + #self.grid_contents.append(element_field) | |
147 | + | |
148 | + from .optional import OptionalInput | |
149 | + if isinstance(element_field, OptionalInput): | |
150 | + # pack the button at the bottom of the field | |
151 | + but = element_field.get_add_button(parent = self.option_button_frame2) | |
152 | + self.option_button_frame2.add_button(but) | |
153 | + but.grid(row=0, column=option_button_column) | |
154 | + option_button_column+=1 | |
155 | + | |
64 | 156 | elif isinstance(item, Choice): |
65 | - from xsd2tkform.ui.choice import ChoiceInput | |
66 | - chh = ChoiceInput(self, item, self.schema) | |
67 | - chh.pack(side=tk.TOP, fill=tk.X, expand=1) | |
68 | - sequence_inputs.append(chh) | |
157 | + from .choice import ChoiceInput | |
158 | + chh = ChoiceInput(self.field_frame, item, self.schema, widget_config=self.widget_config) | |
159 | + #self.grid_contents.append(chh) | |
160 | + self.inputs.append(chh) | |
161 | + elif isinstance(item, AnyElement): | |
162 | + from .adaptivetextentry import AdaptiveHeightText | |
163 | + r=XSDAnyInputFrame(parent=self.field_frame,\ | |
164 | + input_widget_type=lambda p: AdaptiveHeightText(p, height=3)) | |
165 | + self.inputs.append(r) | |
166 | + | |
69 | 167 | else: |
70 | 168 | # TODO : add groups |
71 | 169 | print("Group support not implemeneted yet") |
72 | - self.inputs.append(sequence_inputs) | |
73 | - def collapse(self): | |
74 | - # hide all inputs | |
75 | - for i in self.inputs: | |
76 | - if isinstance(i, list): | |
77 | - for e in i: | |
78 | - e.pack_forget() | |
170 | + | |
171 | + #c+=1 | |
172 | + | |
173 | + row=1 | |
174 | + if self.attribute_frame is not None: | |
175 | + self.attribute_frame.grid(row=1, sticky=tk.EW) | |
176 | + row+=1 | |
177 | + self.field_frame.grid(row=row, sticky=tk.EW) | |
178 | + | |
179 | + | |
180 | + | |
181 | + # if filename or content was given | |
182 | + if filename is not None: | |
183 | + print("Setting content from {}".format(filename)) | |
184 | + self.set_content(filename=filename) | |
185 | + if content is not None: | |
186 | + self.set_content(content=content) | |
187 | + | |
188 | + def get_header_frame(self): | |
189 | + hf=tk.Frame(self) | |
190 | + if self.name is None: | |
191 | + label_text=self.sanitize_type(self.type) | |
192 | + else: | |
193 | + label_text=self.name | |
194 | + | |
195 | + #self.header_label=tk.Label(hf, text=label_text, font="bold", anchor="w") | |
196 | + self.header_label=tk.Label(hf, text=label_text, font=("Helvetica",11,"bold"), anchor="w") | |
197 | + | |
198 | + self.header_label.pack(side=tk.LEFT, fill=tk.X, expand=1) | |
199 | + | |
200 | + self.header_label.bind("<Button-1>", self.collapse) | |
201 | + return hf | |
202 | + | |
203 | + | |
204 | + def clear_grid(self): | |
205 | + for child in self.field_frame.winfo_children(): | |
206 | + # delete labels | |
207 | + if isinstance(child, tk.Label): | |
208 | + child.destroy() | |
209 | + elif isinstance(child, ttk.Separator): | |
210 | + child.destroy() | |
79 | 211 | else: |
80 | - i.pack_forget() | |
212 | + child.grid_forget() | |
213 | + def get_label(self, parent): | |
214 | + print(" ------> ComplexType name={}, type={}".format(self.name, self.type)) | |
215 | + label_text=self.sanitize_type(self.type) | |
216 | + if isinstance(self.typedef, Element): | |
217 | + label_text=self.typedef.name | |
218 | + if self.name is not None: | |
219 | + label_text=self.name | |
220 | + | |
221 | + self.label = tk.Label(parent, text=label_text+" :") | |
222 | + self.set_tooltip() | |
223 | + return self.label | |
224 | + def get_fields(self): | |
225 | + from .optional import OptionalInput | |
226 | + l=[] | |
227 | + #for item in self.grid_contents: | |
228 | + for item in self.inputs: | |
229 | + if isinstance(item, OptionalInput): | |
230 | + l+=[w for w in item.get_fields()] | |
231 | + else: | |
232 | + l.append(item) | |
233 | + return l | |
234 | + def remove_separators(self): | |
235 | + for child in self.field_frame.winfo_children(): | |
236 | + if isinstance(child, ttk.Separator): | |
237 | + child.destroy() | |
238 | + def count_grid_contents(self): | |
239 | + c={} | |
240 | + for child in self.field_frame.winfo_children(): | |
241 | + if type(child) in c: | |
242 | + c[type(child)]+=1 | |
243 | + else: | |
244 | + c[type(child)]=1 | |
245 | + for k in c: | |
246 | + print("{} : {}".format(k, c[k])) | |
247 | + | |
248 | + def update_attribute_grid(self): | |
249 | + if self.attribute_frame is not None: | |
250 | + self.attribute_frame.update_grid() | |
251 | + | |
252 | + def update_grid(self): | |
253 | + print("update_grid name={}, type={}, self.type={}".format(self.name, self.type, type(self))) | |
254 | + from .optional import OptionalInput | |
255 | + from .choice import ChoiceInput | |
256 | + | |
257 | + self.remove_separators() | |
258 | + # attribute grid update | |
259 | + self.update_attribute_grid() | |
260 | + # get fields | |
261 | + new_fields = self.get_fields() | |
262 | + | |
263 | + # add the contents of the grid | |
264 | + self.header_frame.grid(row=0, columnspan=2, sticky=tk.EW) | |
265 | + if not self.collapsed: | |
266 | + #ttk.Separator(self, orient="horizontal").grid(row=1, columnspan=2, sticky=tk.EW) | |
267 | + row=2 | |
268 | + for f in new_fields: | |
269 | + # add the input field | |
270 | + grid_info = f.grid_info() | |
271 | + if "row" in grid_info: | |
272 | + if grid_info["row"]!=row: | |
273 | + f.grid_forget() # remove the label too | |
274 | + f.grid(row=row, column=1, sticky=tk.EW) | |
275 | + else: | |
276 | + f.grid(row=row, column=1, sticky=tk.EW) | |
277 | + | |
278 | + # check the label | |
279 | + if not isinstance(f, ChoiceInput): | |
280 | + if f.label is None: | |
281 | + f.get_label(self.field_frame).grid(row=row, column=0, sticky=tk.NW) | |
282 | + else: | |
283 | + label_grid_info = f.label.grid_info() | |
284 | + if "row" in label_grid_info: | |
285 | + if label_grid_info["row"]!=row: | |
286 | + f.label.grid_forget() | |
287 | + f.label.grid(row=row, column=0, sticky=tk.NW) | |
288 | + else: | |
289 | + f.label.grid(row=row, column=0, sticky=tk.NW) | |
290 | + row+=1 | |
291 | + ttk.Separator(self.field_frame, orient="horizontal").grid(row=row, columnspan=2, sticky=tk.EW) | |
292 | + row+=1 | |
293 | + n=len(list(self.option_button_frame2.winfo_children())) | |
294 | + if n>1: | |
295 | + self.option_button_frame2.grid(row=row, columnspan=2, sticky=tk.EW) | |
296 | + | |
297 | + def grid(self, *args, **kwargs): | |
298 | + self.update_grid() | |
299 | + return super().grid(pady=1,*args, **kwargs) | |
300 | + def pack(self, *args, **kwargs): | |
301 | + self.update_grid() | |
302 | + return super().pack(pady=1, *args, **kwargs) | |
303 | + if isinstance(self.master, XSDComplexTypeFrame): | |
304 | + #super().pack(*args, padx=(100,0), **kwargs) | |
305 | + super().pack(*args, **kwargs) | |
306 | + | |
307 | + else: | |
308 | + super().pack(*args, **kwargs) | |
309 | + def iter_inputs(self): | |
310 | + for e in self.inputs: | |
311 | + if isinstance(e, list): | |
312 | + for el in e: | |
313 | + yield el | |
314 | + else: | |
315 | + yield e | |
316 | + def set_content(self, filename=None, content=None, update_grid=True): | |
317 | + if filename is not None: | |
318 | + root = etree.parse(filename).getroot() | |
319 | + return self.set_content(content=root, update_grid=update_grid) | |
320 | + if content is not None: | |
321 | + # set attribute content | |
322 | + for att in content.attrib: | |
323 | + self.attribute_frame.set_attribute_content(att, content.attrib[att]) | |
324 | + # go over children of content | |
325 | + for child in content: | |
326 | + if isinstance(child, etree._Comment): | |
327 | + continue | |
328 | + self.set_child_content(child, update_grid=True) | |
329 | + self.content_has_been_set=True | |
330 | + if update_grid: | |
331 | + self.update_grid() | |
332 | + def is_full(self): | |
333 | + # check if this field is full : all mandatory fields are filled | |
334 | + return self.content_has_been_set | |
335 | + def set_child_content(self, child, update_grid=True): | |
336 | + ct=child.tag.split("}")[-1] | |
337 | + ctype = self.schema.get(ct) | |
338 | + from ..core.element import Element | |
339 | + for minp in self.iter_inputs(): | |
340 | + if isinstance(minp, XSDSimpleTypeFrame) or isinstance(minp, XSDComplexTypeFrame): | |
341 | + if minp.name is None: | |
342 | + ctt=minp.type.split(":")[-1] | |
343 | + else: | |
344 | + ctt=minp.name | |
345 | + if ct==ctt: | |
346 | + # if the field is already filled skip | |
347 | + if minp.is_full(): | |
348 | + continue | |
349 | + minp.set_content(content=child, update_grid=True) | |
350 | + if isinstance(minp, XSDComplexTypeFrame): | |
351 | + minp.collapse() | |
352 | + return | |
353 | + from .optional import OptionalInput | |
354 | + if isinstance(minp, OptionalInput): | |
355 | + if minp.is_full(): | |
356 | + continue | |
357 | + ctt=minp.type.split(":")[-1] | |
358 | + if ct==ctt: | |
359 | + minp.set_content(content=child, update_grid=True) | |
360 | + return | |
361 | + from .choice import ChoiceInput | |
362 | + if isinstance(minp, ChoiceInput): | |
363 | + if minp.has_type(ct): | |
364 | + minp.set_content(content=child, update_grid=True) | |
365 | + return | |
366 | + | |
367 | + def collapse(self, event=None): | |
368 | + self.collapsed = True | |
369 | + if self.attribute_frame is not None: | |
370 | + self.attribute_frame.grid_remove() | |
371 | + for item in self.field_frame.winfo_children(): | |
372 | + if isinstance(item, tk.Label): | |
373 | + item.grid_remove() | |
374 | + if isinstance(item, XSDInputField): | |
375 | + item.grid_remove() | |
376 | + # collapse the optional button frame | |
377 | + self.option_button_frame2.grid_remove() | |
378 | + | |
81 | 379 | # change button action to expand |
82 | - self.collapse_button.configure(text="+", command=self.expand) | |
380 | + self.collapse_button.configure(image=self.expand_img, command=self.expand) | |
83 | 381 | # change the label text |
84 | 382 | in_val=[] |
85 | 383 | for i in self.inputs: |
86 | - if isinstance(i, list): | |
87 | - for e in i: | |
88 | - if isinstance(e, XSDSimpleTypeFrame): | |
89 | - in_val+=["{}:{}".format(self.sanitize_type(e.type),e.get_value())] | |
90 | - new_lab = "{}({})".format(self.sanitize_type(self.type), | |
384 | + if isinstance(i, XSDSimpleTypeFrame): | |
385 | + tval = str(i.get_value()) | |
386 | + if len(tval)>100: | |
387 | + tval=tval[:30]+"..." | |
388 | + in_val+=["{}:{}".format(self.sanitize_type(i.type),tval)] | |
389 | + l=self.sanitize_type(self.type) | |
390 | + if self.name is not None: | |
391 | + l=self.name | |
392 | + new_lab = "{}({})".format(l, | |
91 | 393 | ",".join(in_val)) |
92 | - self.label.configure(text=new_lab) | |
93 | - def expand(self): | |
94 | - # hide all inputs | |
95 | - for i in self.inputs: | |
96 | - if isinstance(i, list): | |
97 | - for e in i: | |
98 | - e.pack(side=tk.TOP, fill=tk.X, expand=1) | |
99 | - else: | |
100 | - i.pack(side=tk.TOP, fill=tk.X, expand=1) | |
394 | + w=int(self.winfo_width()*.8) | |
395 | + self.header_label.configure(text=new_lab, wraplength=w, justify="left") | |
396 | + self.header_label.bind("<Button-1>", self.expand) | |
397 | + def expand(self, event=None): | |
398 | + #self.update_grid() | |
399 | + from .optional import OptionalInput | |
400 | + self.collapsed = False | |
401 | + if self.attribute_frame is not None: | |
402 | + self.attribute_frame.grid() | |
403 | + for item in self.field_frame.winfo_children(): | |
404 | + if isinstance(item, tk.Label): | |
405 | + item.grid() | |
406 | + if isinstance(item, XSDInputField): | |
407 | + if isinstance(item, OptionalInput): | |
408 | + continue | |
409 | + item.grid() | |
410 | + | |
411 | + | |
412 | + # option button frame | |
413 | + #self.option_button_frame.pack(side=tk.BOTTOM, fill=tk.X, expand=1) | |
414 | + self.option_button_frame2.grid() | |
415 | + | |
101 | 416 | # change button action to collapse |
102 | - self.collapse_button.configure(text="_", command=self.collapse) | |
417 | + self.collapse_button.configure(image=self.collapse_img, command=self.collapse) | |
103 | 418 | # set original lable |
104 | - self.label.configure(text=self.sanitize_type(self.type)) | |
419 | + l=self.sanitize_type(self.type) | |
420 | + if self.name is not None: | |
421 | + l=self.name | |
422 | + self.header_label.configure(text=l, justify="center") | |
423 | + self.header_label.bind("<Button-1>", self.collapse) | |
424 | + self.update_grid() | |
425 | + | |
105 | 426 | def delete(self): |
106 | - self.master.decrement_field_count_by_type(self.type) | |
427 | + #self.master.decrement_field_count_by_type(self.type) | |
107 | 428 | self.destroy() |
108 | - def get_choice_field(self, item, sequence_inputs): | |
109 | - choice_inputs=[] | |
110 | - # get the list of choice type | |
111 | - choice_types = [t.type for t in item.elements] | |
112 | - # get occurence bounds for choices | |
113 | - #choice_occ_bounds = self.get_element_occurence_limits(item) | |
114 | - choice_occ_bounds = item.min_occurs, item.max_occurs | |
115 | - # set those bounds for all types | |
116 | - for _type in choice_types: | |
117 | - # TODO : check if bounds are correct, if choice type is present somewhere else in the type definition then the bounds are overwritten here, which can be bad | |
118 | - if _type in self.field_min_counts: | |
119 | - print("Not good if you see this") | |
120 | - if _type in self.field_max_counts: | |
121 | - print("Not good if you see this") | |
122 | - self.field_min_counts[_type]=choice_occ_bounds[0] | |
123 | - self.field_max_counts[_type]=choice_occ_bounds[1] | |
124 | - # frame for storing the selector and choices | |
125 | - choice_frame = tk.Frame(self) | |
126 | - # create a frame to store the choice selector | |
127 | - choice_select_frame = tk.Frame(choice_frame) | |
429 | + if self.on_delete is not None: | |
430 | + self.on_delete() | |
431 | + if self.label is not None: | |
432 | + self.label.destroy() | |
128 | 433 | |
129 | - # add a choice selector : combobox and button | |
130 | - choice_type = ttk.Combobox(choice_select_frame, values=choice_types, state="readonly") | |
131 | - choice_type.current(0) | |
132 | - choice_add = tk.Button(choice_select_frame, text="Add", command=lambda w=choice_type, frame=choice_frame, container=choice_inputs: self.add_frame_by_type(w.get(), frame, container)) | |
133 | - | |
134 | - choice_type.pack(side=tk.LEFT, fill=tk.X, expand=1) | |
135 | - choice_add.pack(side=tk.RIGHT, expand=0) | |
136 | - choice_select_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | |
137 | - ##choice_frame.pack(side=tk.TOP, fill=tk.X, expand=1) | |
138 | - sequence_inputs.append(choice_inputs) | |
139 | - return choice_frame | |
140 | 434 | |
141 | 435 | def get_element_field(self, item, sequence_inputs): |
142 | - if item.min_occurs==0: | |
143 | - # optional field, add a frame to contain the eventual fields, with a button | |
144 | - b_frame= tk.Frame(self) | |
145 | - b=tk.Button(b_frame, text="Add {}".format(item.type), command=lambda t=item.type, frame=b_frame, container=sequence_inputs: self.add_frame_by_type(t, frame, container)) | |
146 | - # add tooltip | |
147 | - doc_str = self.get_type_docstring(item.type) | |
148 | - if len(doc_str): | |
149 | - ttt = ToolTip(b, doc_str) | |
150 | - b.pack(side=tk.TOP, fill=tk.X, expand=1) | |
436 | + from .optional import OptionalInput | |
437 | + if isinstance(item, AnyElement): | |
438 | + of = self.get_frame_by_type(item) | |
439 | + sequence_inputs.append(of) # store the field for later use | |
440 | + yield of | |
151 | 441 | |
442 | + if item.min_occurs==0: | |
152 | 443 | # temp |
153 | - from xsd2tkform.ui.optional import OptionalInput | |
154 | 444 | bounds=(self.field_min_counts[item.type], self.field_max_counts[item.type]) |
155 | - of=OptionalInput(self, item, self.schema, bounds=bounds) | |
156 | - of.pack(side=tk.TOP, fill=tk.X, expand=1) | |
445 | + of=OptionalInput(self.field_frame, item, self.schema, bounds=bounds, widget_config=self.widget_config, on_add_field=self.update_grid,\ | |
446 | + on_delete_field=lambda t=item.type: self.decrement_field_count_by_type(t)\ | |
447 | + ) | |
448 | + | |
157 | 449 | sequence_inputs.append(of) |
158 | - return of | |
159 | - return b_frame | |
450 | + yield of | |
160 | 451 | else: |
161 | - # mandatory field | |
162 | - f = self.get_frame_by_type(item.type) | |
163 | - sequence_inputs.append(f) # store the field for later use | |
164 | - return f | |
452 | + # yield mandatory items | |
453 | + for i in range(item.min_occurs): | |
454 | + of = self.get_frame_by_type(item) | |
455 | + if of is None: | |
456 | + continue | |
457 | + sequence_inputs.append(of) # store the field for later use | |
458 | + yield of | |
459 | + # get optional bounds | |
460 | + bounds=[0, self.field_max_counts[item.type]] | |
461 | + if bounds[1]!="unbounded": | |
462 | + bounds[1]=bounds[1]-item.min_occurs | |
463 | + a = (bounds[1]=="unbounded") | |
464 | + b = False | |
465 | + if not a: | |
466 | + b = bounds[1] | |
467 | + | |
468 | + if a or b: | |
469 | + # yield optional items | |
470 | + of=OptionalInput(self.field_frame, item, self.schema, bounds=tuple(bounds), widget_config=self.widget_config, on_add_field=self.update_grid, \ | |
471 | + on_delete_field=lambda t=item.type: self.decrement_field_count_by_type(t)\ | |
472 | + ) | |
473 | + sequence_inputs.append(of) | |
474 | + yield of | |
475 | + | |
165 | 476 | |
166 | 477 | def set_occurrence_bounds(self, item): |
167 | 478 | self.field_min_counts[item.type]=item.min_occurs |
168 | 479 | self.field_max_counts[item.type]=item.max_occurs |
169 | 480 | |
170 | 481 | def set_tooltip(self): |
482 | + if self.label is None or self.typedef is None: | |
483 | + return | |
484 | + if self.typedef.annotation is None: | |
485 | + return | |
171 | 486 | if len(self.typedef.annotation.documentation): |
172 | 487 | langs = [k for k in self.typedef.annotation.documentation] |
173 | - tt = ToolTip(self.label, self.typedef.annotation.documentation[langs[0]]) | |
488 | + tt = ToolTip(self.header_label, self.typedef.annotation.documentation[langs[0]]) | |
174 | 489 | |
175 | 490 | def get_type_docstring(self, t): |
176 | 491 | td=self.schema.get(t) |
... | ... | @@ -209,8 +524,6 @@ class XSDComplexTypeFrame(XSDInputField): |
209 | 524 | def increment_field_count_by_type(self, t): |
210 | 525 | self.field_counts[t]=self.get_field_count_by_type(t)+1 |
211 | 526 | def decrement_field_count_by_type(self, t): |
212 | - print("Current {} field count {}".format(t, self.get_field_count_by_type(t))) | |
213 | - print([k for k in self.field_counts]) | |
214 | 527 | self.field_counts[t]=self.get_field_count_by_type(t)-1 |
215 | 528 | def delete_field(self, t, field, container=None): |
216 | 529 | field_dims = (field.winfo_width(), field.winfo_height()) |
... | ... | @@ -226,47 +539,64 @@ class XSDComplexTypeFrame(XSDInputField): |
226 | 539 | self.master.master.configure(scrollregion=new_scrollregion) |
227 | 540 | |
228 | 541 | |
229 | - def add_frame_by_type(self, t, frame=None, container=None): | |
230 | - print("in add frame by type : " , t) | |
231 | - # check if the maximum occurences of this field is achieved | |
232 | - | |
233 | - if self.field_max_counts[t]=="unbounded": | |
234 | - print("No limit on {} fields".format(t)) | |
235 | - elif t in self.field_counts: | |
236 | - if self.get_field_count_by_type(t)==self.field_max_counts[t]: | |
237 | - showerror(title="{} maximum occurences reached".format(t), message="Type {} supports a maximum of {} occurences of type {}".format(self.type, self.field_max_counts[t], t)) | |
238 | - return | |
239 | - else: | |
240 | - pass | |
241 | - | |
242 | - self.increment_field_count_by_type(t) | |
243 | - | |
244 | - if frame is None: | |
245 | - f = self.get_frame_by_type(t, delete_button=True) | |
246 | - f.pack(side=tk.TOP, fill=tk.X, expand=1) | |
247 | - else: | |
248 | - f = self.get_frame_by_type(t, parent=frame, delete_button=True) | |
249 | - f.pack(side=tk.TOP, fill=tk.X, expand=1) | |
250 | - if container is not None: | |
251 | - container.append(f) | |
252 | - #container.append(f.winfo_children()[0]) | |
253 | 542 | def get_frame_by_type(self, t, parent=None, delete_button=False): |
543 | + #from ..core.type import SimpleType | |
254 | 544 | if parent is None: |
255 | - parent = self | |
256 | - | |
257 | - td=self.schema.get(t) | |
545 | + parent = self.field_frame | |
546 | + if isinstance(t, AnyElement): | |
547 | + from .adaptivetextentry import AdaptiveHeightText | |
548 | + return XSDAnyInputFrame(parent=parent,\ | |
549 | + input_widget=AdaptiveHeightText) | |
550 | + | |
551 | + | |
552 | + if t.type is None: | |
553 | + td=self.schema.get(t.name) | |
554 | + else: | |
555 | + td=self.schema.get(t.type) | |
556 | + if td is None: | |
557 | + # if type is native type | |
558 | + if t.type.split(":")[-1]=="string": | |
559 | + # return a SimpleType object | |
560 | + from ..core.restriction import Restriction | |
561 | + #from ..core.type import SimpleType | |
562 | + td=SimpleType(t.name, restriction=Restriction(base="string")) | |
563 | + return XSDSimpleTypeFrame(t.name, name=t.name, parent=parent,\ | |
564 | + schema=self.schema, delete_button=delete_button,\ | |
565 | + widget_config=self.widget_config,\ | |
566 | + typedef=td) | |
567 | + | |
258 | 568 | if isinstance(td, SimpleType):# in self.simple_types: |
259 | - return XSDSimpleTypeFrame(t, parent=parent,\ | |
569 | + return XSDSimpleTypeFrame(t.type, name=t.name, parent=parent,\ | |
260 | 570 | schema=self.schema, |
261 | - delete_button=delete_button) | |
571 | + delete_button=delete_button, | |
572 | + widget_config=self.widget_config) | |
262 | 573 | elif isinstance(td, ComplexType): |
263 | - return XSDComplexTypeFrame(t, parent=parent,\ | |
574 | + return XSDComplexTypeFrame(t.type, name=t.name, parent=parent,\ | |
264 | 575 | schema=self.schema, |
265 | 576 | delete_button=delete_button, |
266 | - collapsable=True) | |
577 | + collapsable=True, | |
578 | + widget_config=self.widget_config) | |
267 | 579 | else: |
268 | 580 | # TODO : add Group support |
269 | 581 | print("Group support not yet implemented") |
582 | + #if td.typedef is None: | |
583 | + # return None | |
584 | + if td.ref is not None: | |
585 | + return None | |
586 | + if isinstance(td.typedef, SimpleType): | |
587 | + return XSDSimpleTypeFrame(t.type, name=t.name, parent=parent,\ | |
588 | + schema=self.schema, | |
589 | + delete_button=delete_button, | |
590 | + widget_config=self.widget_config, typedef=td.typedef) | |
591 | + | |
592 | + | |
593 | + return XSDComplexTypeFrame(td.type, name=td.name, parent=parent,\ | |
594 | + schema=self.schema, | |
595 | + delete_button=delete_button, | |
596 | + collapsable=True, | |
597 | + widget_config=self.widget_config, | |
598 | + typedef=td.typedef) | |
599 | + | |
270 | 600 | def get_value(self): |
271 | 601 | return "" |
272 | 602 | def add_content(self, root, content): |
... | ... | @@ -276,31 +606,46 @@ class XSDComplexTypeFrame(XSDInputField): |
276 | 606 | else: |
277 | 607 | if content is not None: |
278 | 608 | root.append(content) |
279 | - def get_content(self, obj=None): | |
609 | + def get_attribute_values(self): | |
610 | + if self.attribute_frame is None: | |
611 | + return {} | |
612 | + return self.attribute_frame.get_attribute_values() | |
613 | + def get_content(self, obj=None, nsmap=None, qname_attrs=None): | |
280 | 614 | if obj is not None: |
281 | - from xsd2tkform.ui.choice import ChoiceInput | |
615 | + from .choice import ChoiceInput | |
282 | 616 | if isinstance(obj, list): |
283 | - return [self.get_content(i) for i in obj] | |
284 | - if isinstance(obj, XSDSimpleTypeFrame): | |
617 | + return [self.get_content(i, nsmap=nsmap) for i in obj] | |
618 | + if isinstance(obj, XSDAnyInputFrame): | |
285 | 619 | if obj.winfo_exists(): |
286 | 620 | return obj.get_content() |
621 | + if isinstance(obj, XSDSimpleTypeFrame): | |
622 | + if obj.winfo_exists(): | |
623 | + return obj.get_content(nsmap=nsmap) | |
287 | 624 | if isinstance(obj, XSDComplexTypeFrame): |
288 | 625 | if obj.winfo_exists(): |
289 | - return obj.get_content() | |
626 | + return obj.get_content(nsmap=nsmap) | |
290 | 627 | if isinstance(obj, ChoiceInput): |
291 | 628 | if obj.winfo_exists(): |
292 | - return obj.get_content() | |
293 | - from xsd2tkform.ui.optional import OptionalInput | |
629 | + return obj.get_content(nsmap=nsmap) | |
630 | + from .optional import OptionalInput | |
294 | 631 | if isinstance(obj, OptionalInput): |
295 | 632 | if obj.winfo_exists(): |
296 | - return obj.get_content() | |
633 | + return obj.get_content(nsmap=nsmap) | |
297 | 634 | |
298 | 635 | return |
299 | 636 | |
300 | - | |
301 | - root = etree.Element(self.sanitize_type(self.type)) | |
637 | + if nsmap is None: | |
638 | + root = etree.Element(self.sanitize_type(self.type)) | |
639 | + else: | |
640 | + root = etree.Element(self.sanitize_type(self.type), qname_attrs, nsmap=nsmap) | |
641 | + attrib_values=self.get_attribute_values() | |
642 | + for k in attrib_values: | |
643 | + root.set(k, attrib_values[k]) | |
302 | 644 | # returns tree type |
303 | - for c in self.get_content(self.inputs): | |
645 | + for c in self.get_content(self.inputs, nsmap=nsmap): | |
646 | + if isinstance(c, str): | |
647 | + root.text=c | |
648 | + continue | |
304 | 649 | self.add_content(root, c) |
305 | 650 | return root |
306 | 651 | ... | ... |
xsd2tkform/ui/datetime_selector.py
... | ... | @@ -31,5 +31,27 @@ class DatetimeEntry(tk.Frame): |
31 | 31 | def get(self): |
32 | 32 | # return time value |
33 | 33 | return "{}T{}:{}:{}Z".format(self.date.get(), self.hours.get(), self.minutes.get(), self.seconds.get()) |
34 | - | |
34 | + def set(self, content): | |
35 | + content=content.strip() | |
36 | + from datetime import datetime | |
37 | + if content.endswith("Z"): | |
38 | + if "." in content: | |
39 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%S.%fZ") | |
40 | + else: | |
41 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%SZ") | |
42 | + else: | |
43 | + if "." in content: | |
44 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%S.%f") | |
45 | + else: | |
46 | + d=datetime.strptime(content, "%Y-%m-%dT%H:%M:%S") | |
47 | + | |
48 | + | |
49 | + self.date.set_date(d) | |
50 | + t=d.time() | |
51 | + self.hours.delete(0,"end") | |
52 | + self.minutes.delete(0,"end") | |
53 | + self.seconds.delete(0,"end") | |
54 | + self.hours.insert(0, "{0:02d}".format(t.hour)) | |
55 | + self.minutes.insert(0, "{0:02d}".format(t.minute)) | |
56 | + self.seconds.insert(0, "{0:02d}".format(t.second)) | |
35 | 57 | ... | ... |
... | ... | @@ -0,0 +1,73 @@ |
1 | +"""Some Entry widgets with validation depending on type | |
2 | +""" | |
3 | +import tkinter as tk | |
4 | + | |
5 | + | |
6 | +class FloatEntry(tk.Entry): | |
7 | + def __init__(self, parent): | |
8 | + tk.Entry.__init__(self, parent) | |
9 | + vcmd = (self.register(self.validate), | |
10 | + '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') | |
11 | + self.configure(validate="all", validatecommand=vcmd) | |
12 | + | |
13 | + def validate(self, *args): | |
14 | + try: | |
15 | + i=float(args[2]) | |
16 | + self.config(fg="black") | |
17 | + return True | |
18 | + except: | |
19 | + self.config(fg="red") | |
20 | + return True | |
21 | + def get(self, *args, **kwargs): | |
22 | + return float(self.get(*args, **kwargs)) | |
23 | + def set(self, value): | |
24 | + self.delete(0, tk.END) | |
25 | + self.insert(0, value) | |
26 | + | |
27 | + | |
28 | +class IntEntry(tk.Entry): | |
29 | + def __init__(self, parent): | |
30 | + tk.Entry.__init__(self, parent) | |
31 | + vcmd = (self.register(self.validate), | |
32 | + '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') | |
33 | + self.configure(validate="all", validatecommand=vcmd) | |
34 | + def set(self, value): | |
35 | + self.delete(0, tk.END) | |
36 | + self.insert(0, value) | |
37 | + def get(self, *args, **kwargs): | |
38 | + return int(self.get(*args, **kwargs)) | |
39 | + | |
40 | + | |
41 | + def validate(self, *args): | |
42 | + try: | |
43 | + i=int(args[2]) | |
44 | + self.config(fg="black") | |
45 | + return True | |
46 | + except: | |
47 | + self.config(fg="red") | |
48 | + return True | |
49 | + | |
50 | +class BoolEntry(tk.Frame): | |
51 | + def __init__(self, parent): | |
52 | + tk.Frame.__init__(self, parent)#highlightbackground="bleu", highlightthickness=2) | |
53 | + vals = ["true", "false"] | |
54 | + labels=["True", "False"] | |
55 | + self.var = tk.StringVar() | |
56 | + self.var.set(vals[0]) | |
57 | + for i in range(2): | |
58 | + tk.Radiobutton(self, variable=self.var, text=labels[i], value=vals[i]).pack(side=tk.LEFT, fill=tk.X, expand=1) | |
59 | + def get(self, *args): | |
60 | + return self.var.get() | |
61 | + def set(self, value): | |
62 | + self.var.set(value) | |
63 | + | |
64 | +if __name__=="__main__": | |
65 | + root = tk.Tk() | |
66 | + | |
67 | + float_entry = FloatEntry(root) | |
68 | + float_entry.pack(side=tk.TOP, fill=tk.X, expand=1) | |
69 | + | |
70 | + int_entry = IntEntry(root) | |
71 | + int_entry.pack(side=tk.TOP, fill=tk.X, expand=1) | |
72 | + | |
73 | + root.mainloop() | ... | ... |
xsd2tkform/ui/field.py
... | ... | @@ -4,10 +4,18 @@ import tkinter as tk |
4 | 4 | |
5 | 5 | |
6 | 6 | class XSDInputField(tk.Frame): |
7 | - def __init__(self, parent, *args, **kwargs): | |
7 | + def __init__(self, parent, content=None, widget_config={}, on_delete=None, *args, **kwargs): | |
8 | 8 | tk.Frame.__init__(self, parent, *args, **kwargs) |
9 | + self.widget_config=widget_config | |
10 | + self.on_delete = on_delete | |
11 | + if content is not None: | |
12 | + self.set_content(content) | |
9 | 13 | def sanitize_type(self, t): |
14 | + if t is None: | |
15 | + return "?" | |
10 | 16 | if ":" in t: |
11 | 17 | return t.split(":")[-1] |
12 | 18 | return t |
19 | + def set_content(self, content=None): | |
20 | + print("XSDInputField::set_content {}".format(content)) | |
13 | 21 | ... | ... |
xsd2tkform/ui/optional.py
1 | 1 | import tkinter as tk |
2 | 2 | from tkinter.messagebox import showerror |
3 | 3 | |
4 | -from xsd2tkform.ui.field import XSDInputField | |
5 | -from xsd2tkform.ui.tooltip import ToolTip | |
4 | +from .field import XSDInputField | |
5 | +from .tooltip import ToolTip | |
6 | 6 | |
7 | -from xsd2tkform.core.type import SimpleType, ComplexType | |
7 | +from ..core.type import SimpleType, ComplexType | |
8 | 8 | |
9 | -from xsd2tkform.ui.simpletype import XSDSimpleTypeFrame | |
10 | -from xsd2tkform.ui.complextype import XSDComplexTypeFrame | |
9 | +from .simpletype import XSDSimpleTypeFrame | |
10 | +from .complextype import XSDComplexTypeFrame | |
11 | 11 | |
12 | 12 | |
13 | 13 | class OptionalInput(XSDInputField): |
14 | - def __init__(self, parent, item, schema, bounds=None, *args, **kwargs): | |
15 | - XSDInputField.__init__(self, parent , *args, **kwargs) | |
14 | + def __init__(self, parent, item, schema, bounds=None, on_add_field=None, on_delete_field=None, *args, **kwargs): | |
15 | + XSDInputField.__init__(self, parent , \ | |
16 | + highlightbackground="red",\ | |
17 | + highlightthickness=1,\ | |
18 | + | |
19 | + *args, **kwargs) | |
20 | + self.grid_columnconfigure(0, weight=1) | |
16 | 21 | self.count=0 |
17 | 22 | self.bounds=bounds |
18 | 23 | self.type = item.type |
24 | + self.item=item | |
19 | 25 | self.schema = schema |
26 | + | |
20 | 27 | self.fields=[] |
21 | - b=tk.Button(self, text="Add {}".format(self.sanitize_type(item.type)), command=self.add_field) | |
22 | - # add tooltip | |
23 | - doc_str = self.get_type_docstring(item.type) | |
28 | + self.add_button2=None | |
29 | + self.next_row=0 | |
30 | + self.label_frame=None | |
31 | + self.label=None | |
32 | + | |
33 | + self.on_add_field=on_add_field | |
34 | + self.on_delete_field=on_delete_field | |
35 | + def is_full(self): | |
36 | + return self.count == self.bounds[1] | |
37 | + def remove_label(self): | |
38 | + if self.label is not None: | |
39 | + self.label.destroy() | |
40 | + self.label=None | |
41 | + | |
42 | + def set_label_frame(self, frame): | |
43 | + self.label_frame=frame | |
44 | + | |
45 | + def get_add_button(self, parent): | |
46 | + button_text=self.sanitize_type(self.type) | |
47 | + if button_text in ["float", "string", "integer", "?"]: | |
48 | + button_text=self.item.name | |
49 | + self.add_button2= tk.Button(parent, text=button_text, command=self.add_field) | |
50 | + doc_str = self.get_type_docstring(self.type) | |
24 | 51 | if len(doc_str): |
25 | - ttt = ToolTip(b, doc_str) | |
26 | - b.pack(side=tk.TOP, fill=tk.X, expand=1) | |
27 | - def add_field(self): | |
52 | + ttt = ToolTip(self.add_button2, doc_str) | |
53 | + | |
54 | + | |
55 | + return self.add_button2 | |
56 | + def set_content(self, content, update_grid=True): | |
57 | + self.add_field(content=content, update_grid=update_grid) | |
58 | + def add_label(self): | |
59 | + if self.label_frame is not None and self.label is None: | |
60 | + self.label=tk.Label(self.label_frame, text=self.sanitize_type(self.type)+":") | |
61 | + self.label.grid() | |
62 | + def get_fields(self): | |
63 | + self.fields = [w for w in self.fields if w.winfo_exists()] | |
64 | + return self.fields | |
65 | + def add_field(self, content=None, update_grid=True): | |
28 | 66 | if self.bounds[1]=="unbounded": |
29 | - print("No limit on {} fields".format(self.type)) | |
67 | + pass | |
30 | 68 | elif self.count==self.bounds[1]: |
31 | 69 | showerror(title="{} maximum occurences reached".format(self.type), message="A maximum of {} occurences of type {}".format(self.bounds[1], self.type)) |
32 | 70 | return |
33 | 71 | else: |
34 | 72 | pass |
35 | - | |
73 | + self.add_label() | |
36 | 74 | self.count+=1 |
37 | - | |
38 | - f=self.get_frame_by_type(self.type, delete_button=True) | |
39 | - f.pack(side=tk.TOP, fill=tk.X, expand=1) | |
40 | - #self.fields.append(f.winfo_children()[0]) | |
75 | + | |
76 | + if self.count <= self.bounds[0]: | |
77 | + f=self.get_frame_by_type(self.type, delete_button=False, content=content, parent=self.master, update_grid=update_grid) | |
78 | + else: | |
79 | + f=self.get_frame_by_type(self.type, delete_button=True, content=content, parent=self.master, update_grid=update_grid) | |
80 | + if f is None: | |
81 | + return | |
41 | 82 | self.fields.append(f) |
42 | - def get_frame_by_type(self, t, parent=None, delete_button=False): | |
83 | + if isinstance(f, XSDComplexTypeFrame): | |
84 | + f.collapse() | |
85 | + | |
86 | + | |
87 | + # if the maximum number of fields of this type is attained then remove the add button | |
88 | + if self.count == self.bounds[1] and self.add_button2 is not None: | |
89 | + self.add_button2.grid_remove() | |
90 | + | |
91 | + # update grid in parent | |
92 | + if content is None: | |
93 | + if self.on_add_field is not None: | |
94 | + self.on_add_field() | |
95 | + #self.master.update_grid() | |
96 | + | |
97 | + def get_frame_by_type(self, t, parent=None, delete_button=False, content=None, update_grid=True): | |
43 | 98 | if parent is None: |
44 | 99 | parent = self |
45 | 100 | |
46 | 101 | td=self.schema.get(t) |
102 | + if td is None: | |
103 | + # if type is native type | |
104 | + ttt=t.split(":")[-1] | |
105 | + if ttt=="string" or ttt=="float" or ttt=="integer" or ttt=="int": | |
106 | + # return a SimpleType object | |
107 | + from ..core.restriction import Restriction | |
108 | + #from ..core.type import SimpleType | |
109 | + td=SimpleType(self.item.name, restriction=Restriction(base="string")) | |
110 | + #return XSDSimpleTypeFrame(self.item.name, name=self.item.name, parent=parent,\ | |
111 | + return XSDSimpleTypeFrame(ttt, name=self.item.name, parent=parent,\ | |
112 | + schema=self.schema, delete_button=delete_button,\ | |
113 | + widget_config=self.widget_config,\ | |
114 | + typedef=td,\ | |
115 | + on_delete=self.delete_field) | |
116 | + | |
117 | + | |
47 | 118 | if isinstance(td, SimpleType):# in self.simple_types: |
48 | 119 | return XSDSimpleTypeFrame(t, parent=parent,\ |
49 | 120 | schema=self.schema, |
50 | - delete_button=delete_button) | |
121 | + delete_button=delete_button, | |
122 | + content=content, | |
123 | + widget_config=self.widget_config, | |
124 | + on_delete=self.delete_field) | |
51 | 125 | elif isinstance(td, ComplexType): |
52 | 126 | return XSDComplexTypeFrame(t, parent=parent,\ |
53 | 127 | schema=self.schema, |
54 | - delete_button=delete_button) | |
128 | + delete_button=delete_button, | |
129 | + content=content, | |
130 | + collapsable=True, | |
131 | + widget_config=self.widget_config, | |
132 | + on_delete=self.delete_field) | |
55 | 133 | else: |
56 | 134 | # TODO : add Group support |
57 | 135 | print("Group support not yet implemented") |
58 | - def delete_field(self, t, widget): | |
59 | - print("in delete_field : {}".format(type(widget))) | |
60 | - print(self.fields) | |
61 | - widget.destroy() | |
62 | - self.count-=1 | |
63 | - def get_content(self): | |
64 | - return [i.get_content() for i in self.fields if i.winfo_exists()] | |
136 | + def delete_field(self, t=None, widget=None): | |
137 | + # doesn't seem to be used | |
138 | + self.fields = [w for w in self.fields if w.winfo_exists()] | |
139 | + self.decrement_field_count_by_type(self.type) | |
140 | + #self.count-=1 | |
141 | + | |
142 | + if self.bounds[1]!="unbounded": | |
143 | + if self.bounds[1]-self.count == 1: | |
144 | + #self.add_button2.pack(side=tk.LEFT) | |
145 | + self.add_button2.grid() | |
146 | + if self.on_delete_field is not None: | |
147 | + self.on_delete_field() | |
148 | + #self.master.update_grid() | |
149 | + def get_content(self, nsmap=None, qname_attrs=None): | |
150 | + return [i.get_content(nsmap=nsmap, qname_attrs=qname_attrs) for i in self.fields if i.winfo_exists()] | |
65 | 151 | def get_type_docstring(self, t): |
152 | + if t is None: | |
153 | + return "" | |
66 | 154 | td=self.schema.get(t) |
155 | + if td is None or (not hasattr(td, "annotation")): | |
156 | + return "" | |
67 | 157 | a=td.annotation.documentation |
68 | 158 | if len(a): |
69 | 159 | ls=[l for l in a] |
70 | 160 | return td.annotation.documentation[ls[0]] |
71 | 161 | return "" |
72 | - def decrement_field_count_by_type(self, t): | |
162 | + def decrement_field_count_by_type(self, t, widget=None): | |
73 | 163 | self.count-=1 |
74 | - | |
75 | 164 | ... | ... |
xsd2tkform/ui/scrollframe.py
... | ... | @@ -7,7 +7,7 @@ import platform |
7 | 7 | class ScrollFrame(tk.Frame): |
8 | 8 | def __init__(self, parent): |
9 | 9 | super().__init__(parent) # create a frame (self) |
10 | - | |
10 | + self.bind_ids=[] | |
11 | 11 | self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") #place canvas on self |
12 | 12 | self.viewPort = tk.Frame(self.canvas, background="#ffffff") #place a frame on the canvas, this frame will hold the child widgets |
13 | 13 | self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self |
... | ... | @@ -18,11 +18,11 @@ class ScrollFrame(tk.Frame): |
18 | 18 | self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw", #add view port frame to canvas |
19 | 19 | tags="self.viewPort") |
20 | 20 | |
21 | - self.viewPort.bind("<Configure>", self.onFrameConfigure) #bind an event whenever the size of the viewPort frame changes. | |
22 | - self.canvas.bind("<Configure>", self.onCanvasConfigure) #bind an event whenever the size of the canvas frame changes. | |
21 | + self.bind_ids.append((self.viewPort, self.viewPort.bind("<Configure>", self.onFrameConfigure))) #bind an event whenever the size of the viewPort frame changes. | |
22 | + self.bind_ids.append((self.canvas, self.canvas.bind("<Configure>", self.onCanvasConfigure))) #bind an event whenever the size of the canvas frame changes. | |
23 | 23 | |
24 | - self.viewPort.bind('<Enter>', self.onEnter) # bind wheel events when the cursor enters the control | |
25 | - self.viewPort.bind('<Leave>', self.onLeave) # unbind wheel events when the cursorl leaves the control | |
24 | + self.bind_ids.append((self.viewPort, self.viewPort.bind('<Enter>', self.onEnter))) # bind wheel events when the cursor enters the control | |
25 | + self.bind_ids.append((self.viewPort, self.viewPort.bind('<Leave>', self.onLeave))) # unbind wheel events when the cursorl leaves the control | |
26 | 26 | |
27 | 27 | self.onFrameConfigure(None) #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize |
28 | 28 | |
... | ... | @@ -49,18 +49,25 @@ class ScrollFrame(tk.Frame): |
49 | 49 | |
50 | 50 | def onEnter(self, event): # bind wheel events when the cursor enters the control |
51 | 51 | if platform.system() == 'Linux': |
52 | - self.canvas.bind_all("<Button-4>", self.onMouseWheel) | |
53 | - self.canvas.bind_all("<Button-5>", self.onMouseWheel) | |
52 | + self.bind_ids.append((self.canvas,self.canvas.bind_all("<Button-4>", self.onMouseWheel))) | |
53 | + self.bind_ids.append((self.canvas,self.canvas.bind_all("<Button-5>", self.onMouseWheel))) | |
54 | 54 | else: |
55 | - self.canvas.bind_all("<MouseWheel>", self.onMouseWheel) | |
55 | + self.bind_ids.append((self.canvas,self.canvas.bind_all("<MouseWheel>", self.onMouseWheel))) | |
56 | 56 | |
57 | - def onLeave(self, event): # unbind wheel events when the cursorl leaves the control | |
57 | + def onLeave(self, event): # unbind wheel events when the cursorl leaves the control | |
58 | 58 | if platform.system() == 'Linux': |
59 | 59 | self.canvas.unbind_all("<Button-4>") |
60 | 60 | self.canvas.unbind_all("<Button-5>") |
61 | 61 | else: |
62 | 62 | self.canvas.unbind_all("<MouseWheel>") |
63 | - | |
63 | + def destroy(self): | |
64 | + # unbind all | |
65 | + for w,fid in self.bind_ids: | |
66 | + w.unbind(fid) | |
67 | + self.canvas.unbind_all("<Button-4>") | |
68 | + self.canvas.unbind_all("<Button-5>") | |
69 | + self.canvas.unbind_all("<MouseWheel>") | |
70 | + super().destroy() | |
64 | 71 | |
65 | 72 | |
66 | 73 | # ******************************** | ... | ... |
xsd2tkform/ui/simpletype.py
... | ... | @@ -3,22 +3,142 @@ from tkinter import ttk |
3 | 3 | |
4 | 4 | from lxml import etree |
5 | 5 | |
6 | -from xsd2tkform.ui.tooltip import ToolTip | |
6 | +from .tooltip import ToolTip | |
7 | 7 | |
8 | 8 | from tkcalendar import DateEntry |
9 | 9 | |
10 | -from xsd2tkform.ui.datetime_selector import DatetimeEntry | |
10 | +from .datetime_selector import DatetimeEntry | |
11 | 11 | |
12 | 12 | from .field import XSDInputField |
13 | + | |
14 | +from amda_xml_manager import delete_image_file | |
15 | + | |
16 | +from .entrywidgets import IntEntry, FloatEntry, BoolEntry | |
17 | + | |
18 | +class XSDAnyInputFrame(XSDInputField): | |
19 | + def __init__(self, parent, input_widget_type, content=None): | |
20 | + XSDInputField.__init__(self, parent) | |
21 | + | |
22 | + self.label=None | |
23 | + self.add_button=None | |
24 | + | |
25 | + self.grid_columnconfigure(0, weight=1) | |
26 | + self.input_widget = input_widget_type(self) | |
27 | + self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) | |
28 | + | |
29 | + self.set_content(content) | |
30 | + def set_content(self, content=None): | |
31 | + if content is None: | |
32 | + return | |
33 | + if isinstance(content, etree._Element): | |
34 | + self.input_widget.insert(0, etree.tostring(content, pretty_print=True)) | |
35 | + return | |
36 | + self.input_widget.insert(0, str(content)) | |
37 | + | |
38 | + | |
39 | + def get_label(self, parent): | |
40 | + label_text="any :" | |
41 | + self.label = tk.Label(parent, text=label_text) | |
42 | + return self.label | |
43 | + def get_content(self): | |
44 | + return self.input_widget.get("1.0", tk.END) | |
45 | + | |
46 | + | |
47 | +class XSDAttributeFrame(XSDInputField): | |
48 | + def __init__(self, attribute, parent, on_delete=None, on_add=None): | |
49 | + XSDInputField.__init__(self, parent) | |
50 | + | |
51 | + self.on_delete=on_delete | |
52 | + self.on_add=on_add | |
53 | + self.name = attribute.name | |
54 | + self.type = attribute.type | |
55 | + self.attribute = attribute | |
56 | + if attribute.use=="required": | |
57 | + self.mandatory = True | |
58 | + else: | |
59 | + self.mandatory = False | |
60 | + self.label=None | |
61 | + self.add_button=None | |
62 | + | |
63 | + self.grid_columnconfigure(0, weight=1) | |
64 | + if self.type.endswith("float"): | |
65 | + self.input_widget=FloatEntry(self) | |
66 | + elif self.type.endswith("int") or self.type.endswith("integer"): | |
67 | + self.input_widget=IntEntry(self) | |
68 | + elif self.type.endswith("boolean"): | |
69 | + self.input_widget=BoolEntry(self) | |
70 | + else: | |
71 | + self.input_widget=tk.Entry(self) | |
72 | + self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) | |
73 | + | |
74 | + if not self.mandatory: | |
75 | + self.delete_img = tk.PhotoImage(file=delete_image_file) | |
76 | + #self.db = tk.Button(self, text="X", command=self.delete) | |
77 | + self.db = tk.Button(self, image=self.delete_img, command=self.delete) | |
78 | + self.db.pack(side=tk.RIGHT, expand=0) | |
79 | + def is_visible(self): | |
80 | + return "row" in self.grid_info() | |
81 | + def get(self): | |
82 | + return self.input_widget.get() | |
83 | + def set(self, value): | |
84 | + if isinstance(self.input_widget, FloatEntry) or isinstance(self.input_widget, IntEntry) or \ | |
85 | + isinstance(self.input_widget, BoolEntry): | |
86 | + self.input_widget.set(value) | |
87 | + else: | |
88 | + self.input_widget.insert(0, value) | |
89 | + def get_add_button(self, parent): | |
90 | + self.add_button = tk.Button(parent, text=self.name, command=self.add) | |
91 | + return self.add_button | |
92 | + def add(self): | |
93 | + # remove the button | |
94 | + self.grid() | |
95 | + if self.label is not None: | |
96 | + self.label.grid() | |
97 | + if self.add_button is not None: | |
98 | + self.add_button.grid_remove() | |
99 | + if self.on_add is not None: | |
100 | + self.on_add() | |
101 | + def delete(self, *args): | |
102 | + self.grid_remove() | |
103 | + if self.label is not None: | |
104 | + self.label.grid_remove() | |
105 | + # replace the add button | |
106 | + if self.add_button is not None: | |
107 | + self.add_button.grid() | |
108 | + | |
109 | + if self.on_delete is not None: | |
110 | + self.on_delete() | |
111 | + | |
112 | + def get_label(self, parent): | |
113 | + label_text=self.sanitize_type(self.name) | |
114 | + label_text+=" :" | |
115 | + self.label = tk.Label(parent, text=label_text) | |
116 | + return self.label | |
117 | + | |
13 | 118 | class XSDSimpleTypeFrame(XSDInputField): |
14 | - def __init__(self, typename, parent=None, schema=None, delete_button=False, *args, **kwargs): | |
15 | - XSDInputField.__init__(self, parent, *args, **kwargs) | |
119 | + def __init__(self, typename, parent=None, schema=None, delete_button=False, filename=None, content=None, name=None, widget_config={}, typedef=None, input_widget=None,*args, **kwargs): | |
120 | + XSDInputField.__init__(self, parent, widget_config=widget_config,\ | |
121 | + # highlightbackground="blue",\ | |
122 | + # highlightthickness=1,\ | |
123 | + *args, **kwargs) | |
124 | + # flag indicating that the content has been set | |
125 | + self.content_has_been_set = False | |
126 | + | |
127 | + self.grid_columnconfigure(0, weight=1) | |
128 | + self.input_widget_type=input_widget | |
16 | 129 | |
17 | 130 | self.type = typename |
18 | - self.label = tk.Label(self, text="{}: ".format(self.sanitize_type(typename))) | |
19 | - self.label.pack(side=tk.LEFT, expand=0) | |
131 | + self.name = name | |
132 | + | |
133 | + print("XSDSimpleTypeFrame name={}, type={}".format(self.name, self.type)) | |
134 | + | |
135 | + # label reference | |
136 | + self.label=None | |
20 | 137 | |
21 | - self.typedef = schema.get(typename) | |
138 | + if typedef is None: | |
139 | + self.typedef = schema.get(typename) | |
140 | + else: | |
141 | + self.typedef = typedef | |
22 | 142 | |
23 | 143 | self.set_tooltip() |
24 | 144 | |
... | ... | @@ -27,41 +147,126 @@ class XSDSimpleTypeFrame(XSDInputField): |
27 | 147 | self.input_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) |
28 | 148 | |
29 | 149 | # delete button |
150 | + self.delete_img = None | |
30 | 151 | if delete_button: |
31 | - self.db = tk.Button(self, text="X", command=self.delete) | |
152 | + self.delete_img = tk.PhotoImage(file=delete_image_file) | |
153 | + #self.db = tk.Button(self, text="X", command=self.delete) | |
154 | + self.db = tk.Button(self, image=self.delete_img, command=self.delete) | |
32 | 155 | self.db.pack(side=tk.RIGHT, expand=0) |
156 | + | |
157 | + # if a filename was given then load the content | |
158 | + if filename is not None: | |
159 | + print("Setting SimpleType content from file : {}".format(filename)) | |
160 | + if content is not None: | |
161 | + self.set_content(content) | |
162 | + def get_label(self, parent): | |
163 | + label_text=self.sanitize_type(self.type) | |
164 | + if label_text in ["string", "float", "integer"]: | |
165 | + label_text=self.name | |
166 | + | |
167 | + if self.name is not None: | |
168 | + label_text=self.name | |
169 | + else: | |
170 | + label_text=self.sanitize_type(self.type) | |
171 | + if label_text in ["string", "float", "integer"]: | |
172 | + label_text=self.name | |
173 | + | |
174 | + label_text+=" :" | |
175 | + self.label = tk.Label(parent, text=label_text) | |
176 | + self.set_tooltip() | |
177 | + return self.label | |
33 | 178 | def delete(self): |
34 | 179 | # destroy this widget and decrement the field count in |
35 | 180 | # the master frame |
36 | - print("self id : ", id(self)) | |
37 | - print([id(e) for e in self.master.fields]) | |
38 | - print(self.master.fields) | |
39 | - self.master.decrement_field_count_by_type(self.type) | |
181 | + #self.master.decrement_field_count_by_type(self.type) | |
40 | 182 | self.destroy() |
183 | + if self.on_delete is not None: | |
184 | + self.on_delete() | |
185 | + if self.label is not None: | |
186 | + self.label.destroy() | |
187 | + def is_full(self): | |
188 | + return self.content_has_been_set | |
189 | + def set_content(self, content, update_grid=True): | |
190 | + text=content.text | |
191 | + if text is None: | |
192 | + text="" | |
193 | + if isinstance(self.input_widget, ttk.Combobox): | |
194 | + if not content.text is None: | |
195 | + self.input_widget.set(content.text) | |
196 | + elif isinstance(self.input_widget, tk.Text): | |
197 | + n_lines = len(text.split("\n"))+1 | |
198 | + self.input_widget.insert("1.0", text) | |
199 | + #self.input_widget.configure(height=n_lines) | |
200 | + | |
201 | + elif isinstance(self.input_widget, tk.Entry): | |
202 | + self.input_widget.insert(0, text) | |
203 | + else: | |
204 | + self.input_widget.set(content.text) | |
205 | + self.content_has_been_set = True | |
41 | 206 | def set_tooltip(self): |
207 | + if self.label is None: | |
208 | + return | |
209 | + if self.typedef is None: | |
210 | + return | |
211 | + if self.typedef.annotation is None: | |
212 | + return | |
42 | 213 | if len(self.typedef.annotation.documentation): |
43 | 214 | langs = [k for k in self.typedef.annotation.documentation] |
44 | - tt = ToolTip(self.label, self.typedef.annotation.documentation[langs[0]]) | |
215 | + tt=ToolTip(self.label, self.typedef.annotation.documentation[langs[0]]) | |
45 | 216 | def get_input_widget(self): |
217 | + if self.input_widget_type is not None: | |
218 | + return self.input_widget_type(self) | |
219 | + if self.sanitize_type(self.type) in self.widget_config: | |
220 | + persot = self.widget_config[self.sanitize_type(self.type)] | |
221 | + if isinstance(persot, tuple): | |
222 | + return persot[0](self, *persot[1]) | |
223 | + return self.widget_config[self.sanitize_type(self.type)](self) | |
46 | 224 | if self.typedef.restriction is not None: |
47 | 225 | if self.typedef.restriction.base=="xsd:string": |
48 | 226 | if len(self.typedef.restriction.enum): |
49 | - b=ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") | |
227 | + #b=ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") | |
228 | + from .autocompleteentry import AutocompleteCombobox | |
229 | + b=AutocompleteCombobox(self) | |
230 | + b.set_completion_list(self.typedef.restriction.enum) | |
231 | + | |
50 | 232 | b.current(0) |
233 | + b.unbind_class("TCombobox","<MouseWheel>") | |
234 | + b.unbind_class("TCombobox","<ButtonPress-4>") | |
235 | + b.unbind_class("TCombobox","<ButtonPress-5>") | |
236 | + | |
51 | 237 | return b |
238 | + #if "Description" in self.type: | |
239 | + # return tk.Text(self) | |
52 | 240 | return tk.Entry(self) |
53 | 241 | if self.typedef.restriction.base=="xsd:dateTime": |
54 | 242 | return DatetimeEntry(self) |
55 | 243 | if len(self.typedef.restriction.enum): |
56 | 244 | return ttk.Combobox(self, values=self.typedef.restriction.enum, state="readonly") |
245 | + if self.type.endswith("float"): | |
246 | + return FloatEntry(self) | |
247 | + if self.type.endswith("int") or self.type.endswith("integer"): | |
248 | + return IntEntry(self) | |
57 | 249 | return tk.Entry(self) |
58 | 250 | |
59 | 251 | def get_value(self): |
252 | + if isinstance(self.input_widget, tk.Text): | |
253 | + return self.input_widget.get("1.0",tk.END).strip() | |
60 | 254 | return self.input_widget.get() |
61 | - def get_content(self): | |
255 | + def get_attribute_values(self): | |
256 | + return {} | |
257 | + def get_content(self, nsmap=None, qname_attrs=None): | |
62 | 258 | # returns tree type |
63 | - t = self.sanitize_type(self.type) | |
64 | - root = etree.Element(t) | |
259 | + if self.name is None: | |
260 | + t = self.sanitize_type(self.type) | |
261 | + else: | |
262 | + t=self.name | |
263 | + if nsmap is not None: | |
264 | + root = etree.Element(t,qname_attrs, nsmap=nsmap) | |
265 | + else: | |
266 | + root = etree.Element(t) | |
267 | + attrib_values=self.get_attribute_values() | |
268 | + for k in attrib_values: | |
269 | + root.set(k, attrib_values[k]) | |
65 | 270 | root.text = self.get_value() |
66 | 271 | return root |
67 | 272 | ... | ... |