Initial commit
[kivyforms.git] / kivyforms / formcanvas.py
1 from kivy.graphics import Color, Line
2 from kivy.uix.behaviors import ButtonBehavior
3 from kivy.uix.boxlayout import BoxLayout
4 from kivy.uix.button import Button
5 from kivy.uix.stacklayout import StackLayout
6
7 class Grabbable(BoxLayout):
8     def __init__(self, **kwargs):
9         super(Grabbable, self).__init__(**kwargs)
10         self.detached = False
11         self.form_canvas = None
12
13         self.point_area = None
14         with self.canvas.after:
15             Color(1, 1, 0)
16             self.line = Line(points=[])
17
18     def point(self, touch=None):
19         if touch:
20             side = self.x + self.width / 6
21             if touch.x > side * 5:
22                 self.point_area = 'right'
23                 self.line.points = [self.x + self.width + 5, self.y + 5, self.x + self.width + 5, self.y + self.height - 5]
24             elif touch.x < side:
25                 self.point_area = 'left'
26                 self.line.points = [self.x - 5, self.y + 5, self.x - 5, self.y + self.height - 5]
27             elif touch.y > self.y + self.height / 2:
28                 self.point_area = 'top'
29                 self.line.points = [self.x + 5, self.y + self.height + 5, self.x + self.width - 5, self.y + self.height + 5]
30             else:
31                 self.point_area = 'bottom'
32                 self.line.points = [self.x + 5, self.y - 5, self.x + self.width - 5, self.y - 5]
33
34         else:
35             self.point_area = None
36             self.line.points = []
37
38     def get_form_canvas(self):
39         parent = self.parent
40         while parent and not isinstance(parent, FormCanvas):
41             parent = parent.parent
42         return parent
43
44     def get_index(self):
45         for idx in range(len(self.parent.children)):
46             if self.parent.children[idx] is self:
47                 return idx
48         return None
49             
50     def detach(self):
51         self.detached = True
52         self.form_canvas = self.get_form_canvas()
53         root_window = self.get_root_window()
54         self.parent.remove_widget(self)
55         self.size_hint = (None, None)
56         self.width = 150
57         self.height = 50
58         root_window.add_widget(self)
59
60     def attach(self, x, y):
61         self.detached = False
62         self.parent.remove_widget(self)
63         self.height = self.form_canvas.widgets_height
64         self.size_hint = self.form_canvas.widgets_size_hint
65
66         for widget in self.form_canvas.walk(restrict=True):
67             if type(widget) is Grabbable and widget.collide_point(x, y):
68                 idx = widget.get_index()
69                 if widget.point_area == 'top':
70                     widget.parent.add_widget(self, index=idx + 1)
71                 elif widget.point_area == 'bottom':
72                     widget.parent.add_widget(self, index=idx)
73                 elif widget.point_area == 'left':
74                     parent = widget.parent
75                     box = parent.create_box()
76                     parent.add_widget(box, index=idx)
77                     box.add_widget(self)
78                     parent.remove_widget(widget)
79                     box.add_widget(widget)
80                 else:
81                     parent = widget.parent
82                     box = parent.create_box()
83                     parent.add_widget(box, index=idx)
84                     parent.remove_widget(widget)
85                     box.add_widget(widget)
86                     box.add_widget(self)
87                 widget.point()
88                 break
89         else:
90             self.form_canvas.add_widget(self)
91         
92     def on_touch_down(self, touch):
93         if self.collide_point(*touch.pos):
94             if touch.grab_current is None:
95                 touch.grab(self)
96                 return True
97         return super(Grabbable, self).on_touch_down(touch)
98
99     def on_touch_move(self, touch):
100         if touch.grab_current is self:
101             self.pos = [touch.x - self.width / 2, touch.y - self.height / 2]
102             self.point()
103             if not self.detached:
104                 self.detach()
105         else:
106             if self.parent and self.parent != self.get_root_window():
107                 if self.collide_point(touch.x, touch.y):
108                     self.point(touch)
109                 else:
110                     self.point()
111
112     def on_touch_up(self, touch):
113         if touch.grab_current is self:
114             touch.ungrab(self)
115             if self.detached:
116                 self.attach(touch.x, touch.y)
117             
118
119 class FormCanvas(ButtonBehavior, StackLayout):
120     def __init__(self, *args, **kwargs):
121         super(FormCanvas, self).__init__(*args, **kwargs)
122         self.n = 0
123         self.orientation = 'lr-tb'
124         self.padding = [10, 10, 10, 10]
125         self.spacing = [10, 10]
126
127         self.widgets_height = 40
128         self.widgets_size_hint = (1, None)
129
130     def on_press(self, *args):
131         self.n += 1
132         g = Grabbable(
133             height=self.widgets_height,
134             size_hint=self.widgets_size_hint
135         )
136         g.add_widget(Button(text=str(self.n)))
137         self.add_widget(g)
138         #print(self.export_to_kv(self))
139
140     def create_box(self):
141         return BoxLayout(
142             orientation='horizontal',
143             height=self.widgets_height,
144             size_hint=self.widgets_size_hint,
145             spacing=self.spacing[0]
146         )
147
148     def export_to_kv(self, widget, indent=''):
149         kv = """StackLayout:
150     orientation: '{orientation}'
151     padding: {padding}
152     spacing: {spacing}
153 """.format(orientation=self.orientation, padding=self.padding, spacing=self.spacing)
154
155         indent = '    '
156         stack = [self]
157
158         widgets = self.walk(restrict=True)
159         next(widgets)    # the first widget is the FormCanvas
160         for widget in widgets:
161             if not type(widget) is Grabbable:
162                 # Look for the widget position inside the tree
163                 parent = widget.parent
164                 if type(parent) is Grabbable:
165                     parent = parent.parent
166                 while not parent is stack[-1]:
167                     stack.pop()
168
169                 # Widget header
170                 kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
171
172                 stack.append(widget)
173
174                 # Widget attributes
175                 kv += """{indent}height: 40
176 {indent}size_hint: (1., None)
177 {indent}text: '{text}'
178 """.format(indent=indent*len(stack), text=widget.text)
179
180         return kv