]> git.jsancho.org Git - kivyforms.git/blob - kivyforms/formcanvas.py
0f4c18302cb6a97fd4170a960763da1c642ef77f
[kivyforms.git] / kivyforms / formcanvas.py
1 from kivy.graphics import Color, Line
2 from kivy.uix.boxlayout import BoxLayout
3 from kivy.uix.button import Button
4 from kivy.uix.label import Label
5 from kivy.uix.stacklayout import StackLayout
6
7
8 """
9 Some common methods
10 """
11 def get_form_canvas(widget):
12     parent = widget.parent
13     while parent and not isinstance(parent, FormCanvas):
14         if parent is parent.parent:
15             raise Exception("Infinite loop when searching FormCanvas")
16         parent = parent.parent
17     return parent
18
19 def get_index(widget):
20     for idx in range(len(widget.parent.children)):
21         if widget.parent.children[idx] is widget:
22             return idx
23     return None
24
25 def get_zone(widget, x, y):
26     side = widget.x + widget.width / 4
27     if x > side * 3:
28         return 'right'
29     elif x < side:
30         return 'left'
31     elif y > widget.y + widget.height / 2:
32         return 'top'
33     else:
34         return 'bottom'
35
36
37 class Destination(Label):
38     def __init__(self, **kwargs):
39         super(Destination, self).__init__(**kwargs)
40         self.text = 'Widget goes here'
41         self.color = [1, 1, 0, 1]
42         with self.canvas.after:
43             Color(1, 1, 0, 1)
44             self.box = Line(dash_length=8, dash_offset=4)
45
46     def update_box(self):
47         self.box.points = [
48             self.x, self.y,
49             self.x, self.y + self.height,
50             self.x + self.width, self.y + self.height,
51             self.x + self.width, self.y,
52             self.x, self.y
53         ]
54
55     def on_pos(self, instance, value):
56         self.update_box()
57
58     def on_size(self, instance, value):
59         self.update_box()
60
61
62 class Grabbable(BoxLayout):
63     def __init__(self, **kwargs):
64         super(Grabbable, self).__init__(**kwargs)
65         self.detached = False
66
67     def point(self, touch):
68         get_form_canvas(self).point_widget(self, touch.pos)
69
70     def detach(self):
71         self.detached = True
72         get_form_canvas(self).detach_widget(self)
73
74     def attach(self, touch):
75         if self.detached:
76             form_canvas = None
77             for widget in self.get_root_window().children[1].walk():
78                 if isinstance(widget, FormCanvas) and widget.collide_point(*touch.pos):
79                     form_canvas = widget
80                     break
81             else:
82                 raise Exception("FormCanvas not found")
83             form_canvas.attach_widget(self)
84             self.detached = False
85
86     def move(self, x, y):
87         self.pos = [x - self.width / 2, y - self.height / 2]
88         
89     def on_touch_down(self, touch):
90         if self.collide_point(*touch.pos):
91             if touch.grab_current is None:
92                 touch.grab(self)
93                 self.detach()
94                 self.move(*touch.pos)
95                 return True
96         return super(Grabbable, self).on_touch_down(touch)
97
98     def on_touch_move(self, touch):
99         if touch.grab_current is self:
100             self.move(*touch.pos)
101         else:
102             if self.parent and self.parent != self.get_root_window():
103                 if self.collide_point(touch.x, touch.y):
104                     self.point(touch)
105
106     def on_touch_up(self, touch):
107         if touch.grab_current is self:
108             touch.ungrab(self)
109             self.attach(touch)
110             
111
112 class FormCanvas(BoxLayout):
113     def __init__(self, *args, **kwargs):
114         super(FormCanvas, self).__init__(*args, **kwargs)
115
116         self._canvas = StackLayout(
117             orientation='lr-tb',
118             padding=[10, 10, 10, 10],
119             spacing=[10, 10]
120         )
121         super(FormCanvas, self).add_widget(self._canvas)
122
123         self.widgets_height = 40
124         self.widgets_size_hint = (1, None)
125
126     def add_widget(self, widget):
127         g = Grabbable(
128             height=self.widgets_height,
129             size_hint=self.widgets_size_hint
130         )
131         g.add_widget(widget)
132         self._canvas.add_widget(g)
133
134     def detach_widget(self, widget):
135         "Detach grabbable widget from canvas and show widget destination"
136
137         # Show widget destination
138         self.destination = Destination()
139         self.destination.height = self.widgets_height
140         self.destination.size_hint = self.widgets_size_hint
141         widget.parent.add_widget(self.destination, index=get_index(widget))
142
143         # Place widget in the root window
144         widget.parent.remove_widget(widget)
145         widget.size_hint = (None, None)
146         widget.width = 150
147         widget.height = 50
148         self.get_root_window().add_widget(widget)
149
150     def point_widget(self, widget, position):
151         if not self.destination:
152             raise Exception("Wrong status: destination point not specified")
153
154         # Widget info about destination
155         widget_idx = get_index(widget)
156         zone = get_zone(widget, *position)
157
158         # Remove destination
159         box = self.destination.parent
160         box.remove_widget(self.destination)
161
162         # Put destination in the right place
163         if zone in ('left', 'right') and widget.parent.orientation != 'horizontal':
164             parent = widget.parent
165             box = self.create_box()
166             parent.add_widget(box, index=widget_idx)
167             parent.remove_widget(widget)
168             box.add_widget(widget)
169             if zone == 'left':
170                 box.add_widget(self.destination, index=1)
171             else:
172                 box.add_widget(self.destination, index=0)
173         else:
174             widget.parent.add_widget(self.destination, index=widget_idx)
175
176         # Remove useless boxes
177         if isinstance(box, BoxLayout) and len(box.children) == 1:
178             child = box.children[0]
179             box.remove_widget(child)
180             box.parent.add_widget(child, index=get_index(box))
181             box.parent.remove_widget(box)
182
183     def attach_widget(self, widget):
184         """
185         Attach grabbable widget to the destination
186         """
187         widget.parent.remove_widget(widget)
188         widget.height = self.widgets_height
189         widget.size_hint = self.widgets_size_hint
190
191         dest_idx = get_index(self.destination)
192         self.destination.parent.add_widget(widget, index=dest_idx)
193         self.destination.parent.remove_widget(self.destination)
194         self.destination = None
195
196     def create_box(self):
197         return BoxLayout(
198             orientation='horizontal',
199             height=self.widgets_height,
200             size_hint=self.widgets_size_hint,
201             spacing=self._canvas.spacing[0]
202         )
203
204     def export_to_kv(self):
205         kv = """StackLayout:
206     orientation: '{orientation}'
207     padding: {padding}
208     spacing: {spacing}
209 """.format(orientation=self._canvas.orientation, padding=self._canvas.padding, spacing=self._canvas.spacing)
210
211         indent = '    '
212         stack = [self._canvas]
213
214         widgets = self.walk(restrict=True)
215         next(widgets)    # the first widget is the FormCanvas
216         next(widgets)    # and the second is the inner StackLayout
217         for widget in widgets:
218             if not isinstance(widget, Grabbable):
219                 # Look for the widget position inside the tree
220                 parent = widget.parent
221                 if isinstance(parent, Grabbable):
222                     parent = parent.parent
223                 while not parent is stack[-1]:
224                     stack.pop()
225
226                 # Widget header
227                 kv += '{indent}{widget}:\n'.format(indent=indent*len(stack), widget=type(widget).__name__)
228
229                 stack.append(widget)
230
231                 # Widget attributes
232                 for attr in ('height', 'size_hint', 'text'):
233                     if hasattr(widget, attr):
234                         value = getattr(widget, attr)
235                         if type(value) is str:
236                             value = "'" + value + "'"
237                         kv += "{indent}{attr}: {value}\n".format(
238                             indent=indent*len(stack),
239                             attr=attr,
240                             value=value
241                         )
242
243         return kv