Add CSS transition on images (only for webkit for now)
[booh] / lib / booh / GtkAutoTable.rb
1 #                         *  BOOH  *
2 #
3 # A.k.a `Best web-album Of the world, Or your money back, Humerus'.
4 #
5 # The acronyn sucks, however this is a tribute to Dragon Ball by
6 # Akira Toriyama, where the last enemy beaten by heroes of Dragon
7 # Ball is named "Boo". But there was already a free software project
8 # called Boo, so this one will be it "Booh". Or whatever.
9 #
10 #
11 # Copyright (c) 2004 Guillaume Cottenceau <gc3 at bluewin.ch>
12 #
13 # This software may be freely redistributed under the terms of the GNU
14 # public license version 2.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 require 'gtk2'
21
22 require 'booh/booh-lib'
23 include Booh
24
25 class Gtk::Allocation
26     def to_s
27         "width:#{width} heigth:#{height}"
28     end
29 end
30
31 class Gtk::AutoTable < Gtk::EventBox
32
33     attr_accessor :queue_draws
34
35     def initialize(row_spacings)
36         @children = []
37         @containers = []
38         @width = -1
39         @old_widths = []
40         @queue_draws = []
41         @row_spacings = row_spacings
42         @table = nil
43         super()
44         recreate_table
45         signal_connect('size-allocate') { |w, allocation|
46             msg 3, "got self allocation: #{allocation}"
47             if @width != allocation.width
48                 if !@old_widths.include?(allocation.width)
49                     @width = allocation.width
50                     @old_widths.unshift(@width)
51                     @old_widths = @old_widths[0..2]
52                     redistribute(false)
53                 else
54                     msg 3, "\tDISABLING: #{allocation.width} - #{@old_widths.join(',')}"
55                 end
56             end
57         }
58         @timeout = Gtk.timeout_add(100) {
59             if @queue_draws.size > 0
60                 @queue_draws.each { |elem| elem.call }
61                 @queue_draws.clear
62             end
63             true
64         }
65     end
66
67
68     def destroy
69         Gtk.timeout_remove(@timeout)
70         super.destroy
71     end
72
73     #- add (append) a widget to the list of automatically handled widgets
74     def append(widget, name)
75         #- create my child hash
76         child = { :widget => widget, :name => name }
77         #- put it in the table if last container's widget has been allocated
78         last_container = @containers[-1]
79         if !last_container || last_container[:contained_element] && last_container[:contained_element][:allocation]
80             put(child, (last_container ? last_container[:x] : -1) + 1, last_container ? last_container[:y] : 0)
81         end
82         #- add it to the internal children array
83         @children << child
84
85         #- connect 'size-allocate' signal to be sure to update allocation when received
86         widget.signal_connect('size-allocate') { |w, allocation|
87             msg 3, "got allocation for #{w.hash}: #{allocation} (#{allocation.hash})"
88             chld = @children.find { |e| e[:widget] == w }
89             if chld
90                 old = chld[:allocation]
91                 #- need to copy values because the object will be magically exploded when widget is removed
92                 chld[:allocation] = { :width => allocation.width, :height => allocation.height }
93                 if !old #|| old[:width] != allocation.width # || old[:height] != allocation.height
94                     msg 3, "redistribute!"
95                     chld == @children[0] && old and msg 3, "************ old was #{old[:width]} #{old[:height]}"
96                     redistribute(true)
97                 end
98             else
99                 warn "Critical: child not found!"
100             end
101         }
102     end
103
104     #- remove a widget from the list of automatically handled widgets
105     def remove_widget(widget)
106         @children.each_with_index { |chld, index|
107             if chld[:widget] == widget
108                 @children.delete_at(index)
109                 redistribute(true)
110                 return true
111             end
112         }
113         return false
114     end
115
116     #- re-insert a widget at a given pos
117     def reinsert(pos, widget, name)
118         child = { :widget => widget, :name => name }
119         @children[pos, 0] = child
120         redistribute(true)
121     end
122
123     #- remove all widgets
124     def clear
125         @children = []
126         redistribute(false)
127     end
128
129     #- get current order of widget
130     def current_order
131         return @children.collect { |chld| chld[:name] }
132     end
133
134     #- get current [x, y] position of widget within automatically handled table
135     def get_current_pos(widget)
136         chld = @children.find { |e| e[:widget] == widget }
137         if chld
138             return [ chld[:x], chld[:y] ]
139         else
140             return nil
141         end
142     end
143
144     #- get current number (rank in current ordering) of widget within automatically handled table
145     def get_current_number(widget)
146         @children.each_with_index { |chld, index|
147             if chld[:widget] == widget
148                 return index
149             end
150         }
151         return -1
152     end
153
154     #- move widgets by numbers
155     def move(src, dst)
156         if src != dst
157             chld = @children.delete_at(src)
158             @children[dst > src ? dst - 1 : dst, 0] = chld
159             redistribute(true)
160         end
161     end
162
163     #- get widget at [x, y] position of automatically handled table
164     def get_widget_at_pos(x, y)
165         @children.each { |chld|
166             if chld[:x] == x && chld[:y] == y
167                 return chld[:widget]
168             end
169         }
170         return nil
171     end
172
173     #- get maximum `y' position within the automatically handled table
174     def get_max_y
175         return @children[-1][:y]
176     end
177
178     #- get the current `previous' widget (table-wise); important since widgets can be reordered with mouse drags
179     def get_previous_widget(widget)
180         @children.each_with_index { |chld, index|
181             if chld[:widget] == widget
182                 if index == 0
183                     return nil
184                 else
185                     return @children[index - 1][:widget]
186                 end
187             end
188         }
189         return nil
190     end
191
192     #- get the current `next' widget (table-wise); important since widgets can be reordered with mouse drags
193     def get_next_widget(widget)
194         @children.each_with_index { |chld, index|
195             if chld[:widget] == widget
196                 if index == @children.size - 1
197                     return nil
198                 else
199                     return @children[index + 1][:widget]
200                 end
201             end
202         }
203         return nil
204     end
205
206     #- move specified widget `up' in the table
207     def move_up(widget)
208         @children.each_with_index { |chld, index|
209             if chld[:widget] == widget && chld[:y] > 0
210                 @children.each_with_index { |chld2, index2|
211                     if chld2[:x] == chld[:x] && chld[:y] == chld2[:y] + 1
212                         @children[index], @children[index2] = chld2, chld
213                         redistribute(true)
214                         return true
215                     end
216                 }
217             end
218         }
219         return false
220     end
221
222     #- move specified widget `down' in the table
223     def move_down(widget)
224         @children.each_with_index { |chld, index|
225             if chld[:widget] == widget && chld[:y] < get_max_y
226                 @children.each_with_index { |chld2, index2|
227                     if chld2[:x] == chld[:x] && chld[:y] == chld2[:y] - 1
228                         @children[index], @children[index2] = chld2, chld
229                         redistribute(true)
230                         return true
231                     end
232                 }
233             end
234         }
235         return false
236     end
237
238     #- move specified widget `left' in the table
239     def move_left(widget)
240         @children.each_with_index { |chld, index|
241             if chld[:widget] == widget && chld[:x] > 0
242                 @children[index], @children[index - 1] = @children[index - 1], @children[index]
243                 redistribute(true)
244                 return true
245             end
246         }
247         return false
248     end
249
250     #- move specified widget `right' in the table
251     def move_right(widget)
252         @children.each_with_index { |chld, index|
253             if chld[:widget] == widget && @children[index + 1] && chld[:x] < @children[index + 1][:x]
254                 @children[index], @children[index + 1] = @children[index + 1], @children[index]
255                 redistribute(true)
256                 return true
257             end
258         }
259         return false
260     end
261
262
263     private
264
265     def put(element, x, y)
266         msg 3, "putting #{element[:widget].hash} at #{x},#{y}"
267         element[:x] = x
268         element[:y] = y
269         container = @containers.find { |e| e[:x] == x && e[:y] == y }
270         if !container
271             container = { :x => x, :y => y, :widget => Gtk::VBox.new, :fake => Gtk::Label.new('fake') }
272             msg 3, "attaching at #{x},#{y}"
273             @table.attach(container[:widget], x, x + 1, y, y + 1, Gtk::FILL, Gtk::FILL, 5, 0)
274             @containers << container
275         end
276         if container[:contained_element]
277             container[:widget].remove(container[:contained_element][:widget])
278         end
279         container[:contained_element] = element
280         container[:widget].add(element[:widget])
281         @table.show_all
282     end
283
284     def recreate_table
285         @containers.each { |e|
286             if e[:contained_element]
287                 e[:widget].remove(e[:contained_element][:widget])
288             end
289         }
290         @containers = []
291         if @table
292             remove(@table)
293             @table.hide     #- should be #destroy, but that triggers an Abort in booh, and I cannot really understand why and fix 
294                             #- this is a memory leak, so ideally it should be either fixed in ruby-gtk2 0.16.0, or at least checked if
295                             #- it's not fixed from a side effect of another fix in the future
296         end
297         add(@table = Gtk::Table.new(0, 0, true))
298         @table.set_row_spacings(@row_spacings)
299     end
300
301     def redistribute(force)
302         msg 3, "redistribute: "
303         @children.each { |e| msg_ 3, e[:allocation] ? 'O' : '.' }; msg 3, ''
304         if unallocated = @children.find { |e| !e[:allocation] }
305             #- waiting for allocations. replace last displayed widget with first unallocated.
306             last_container = @containers[-1]
307             put(unallocated, last_container ? last_container[:x] : 0, last_container ? last_container[:y]: 0)
308
309         else
310             if @children.size == 0
311                 recreate_table
312                 return
313
314             else
315                 totalwidth = allocation.width
316                 maxwidth = @children.collect { |chld| chld[:allocation][:width] }.max
317                 xpix = 5 + maxwidth + 5
318                 x = 1
319                 y = 0
320                 @children[1..-1].each { |e|
321                     if xpix + 5 + maxwidth + 5 > totalwidth - 1
322                         x = 0
323                         y += 1
324                         xpix = 0
325                     end
326                     e[:xnew] = x
327                     e[:ynew] = y
328                     x += 1
329                     xpix += 5 + maxwidth + 5
330                 }
331                 if @children[1..-1].find { |e| e[:xnew] != e[:x] || e[:ynew] != e[:y] } || force
332                     msg 3, "I can proceed with #{allocation}"
333                     recreate_table
334                     put(@children[0], 0, 0)
335                     @children[1..-1].each { |e|
336                         put(e, e[:xnew], e[:ynew])
337                     }
338                     show_all
339                     @queue_draws << proc { queue_draw }
340                 end
341             end
342         end
343     end
344
345 end