allow to specify frame offset for videos in the GUI
[booh] / lib / booh / GtkAutoTable.rb
1 require 'gtk2'
2
3 require 'booh/booh-lib'
4 include Booh
5
6 class Gtk::Allocation
7     def to_s
8         "width:#{width} heigth:#{height}"
9     end
10 end
11
12 class Gtk::AutoTable < Gtk::VBox
13
14     def initialize(row_spacings)
15         @children = []
16         @containers = []
17         @width = -1
18         @old_widths = []
19         @row_spacings = row_spacings
20         @table = nil
21         super
22         recreate_table
23         signal_connect('size-allocate') { |w, allocation|
24             msg 3, "got self allocation: #{allocation}"
25             if @width != allocation.width
26                 if !@old_widths.include?(allocation.width)
27                     @width = allocation.width
28                     @old_widths.unshift(@width)
29                     @old_widths = @old_widths[0..2]
30                     redistribute(false)
31                 else
32                     msg 3, "\tDISABLING: #{allocation.width} - #{@old_widths.join(',')}"
33                 end
34             end
35         }
36         @timeout = Gtk.timeout_add(100) {
37             if @queue_draw
38                 queue_draw
39                 @queue_draw = false
40             end
41             true
42         }
43     end
44
45
46     def destroy
47         Gtk.timeout_remove(@timeout)
48         super.destroy
49     end
50
51     #- add (append) a widget to the list of automatically handled widgets
52     def append(widget, name)
53         #- create my child hash
54         child = { :widget => widget, :name => name }
55         #- put it in the table if last container's widget has been allocated
56         last_container = @containers[-1]
57         if !last_container || last_container[:contained_element] && last_container[:contained_element][:allocation]
58             put(child, (last_container ? last_container[:x] : -1) + 1, last_container ? last_container[:y] : 0)
59         end
60         #- add it to the internal children array
61         @children << child
62
63         #- connect 'size-allocate' signal to be sure to update allocation when received
64         widget.signal_connect('size-allocate') { |w, allocation|
65             msg 3, "got allocation for #{w.hash}: #{allocation} (#{allocation.hash})"
66             chld = @children.find { |e| e[:widget] == w }
67             if chld
68                 old = chld[:allocation]
69                 #- need to copy values because the object will be magically exploded when widget is removed
70                 chld[:allocation] = { :width => allocation.width, :height => allocation.height }
71                 if !old #|| old[:width] != allocation.width # || old[:height] != allocation.height
72                     msg 3, "redistribute!"
73                     chld == @children[0] && old and msg 3, "************ old was #{old[:width]} #{old[:height]}"
74                     redistribute(true)
75                 end
76             else
77                 warn "Critical: child not found!"
78             end
79         }
80     end
81
82     #- remove a widget from the list of automatically handled widgets
83     def remove(widget)
84         @children.each_with_index { |chld, index|
85             if chld[:widget] == widget
86                 @children.delete_at(index)
87                 redistribute(true)
88                 return true
89             end
90         }
91         return false
92     end
93
94     #- re-insert a widget at a given pos
95     def reinsert(pos, widget, name)
96         child = { :widget => widget, :name => name }
97         @children[pos, 0] = child
98         redistribute(true)
99     end
100
101     #- remove all widgets
102     def clear
103         @children = []
104         redistribute(false)
105     end
106
107     #- get current order of widget
108     def current_order
109         return @children.collect { |chld| chld[:name] }
110     end
111
112     #- get current [x, y] position of widget within automatically handled table
113     def get_current_pos(widget)
114         chld = @children.find { |e| e[:widget] == widget }
115         if chld
116             return [ chld[:x], chld[:y] ]
117         else
118             return nil
119         end
120     end
121
122     #- get current number (rank in current ordering) of widget within automatically handled table
123     def get_current_number(widget)
124         @children.each_with_index { |chld, index|
125             if chld[:widget] == widget
126                 return index
127             end
128         }
129         return -1
130     end
131
132     #- move widgets by numbers
133     def move(src, dst)
134         if src != dst
135             chld = @children.delete_at(src)
136             @children[dst > src ? dst - 1 : dst, 0] = chld
137             redistribute(true)
138         end
139     end
140
141     #- get widget at [x, y] position of automatically handled table
142     def get_widget_at_pos(x, y)
143         @children.each { |chld|
144             if chld[:x] == x && chld[:y] == y
145                 return chld[:widget]
146             end
147         }
148         return nil
149     end
150
151     #- get maximum `y' position within the automatically handled table
152     def get_max_y
153         return @children[-1][:y]
154     end
155
156     #- get the current `previous' widget (table-wise); important since widgets can be reordered with mouse drags
157     def get_previous_widget(widget)
158         @children.each_with_index { |chld, index|
159             if chld[:widget] == widget
160                 if index == 0
161                     return nil
162                 else
163                     return @children[index - 1][:widget]
164                 end
165             end
166         }
167         return nil
168     end
169
170     #- get the current `next' widget (table-wise); important since widgets can be reordered with mouse drags
171     def get_next_widget(widget)
172         @children.each_with_index { |chld, index|
173             if chld[:widget] == widget
174                 if index == @children.size - 1
175                     return nil
176                 else
177                     return @children[index + 1][:widget]
178                 end
179             end
180         }
181         return nil
182     end
183
184     #- move specified widget `up' in the table
185     def move_up(widget)
186         @children.each_with_index { |chld, index|
187             if chld[:widget] == widget && chld[:y] > 0
188                 @children.each_with_index { |chld2, index2|
189                     if chld2[:x] == chld[:x] && chld[:y] == chld2[:y] + 1
190                         @children[index], @children[index2] = chld2, chld
191                         redistribute(true)
192                         return true
193                     end
194                 }
195             end
196         }
197         return false
198     end
199
200     #- move specified widget `down' in the table
201     def move_down(widget)
202         @children.each_with_index { |chld, index|
203             if chld[:widget] == widget && chld[:y] < get_max_y
204                 @children.each_with_index { |chld2, index2|
205                     if chld2[:x] == chld[:x] && chld[:y] == chld2[:y] - 1
206                         @children[index], @children[index2] = chld2, chld
207                         redistribute(true)
208                         return true
209                     end
210                 }
211             end
212         }
213         return false
214     end
215
216     #- move specified widget `left' in the table
217     def move_left(widget)
218         @children.each_with_index { |chld, index|
219             if chld[:widget] == widget && chld[:x] > 0
220                 @children[index], @children[index - 1] = @children[index - 1], @children[index]
221                 redistribute(true)
222                 return true
223             end
224         }
225         return false
226     end
227
228     #- move specified widget `right' in the table
229     def move_right(widget)
230         @children.each_with_index { |chld, index|
231             if chld[:widget] == widget && @children[index + 1] && chld[:x] < @children[index + 1][:x]
232                 @children[index], @children[index + 1] = @children[index + 1], @children[index]
233                 redistribute(true)
234                 return true
235             end
236         }
237         return false
238     end
239
240
241     private
242
243     def put(element, x, y)
244         msg 3, "putting #{element[:widget].hash} at #{x},#{y}"
245         element[:x] = x
246         element[:y] = y
247         container = @containers.find { |e| e[:x] == x && e[:y] == y }
248         if !container
249             container = { :x => x, :y => y, :widget => Gtk::VBox.new, :fake => Gtk::Label.new('fake') }
250             msg 3, "attaching at #{x},#{y}"
251             @table.attach(container[:widget], x, x + 1, y, y + 1, Gtk::FILL, Gtk::FILL, 5, 0)
252             @containers << container
253         end
254         if container[:contained_element]
255             container[:widget].remove(container[:contained_element][:widget])
256         end
257         container[:contained_element] = element
258         container[:widget].add(element[:widget])
259         @table.show_all
260     end
261
262     def recreate_table
263         @containers.each { |e|
264             if e[:contained_element]
265                 e[:widget].remove(e[:contained_element][:widget])
266             end
267         }
268         @containers = []
269         if @table
270             remove(@table)
271             @table.destroy
272         end
273         add(@table = Gtk::Table.new(0, 0, true))
274         @table.set_row_spacings(@row_spacings)
275     end
276
277     def redistribute(force)
278         msg 3, "redistribute: "
279         @children.each { |e| msg_ 3, e[:allocation] ? 'O' : '.' }; msg 3, ''
280         if unallocated = @children.find { |e| !e[:allocation] }
281             #- waiting for allocations. replace last displayed widget with first unallocated.
282             last_container = @containers[-1]
283             put(unallocated, last_container[:x], last_container[:y])
284
285         else
286             if @children.size == 0
287                 recreate_table
288                 return
289
290             else
291                 totalwidth = allocation.width
292                 maxwidth = @children.collect { |chld| chld[:allocation][:width] }.max
293                 xpix = 5 + maxwidth + 5
294                 x = 1
295                 y = 0
296                 @children[1..-1].each { |e|
297                     if xpix + 5 + maxwidth + 5 > totalwidth - 1
298                         x = 0
299                         y += 1
300                         xpix = 0
301                     end
302                     e[:xnew] = x
303                     e[:ynew] = y
304                     x += 1
305                     xpix += 5 + maxwidth + 5
306                 }
307                 if @children[1..-1].find { |e| e[:xnew] != e[:x] || e[:ynew] != e[:y] } || force
308                     msg 3, "I can proceed with #{allocation}"
309                     recreate_table
310                     put(@children[0], 0, 0)
311                     @children[1..-1].each { |e|
312                         put(e, e[:xnew], e[:ynew])
313                     }
314                     show_all
315                     @queue_draw = true
316                 end
317             end
318         end
319     end
320
321 end