rui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,555 @@
1
+ # Copyright (c) 2009-2010 Paolo Capriotti <p.capriotti@gmail.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 3 of the
6
+ # License, or (at your option) any later version.
7
+
8
+ require 'rui/observer_utils'
9
+ require 'rui/descriptor'
10
+ require 'rui/toolkits/qtbase/gui_builder'
11
+
12
+ ParseException = Class.new(Exception)
13
+
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
21
+
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
30
+
31
+ class Qt::ByteArray
32
+ def self.from_hex(str)
33
+ new([str.gsub(/\W+/, '')].pack('H*'))
34
+ end
35
+ end
36
+
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
58
+
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
+ img = Qt::Image.new(size.x, size.y, Qt::Image::Format_ARGB32_Premultiplied)
73
+ img.fill(0)
74
+ Qt::Painter.new(img).paint(&blk)
75
+ img
76
+ end
77
+
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
93
+
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
102
+ end
103
+
104
+ module PrintableRect
105
+ def to_s
106
+ "[#{self.x}, #{self.y} - #{self.width}, #{self.height}]"
107
+ end
108
+ end
109
+
110
+ class Qt::Point
111
+ include PrintablePoint
112
+
113
+ def to_f
114
+ Qt::PointF.new(self.x, self.y)
115
+ end
116
+ end
117
+
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
124
+ end
125
+
126
+ class Qt::Size
127
+ include PrintablePoint
128
+
129
+ def x
130
+ width
131
+ end
132
+
133
+ def y
134
+ height
135
+ end
136
+ end
137
+
138
+ class Qt::SizeF
139
+ include PrintablePoint
140
+
141
+ def x
142
+ width
143
+ end
144
+
145
+ def y
146
+ height
147
+ end
148
+ end
149
+
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
156
+ end
157
+
158
+ class Qt::RectF
159
+ include PrintableRect
160
+ end
161
+
162
+ class Qt::Pixmap
163
+ #
164
+ # Qt > 4.6 provides effects to be applied to Qt::GraphicsItem's.
165
+ # Since kaya effects work at a lower level of abstraction (i.e. at
166
+ # pixmap/image level), we embed effects directly in a pixmap.
167
+ #
168
+ # When a pixmap is assigned to a Qt::GraphicsItem, its effects are
169
+ # transferred to the item.
170
+ #
171
+ def effects
172
+ @effects ||= []
173
+ end
174
+
175
+ private :effects
176
+
177
+ #
178
+ # Add an effect to this pixmap. If later this pixmap is assigned to an
179
+ # Item, all its effects will be transferred to it.
180
+ #
181
+ def add_effect(effect)
182
+ effects << effect
183
+ end
184
+
185
+ #
186
+ # Render a pixmap from an svg file. See also Qt::Image#renderer.
187
+ #
188
+ def self.from_svg(size, file, id = nil)
189
+ from_renderer(size, Qt::SvgRenderer.new(file), id)
190
+ end
191
+
192
+ #
193
+ # Render a pixmap using an svg renderer. See also Qt::Image#renderer.
194
+ #
195
+ def self.from_renderer(size, renderer, id = nil)
196
+ Qt::Image.from_renderer(size, renderer, id).to_pix
197
+ end
198
+
199
+ def to_pix
200
+ self
201
+ end
202
+ end
203
+
204
+ class Qt::MetaObject
205
+ def create_signal_map
206
+ map = {}
207
+ (0...methodCount).map do |i|
208
+ m = method(i)
209
+ if m.methodType == Qt::MetaMethod::Signal
210
+ sign = m.signature
211
+ sign =~ /^(.*)\(.*\)$/
212
+ sig = $1.underscore.to_sym
213
+ val = [sign, m.parameterTypes]
214
+ map[sig] ||= []
215
+ map[sig] << val
216
+ end
217
+ end
218
+ map
219
+ end
220
+ end
221
+
222
+ class Qt::Base
223
+ include Observable
224
+
225
+ class SignalDisconnecter
226
+ def initialize(obj, sig)
227
+ @obj = obj
228
+ @sig = sig
229
+ end
230
+
231
+ def disconnect!
232
+ @obj.disconnect(@sig)
233
+ end
234
+ end
235
+
236
+ class ObserverDisconnecter
237
+ def initialize(obj, observer)
238
+ @obj = obj
239
+ @observer = observer
240
+ end
241
+
242
+ def disconnect!
243
+ @obj.delete_observer(@observer)
244
+ end
245
+ end
246
+
247
+ class Signal
248
+ attr_reader :symbol
249
+
250
+ def initialize(signal, types)
251
+ raise "Only symbols are supported as signals" unless signal.is_a?(Symbol)
252
+ @symbol = signal
253
+ @types = types
254
+ end
255
+
256
+ def self.create(signal, types)
257
+ if signal.is_a?(self)
258
+ signal
259
+ else
260
+ new(signal, types)
261
+ end
262
+ end
263
+
264
+ def to_s
265
+ @symbol.to_s
266
+ end
267
+ end
268
+
269
+ def on(sig, types = nil, &blk)
270
+ sig = Signal.create(sig, types)
271
+ candidates = if is_a? Qt::Object
272
+ signal_map[sig.symbol]
273
+ end
274
+ if candidates
275
+ if types
276
+ # find candidate with the correct argument types
277
+ candidates = candidates.find_all{|s| s[1] == types }
278
+ end
279
+ if candidates.size > 1
280
+ # find candidate with the correct arity
281
+ arity = blk.arity
282
+ if blk.arity == -1
283
+ # take first
284
+ candidates = [candidates.first]
285
+ else
286
+ candidates = candidates.find_all{|s| s[1].size == arity }
287
+ end
288
+ end
289
+ if candidates.size > 1
290
+ raise "Ambiguous overload for #{sig} with arity #{arity}"
291
+ elsif candidates.empty?
292
+ msg = if types
293
+ "with types #{types.join(' ')}"
294
+ else
295
+ "with arity #{blk.arity}"
296
+ end
297
+ raise "No overload for #{sig} #{msg}"
298
+ end
299
+ sign = SIGNAL(candidates.first[0])
300
+ connect(sign, &blk)
301
+ SignalDisconnecter.new(self, sign)
302
+ else
303
+ observer = observe(sig.symbol, &blk)
304
+ ObserverDisconnecter.new(self, observer)
305
+ end
306
+ end
307
+
308
+ def in(interval, &blk)
309
+ Qt::Timer.in(interval, self, &blk)
310
+ end
311
+
312
+ def run_later(&blk)
313
+ self.in(0, &blk)
314
+ end
315
+
316
+ def signal_map
317
+ self.class.signal_map(self)
318
+ end
319
+
320
+ def self.signal_map(obj)
321
+ @signal_map ||= self.create_signal_map(obj)
322
+ end
323
+
324
+ def self.create_signal_map(obj)
325
+ obj.meta_object.create_signal_map
326
+ end
327
+
328
+ def gui=(g)
329
+ setGUI(g)
330
+ end
331
+ end
332
+
333
+ class Qt::Timer
334
+ #
335
+ # Execute the given block every interval milliseconds and return a timer
336
+ # object. Note that if the timer is garbage collected, the block will not
337
+ # be executed anymore, so the caller should keep a reference to it for as
338
+ # long as needed.
339
+ # To prevent further invocations of the block, use QTimer#stop.
340
+ #
341
+ def self.every(interval, &blk)
342
+ time = Qt::Time.new
343
+ time.restart
344
+
345
+ timer = new
346
+ timer.connect(SIGNAL('timeout()')) { blk[time.elapsed] }
347
+ timer.start(interval)
348
+ # return the timer, so that the caller
349
+ # has a chance to keep it referenced, so
350
+ # that it is not garbage collected
351
+ timer
352
+ end
353
+
354
+ #
355
+ # Execute the given block after interval milliseconds. If target is
356
+ # specified, the block is invoked in the context of target.
357
+ #
358
+ def self.in(interval, target = nil, &blk)
359
+ single_shot(interval,
360
+ Qt::BlockInvocation.new(target, blk, 'invoke()'),
361
+ SLOT('invoke()'))
362
+ end
363
+ end
364
+
365
+ module ListLike
366
+ module ClassMethods
367
+ #
368
+ # Create a list from an array of pairs (text, data)
369
+ # The data for each item can be retrieved using the
370
+ # item's get method.
371
+ # Note that if an array element is not a pair, its
372
+ # value will be used both for the text and for the
373
+ # data.
374
+ #
375
+ # For example: <tt>list.current_item.get</tt>
376
+ #
377
+ def from_a(parent, array)
378
+ list = new(parent)
379
+ list.reset_from_a(array)
380
+ list
381
+ end
382
+ end
383
+
384
+ #
385
+ # Select the item for which the given block
386
+ # evaluates to true.
387
+ #
388
+ def select_item(&blk)
389
+ (0...count).each do |i|
390
+ if blk[item(i).get]
391
+ self.current_index = i
392
+ break i
393
+ end
394
+ end
395
+ nil
396
+ end
397
+
398
+ #
399
+ # Populate the list with values from an array.
400
+ # See also from_a.
401
+ #
402
+ def reset_from_a(array)
403
+ clear
404
+ array.each do |values|
405
+ text, data = if values.is_a?(String)
406
+ [values, values]
407
+ else
408
+ values
409
+ end
410
+ create_item(text, data)
411
+ end
412
+ end
413
+
414
+ def self.included(base)
415
+ base.extend ClassMethods
416
+ end
417
+ end
418
+
419
+ class Qt::ListWidget
420
+ FROM_A_DATA_ROLE = Qt::UserRole
421
+ include ListLike
422
+
423
+ class Item < Qt::ListWidgetItem
424
+ def initialize(text, list, data)
425
+ super(text, list)
426
+ set_data(FROM_A_DATA_ROLE, Qt::Variant.from_ruby(data))
427
+ end
428
+
429
+ def get
430
+ data(FROM_A_DATA_ROLE).to_ruby
431
+ end
432
+ end
433
+
434
+ def current_index=(i)
435
+ self.current_row = i
436
+ end
437
+
438
+ def create_item(text, data)
439
+ Item.new(text, self, data)
440
+ end
441
+ end
442
+
443
+ class Qt::FileDialog
444
+ def self.get_open_url(dir, filter, parent, caption)
445
+ filename = get_open_file_name(parent, caption, dir.to_local_file, filter)
446
+ Qt::Url.from_local_file(filename)
447
+ end
448
+
449
+ def self.get_save_url(dir, filter, parent, caption)
450
+ filename = get_save_file_name(parent, caption, dir.to_local_file, filter)
451
+ Qt::Url.from_local_file(filename)
452
+ end
453
+ end
454
+
455
+ class Qt::Url
456
+ def is_local_file
457
+ true
458
+ end
459
+ end
460
+
461
+ module ModelUtils
462
+ #
463
+ # Helper method to delete model rows from within a block. This method
464
+ # ensures that the appropriate begin/end functions are called.
465
+ #
466
+ def removing_rows(parent, first, last)
467
+ if first > last
468
+ yield
469
+ else
470
+ begin
471
+ begin_remove_rows(parent || Qt::ModelIndex.new, first, last)
472
+ yield
473
+ ensure
474
+ end_remove_rows
475
+ end
476
+ end
477
+ end
478
+
479
+ #
480
+ # Helper method to insert model rows from within a block. This method
481
+ # ensures that the appropriate begin/end functions are called.
482
+ #
483
+ def inserting_rows(parent, first, last)
484
+ if first > last
485
+ yield
486
+ else
487
+ begin
488
+ begin_insert_rows(parent || Qt::ModelIndex.new, first, last)
489
+ yield
490
+ ensure
491
+ end_insert_rows
492
+ end
493
+ end
494
+ end
495
+ end
496
+
497
+ module Layoutable
498
+ attr_writer :owner
499
+ attr_accessor :main_layout
500
+
501
+ def add_layout(layout)
502
+ self.layout = layout
503
+ owner.main_layout = layout
504
+ end
505
+
506
+ def add_widget(w)
507
+ end
508
+
509
+ def add_accessor(name, result)
510
+ owner.metaclass_eval do
511
+ define_method(name) { result }
512
+ end
513
+ end
514
+
515
+ def buddies
516
+ @buddies ||= { }
517
+ end
518
+
519
+ def owner
520
+ @owner || self
521
+ end
522
+ end
523
+
524
+ class Qt::Widget
525
+ include Layoutable
526
+
527
+ def setGUI(gui)
528
+ RUI::GuiBuilder.build(self, gui)
529
+ buddies.each do |label, buddy|
530
+ label.buddy = owner.__send__(buddy)
531
+ end
532
+ end
533
+ end
534
+
535
+ class KDE::ComboBox
536
+ include ListLike
537
+
538
+ Item = Struct.new(:get)
539
+
540
+ def create_item(text, data)
541
+ add_item(text, Qt::Variant.from_ruby(data))
542
+ end
543
+
544
+ def current_item
545
+ item(current_index)
546
+ end
547
+
548
+ def item(i)
549
+ Item.new(item_data(i).to_ruby)
550
+ end
551
+ end
552
+
553
+ def KDE.download_tempfile(url, parent)
554
+ url.to_local_file
555
+ end
data/lib/rui/utils.rb ADDED
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2009-2010 Paolo Capriotti <p.capriotti@gmail.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 3 of the
6
+ # License, or (at your option) any later version.
7
+
8
+ class Object
9
+ def metaclass
10
+ class << self
11
+ self
12
+ end
13
+ end
14
+
15
+ def metaclass_eval(&blk)
16
+ metaclass.instance_eval(&blk)
17
+ end
18
+ end
19
+
20
+ class String
21
+ #
22
+ # Convert from camel case to underscore_separated.
23
+ #
24
+ # Examples:
25
+ # connectToServer => connect_to_server
26
+ # POP3ConnectionManager => pop3_connection_manager
27
+ #
28
+ def underscore
29
+ self.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
30
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
31
+ downcase
32
+ end
33
+
34
+ #
35
+ # Convert from underscore-separated to camel case.
36
+ #
37
+ # Example: connect_to_server => connectToServer
38
+ #
39
+ def camelize
40
+ gsub(/_(.)/) {|m| $1.upcase }
41
+ end
42
+ end
data/lib/rui.rb ADDED
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2009-2010 Paolo Capriotti <p.capriotti@gmail.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 3 of the
6
+ # License, or (at your option) any later version.
7
+
8
+ require 'rubygems' rescue nil
9
+ require 'rui/observer_utils'
10
+ require 'rui/utils'
11
+ require 'builder'
12
+
13
+ case ($toolkit || :kde)
14
+ when :qt
15
+ require 'Qt4'
16
+ KDE = Qt
17
+ RUI = Qt
18
+ require 'rui/toolkits/qt/qt'
19
+ when :kde
20
+ require 'korundum4'
21
+ require 'rui/toolkits/kde/kde'
22
+ module RUI
23
+ MainWindow = KDE::XmlGuiWindow
24
+
25
+ def self.const_missing(c)
26
+ if KDE.const_defined?(c)
27
+ KDE.const_get(c)
28
+ else
29
+ Qt.const_get(c)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ module RUI
36
+ #
37
+ # Create a GUI descriptor using the descriptor DSL.
38
+ #
39
+ # A GUI descriptor, as returned by this function, can be applied to a Widget
40
+ # by settings the widget's gui property to it. For example:
41
+ #
42
+ # widget.gui = RUI::autogui do
43
+ # button(:text => "Hello world")
44
+ # end
45
+ #
46
+ # See {Descriptor} for more details on the general descriptor DSL.
47
+ #
48
+ # See {RUI::GuiBuilder} for a list of supported descriptor tags for GUI
49
+ # descriptors.
50
+ #
51
+ def self.autogui(name = :gui, opts = { }, &blk)
52
+ Descriptor.build(:gui, opts.merge(:gui_name => name), &blk)
53
+ end
54
+ end