Disambiguate signals using types instead of special cases.
[kaya.git] / lib / toolkits / qt.rb
blobb6b2718f02817d9c8f3238a406c0d986eb397dff
1 # Copyright (c) 2009 Paolo Capriotti <p.capriotti@gmail.com>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 require 'observer_utils'
9 require 'descriptor'
10 require 'toolkits/qt_gui_builder'
12 ParseException = Class.new(Exception)
14 class Qt::Variant
15   # 
16   # Convert any marshallable ruby object into a QVariant.
17   # 
18   def self.from_ruby(x)
19     new(Marshal.dump(x))
20   end
22   # 
23   # Extract the ruby object contained in a QVariant.
24   # 
25   def to_ruby
26     str = toString
27     Marshal.load(str) if str
28   end
29 end
31 class Qt::ByteArray
32   def self.from_hex(str)
33     new([str.gsub(/\W+/, '')].pack('H*'))
34   end
35 end
37 class Qt::Painter
38   # 
39   # Ensure this painter is closed after the block is executed.
40   # 
41   def paint
42     yield self
43   ensure
44     self.end
45   end
46   
47   # 
48   # Execute a block, then restore the painter state to what it
49   # was before execution.
50   # 
51   def saving
52     save
53     yield self
54   ensure
55     restore
56   end
57 end
59 class Qt::Image
60   # 
61   # Convert this image to a pixmap.
62   # 
63   def to_pix
64     Qt::Pixmap.from_image self
65   end
66   
67   # 
68   # Paint on an image using the given block. The block is passed
69   # a painter to use for drawing.
70   # 
71   def self.painted(size, &blk)
72     Qt::Image.new(size.x, size.y, Qt::Image::Format_ARGB32_Premultiplied).tap do |img|
73       img.fill(0)
74       Qt::Painter.new(img).paint(&blk)
75     end
76   end
78   # 
79   # Render an svg object onto a new image of the specified size. If id is not
80   # specified, the whole svg file is rendered.
81   # 
82   def self.from_renderer(size, renderer, id = nil)
83     img = Qt::Image.painted(size) do |p| 
84       if id
85         renderer.render(p, id)
86       else
87         renderer.render(p)
88       end
89     end
90     img
91   end
92 end
94 module PrintablePoint
95   def ==(other)
96     self.x == other.x and self.y == other.y
97   end
98   
99   def to_s
100     "(#{self.x}, #{self.y})"
101   end
104 module PrintableRect
105   def to_s
106     "[#{self.x}, #{self.y} - #{self.width}, #{self.height}]"
107   end
110 class Qt::Point
111   include PrintablePoint
112   
113   def to_f
114     Qt::PointF.new(self.x, self.y)
115   end
118 class Qt::PointF
119   include PrintablePoint
120   
121   def to_i
122     Qt::Point.new(self.x.to_i, self.y.to_i)
123   end
126 class Qt::Size
127   include PrintablePoint
128   
129   def x
130     width
131   end
132   
133   def y
134     height
135   end
138 class Qt::SizeF
139   include PrintablePoint
140   
141   def x
142     width
143   end
144   
145   def y
146     height
147   end
150 class Qt::Rect
151   include PrintableRect
152   
153   def to_f
154     Qt::RectF.new(self.x, self.y, self.width, self.height)
155   end
158 class Qt::RectF
159   include PrintableRect
162 class Qt::Pixmap
163   # 
164   # Render a pixmap from an svg file. See also Qt::Image#renderer.
165   # 
166   def self.from_svg(size, file, id = nil)
167     from_renderer(size, Qt::SvgRenderer.new(file), id)
168   end
169   
170   # 
171   # Render a pixmap using an svg renderer. See also Qt::Image#renderer.
172   # 
173   def self.from_renderer(size, renderer, id = nil)
174     Qt::Image.from_renderer(size, renderer, id).to_pix
175   end
178 class Qt::MetaObject
179   def create_signal_map
180     map = {}
181     (0...methodCount).map do |i|
182       m = method(i)
183       if m.methodType == Qt::MetaMethod::Signal
184         sign = m.signature 
185         sign =~ /^(.*)\(.*\)$/
186         sig = $1.underscore.to_sym
187         val = [sign, m.parameterTypes]
188         map[sig] ||= []
189         map[sig] << val
190       end
191     end
192     map
193   end
196 class Qt::Base
197   include Observable
198   
199   class SignalDisconnecter
200     def initialize(obj, sig)
201       @obj = obj
202       @sig = sig
203     end
204     
205     def disconnect!
206       @obj.disconnect(@sig)
207     end
208   end
209   
210   class ObserverDisconnecter
211     def initialize(obj, observer)
212       @obj = obj
213       @observer = observer
214     end
215     
216     def disconnect!
217       @obj.delete_observer(@observer)
218     end
219   end
220   
221   class Signal
222     attr_reader :symbol
224     def initialize(signal, types)
225       raise "Only symbols are supported as signals" unless signal.is_a?(Symbol)
226       @symbol = signal
227       @types = types
228     end
230     def self.create(signal, types)
231       if signal.is_a?(self)
232         signal
233       else
234         new(signal, types)
235       end
236     end
237     
238     def to_s
239       @symbol.to_s
240     end
241   end
243   def on(sig, types = nil, &blk)
244     sig = Signal.create(sig, types)
245     candidates = if is_a? Qt::Object
246       signal_map[sig.symbol]
247     end
248     if candidates
249       if types
250         # find candidate with the correct argument types
251         candidates = candidates.find_all{|s| s[1] == types }
252       end
253       if candidates.size > 1
254         # find candidate with the correct arity
255         arity = blk.arity
256         if blk.arity == -1
257           # take first
258           candidates = [candidates.first]
259         else
260           candidates = candidates.find_all{|s| s[1].size == arity }
261         end
262       end
263       if candidates.size > 1
264         raise "Ambiguous overload for #{sig} with arity #{arity}"
265       elsif candidates.empty?
266         msg = if types
267           "with types #{types.join(' ')}"
268         else
269           "with arity #{blk.arity}"
270         end
271         raise "No overload for #{sig} #{msg}"
272       end
273       sign = SIGNAL(candidates.first[0])
274       connect(sign, &blk)
275       SignalDisconnecter.new(self, sign)
276     else
277       observer = observe(sig.symbol, &blk)
278       ObserverDisconnecter.new(self, observer)
279     end
280   end
282   def in(interval, &blk)
283     Qt::Timer.in(interval, self, &blk)
284   end
286   def run_later(&blk)
287     self.in(0, &blk)
288   end
289   
290   def signal_map
291     self.class.signal_map(self)
292   end
293   
294   def self.signal_map(obj)
295     @signal_map ||= self.create_signal_map(obj)
296   end
297   
298   def self.create_signal_map(obj)
299     obj.meta_object.create_signal_map
300   end
303 class Qt::Timer
304   # 
305   # Execute the given block every interval milliseconds and return a timer
306   # object. Note that if the timer is garbage collected, the block will not
307   # be executed anymore, so the caller should keep a reference to it for as
308   # long as needed.
309   # To prevent further invocations of the block, use QTimer#stop.
310   # 
311   def self.every(interval, &blk)
312     time = Qt::Time.new
313     time.restart
314     
315     timer = new
316     timer.connect(SIGNAL('timeout()')) { blk[time.elapsed] }
317     timer.start(interval)
318     # return the timer, so that the caller
319     # has a chance to keep it referenced, so
320     # that it is not garbage collected
321     timer
322   end
324   # 
325   # Execute the given block after interval milliseconds. If target is
326   # specified, the block is invoked in the context of target.
327   # 
328   def self.in(interval, target = nil, &blk)
329     single_shot(interval,
330                 Qt::BlockInvocation.new(target, blk, 'invoke()'),
331                 SLOT('invoke()'))
332   end
335 module ListLike
336   module ClassMethods
337     #
338     # Create a list from an array of pairs (text, data)
339     # The data for each item can be retrieved using the
340     # item's get method.
341     # Note that if an array element is not a pair, its
342     # value will be used both for the text and for the
343     # data.
344     # 
345     # For example: <tt>list.current_item.get</tt>
346     # 
347     def from_a(parent, array)
348       new(parent).tap do |list|
349         list.reset_from_a(array)
350       end
351     end
352   end
353   
354   #
355   # Select the item for which the given block
356   # evaluates to true.
357   #
358   def select_item(&blk)
359     (0...count).each do |i|
360       if blk[item(i).get]
361         self.current_index = i
362         break i
363       end
364     end
365     nil
366   end
367   
368   # 
369   # Populate the list with values from an array.
370   # See also from_a.
371   #
372   def reset_from_a(array)
373     clear
374     array.each do |values|
375       text, data = if values.is_a?(String)
376         [values, values]
377       else
378         values
379       end
380       create_item(text, data)
381     end
382   end
384   def self.included(base)
385     base.extend ClassMethods
386   end
389 class Qt::ListWidget
390   FROM_A_DATA_ROLE = Qt::UserRole
391   include ListLike
392   
393   class Item < Qt::ListWidgetItem
394     def initialize(text, list, data)
395       super(text, list)
396       set_data(FROM_A_DATA_ROLE, Qt::Variant.from_ruby(data))
397     end
399     def get
400       data(FROM_A_DATA_ROLE).to_ruby
401     end
402   end
403   
404   def current_index=(i)
405     self.current_row = i
406   end
408   def create_item(text, data)
409     Item.new(text, self, data)
410   end
413 class Qt::FileDialog
414   def self.get_open_url(dir, filter, parent, caption)
415     filename = get_open_file_name(parent, caption, dir.to_local_file, filter)
416     Qt::Url.from_local_file(filename)
417   end
418   
419   def self.get_save_url(dir, filter, parent, caption)
420     filename = get_save_file_name(parent, caption, dir.to_local_file, filter)
421     Qt::Url.from_local_file(filename)
422   end
425 class Qt::Url
426   def is_local_file
427     true
428   end
431 module ModelUtils
432   # 
433   # Helper method to delete model rows from within a block. This method
434   # ensures that the appropriate begin/end functions are called.
435   # 
436   def removing_rows(parent, first, last)
437     if first > last
438       yield
439     else
440       begin
441         begin_remove_rows(parent || Qt::ModelIndex.new, first, last)
442         yield
443       ensure
444         end_remove_rows
445       end
446     end
447   end
448   
449   # 
450   # Helper method to insert model rows from within a block. This method
451   # ensures that the appropriate begin/end functions are called.
452   # 
453   def inserting_rows(parent, first, last)
454     if first > last
455       yield
456     else
457       begin
458         begin_insert_rows(parent || Qt::ModelIndex.new, first, last)
459         yield
460       ensure
461         end_insert_rows
462       end
463     end
464   end
467 module Layoutable
468   attr_writer :owner
469   attr_accessor :main_layout
470   
471   def add_layout(layout)
472     self.layout = layout
473     owner.main_layout = layout
474   end
475   
476   def add_accessor(name, result)
477     owner.metaclass_eval do
478       define_method(name) { result }
479     end
480   end
481   
482   def buddies
483     @buddies ||= { }
484   end
485   
486   def owner
487     @owner || self
488   end
491 class Qt::Widget
492   include Layoutable
493   
494   def setGUI(gui)
495     Qt::GuiBuilder.build(self, gui)
496     buddies.each do |label, buddy|
497       label.buddy = owner.__send__(buddy)
498     end
499   end
502 class KDE::ComboBox
503   include ListLike
504   
505   Item = Struct.new(:get)
506   
507   def create_item(text, data)
508     add_item(text, Qt::Variant.from_ruby(data))
509   end
511   def current_item
512     item(current_index)
513   end
514   
515   def item(i)
516     Item.new(item_data(i).to_ruby)
517   end
520 def KDE.download_tempfile(url, parent)
521   url.to_local_file