Commit 28379b664a7167f5fa9625a9c1b24b5366b23d5d

Authored by Alexandre Schulz
1 parent 27f0d5bd
Exists in better_forms

copied from amda_xml_manager project

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
... ... @@ -4,7 +4,7 @@
4 4 from .element import Element
5 5  
6 6 class Choice:
7   - def __init__(self, min_occurs=0, max_occurs=0):
  7 + def __init__(self, min_occurs=1, max_occurs=1):
8 8 self.min_occurs=min_occurs
9 9 self.max_occurs=max_occurs
10 10 self.elements=[]
... ...
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)
... ...
xsd2tkform/ui/adaptivetextentry.py 0 โ†’ 100644
... ... @@ -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 +
... ...
xsd2tkform/ui/attributeframe.py 0 โ†’ 100644
... ... @@ -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()
... ...
xsd2tkform/ui/autocompleteentry.py 0 โ†’ 100644
... ... @@ -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)
... ...
xsd2tkform/ui/buttoncontainer.py 0 โ†’ 100644
... ... @@ -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  
... ...
xsd2tkform/ui/entrywidgets.py 0 โ†’ 100644
... ... @@ -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  
... ...