freightrain 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README +4 -0
  2. data/Rakefile +33 -0
  3. data/lib/extensions/gtk/dialog_helper.rb +50 -0
  4. data/lib/extensions/gtk/gtk_bootstrapper.rb +32 -0
  5. data/lib/extensions/gtk/interface_builder.rb +54 -0
  6. data/lib/extensions/gtk/widgets/gtk_calendar.rb +18 -0
  7. data/lib/extensions/gtk/widgets/gtk_combo_box.rb +41 -0
  8. data/lib/extensions/gtk/widgets/gtk_image.rb +14 -0
  9. data/lib/extensions/gtk/widgets/gtk_layout.rb +99 -0
  10. data/lib/extensions/gtk/widgets/gtk_toggle_button.rb +13 -0
  11. data/lib/extensions/gtk/widgets/gtk_toggle_tool_button.rb +11 -0
  12. data/lib/extensions/gtk/widgets/gtk_treeview.rb +80 -0
  13. data/lib/extensions/gtk/widgets/gtk_treeviewcolumn.rb +29 -0
  14. data/lib/extensions/gtk/widgets/gtk_widget.rb +47 -0
  15. data/lib/freightrain/auto_requirer.rb +24 -0
  16. data/lib/freightrain/binding/binding_host.rb +35 -0
  17. data/lib/freightrain/binding/binding_loader.rb +36 -0
  18. data/lib/freightrain/binding/converters/boolean_converter.rb +15 -0
  19. data/lib/freightrain/binding/converters/converter_factory.rb +22 -0
  20. data/lib/freightrain/binding/converters/default_converter.rb +16 -0
  21. data/lib/freightrain/binding/converters/float_converter.rb +16 -0
  22. data/lib/freightrain/binding/converters/integer_converter.rb +16 -0
  23. data/lib/freightrain/binding/freight_binding.rb +65 -0
  24. data/lib/freightrain/bootstrapper.rb +35 -0
  25. data/lib/freightrain/dialog_extension.rb +27 -0
  26. data/lib/freightrain/elements/element_extension.rb +1 -0
  27. data/lib/freightrain/elements/freight_element_view.rb +23 -0
  28. data/lib/freightrain/elements/freight_element_view_model.rb +42 -0
  29. data/lib/freightrain/elements/layout_widget.rb +11 -0
  30. data/lib/freightrain/freight_view.rb +52 -0
  31. data/lib/freightrain/freight_view_model.rb +45 -0
  32. data/lib/freightrain/interface_builder.rb +23 -0
  33. data/lib/freightrain/ioc/container.rb +21 -0
  34. data/lib/freightrain/ioc/container_hookable.rb +32 -0
  35. data/lib/freightrain/regions/freight_region.rb +32 -0
  36. data/lib/freightrain/regions/region_host.rb +37 -0
  37. data/lib/freightrain/services/freight_service.rb +14 -0
  38. data/lib/freightrain/services/service_host.rb +30 -0
  39. data/lib/freightrain/signals/freight_signal.rb +23 -0
  40. data/lib/freightrain/signals/signal_host.rb +38 -0
  41. data/lib/freightrain/string_patch.rb +24 -0
  42. data/lib/freightrain/testhelpers/container_helpers.rb +22 -0
  43. data/lib/freightrain/testhelpers.rb +2 -0
  44. data/lib/freightrain.rb +8 -0
  45. data/lib/scaffolding/commands/app.rb +29 -0
  46. data/lib/scaffolding/commands/triad.rb +29 -0
  47. data/lib/scaffolding/ftrain.rb +23 -0
  48. data/lib/scaffolding/generator.rb +15 -0
  49. data/lib/scaffolding/templates/application.ftt +11 -0
  50. data/lib/scaffolding/templates/view.ftt +5 -0
  51. data/lib/scaffolding/templates/view_bnd.ftt +0 -0
  52. data/lib/scaffolding/templates/view_control_glade.ftt +16 -0
  53. data/lib/scaffolding/templates/view_model.ftt +7 -0
  54. data/lib/scaffolding/templates/view_toplevel_glade.ftt +12 -0
  55. metadata +127 -0
data/README ADDED
@@ -0,0 +1,4 @@
1
+ == freightrain
2
+
3
+ DEV ALPHA
4
+ testing
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+
2
+ require 'rubygems'
3
+ require 'spec/rake/spectask'
4
+ require 'rake'
5
+ require 'rake/clean'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/testtask'
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'freightrain'
12
+ s.version = '0.5.9'
13
+ s.add_dependency('require_all', '>= 1.1.0')
14
+ s.add_dependency('needle', '>= 1.3.0')
15
+ s.has_rdoc = false
16
+ s.summary = 'ruby desktop development made easy'
17
+ s.description = s.summary
18
+ s.author = 'Andrea Dallera'
19
+ s.email = 'andrea@andreadallera.com'
20
+ s.files = %w(README Rakefile) + Dir.glob("{bin,lib}/**/*")
21
+ s.require_path = "lib"
22
+ end
23
+
24
+ Rake::GemPackageTask.new(spec) do |p|
25
+ p.gem_spec = spec
26
+ p.need_tar = true
27
+ p.need_zip = true
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new do |t|
31
+ t.warning = true
32
+ t.spec_files = FileList['spec/**/*.rb']
33
+ end
@@ -0,0 +1,50 @@
1
+
2
+ module Freightrain
3
+
4
+ module Toolkit
5
+
6
+ module DialogHelper
7
+
8
+ def open_file_dialog(description = "Open file...")
9
+ raise "toplevel not implemented. If you want to use " +
10
+ "DialogHelper inside your class you should provide " +
11
+ "a toplevel widget via the toplevel method" unless respond_to? :toplevel
12
+ dialog = Gtk::FileChooserDialog.new(description,
13
+ toplevel,
14
+ Gtk::FileChooser::ACTION_OPEN,
15
+ nil,
16
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
17
+ [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])
18
+ if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
19
+ result = dialog.filename
20
+ end
21
+ dialog.destroy
22
+ return result
23
+ end
24
+
25
+ def yes_no_dialog(description, title = "Warning!", secondary_text = "It will not be possible to recover the deleted item")
26
+ raise "toplevel not implemented. If you want to use " +
27
+ "DialogHelper inside your class you should provide " +
28
+ "a toplevel widget via the toplevel method" unless respond_to? :toplevel
29
+ result = false
30
+ dialog = Gtk::MessageDialog.new(
31
+ toplevel,
32
+ Gtk::Dialog::MODAL,
33
+ Gtk::MessageDialog::QUESTION,
34
+ Gtk::MessageDialog::BUTTONS_YES_NO,
35
+ description)
36
+ dialog.title = title
37
+ dialog.secondary_text = secondary_text
38
+ dialog.run do |response|
39
+ result = (response == Gtk::Dialog::RESPONSE_YES)
40
+ end
41
+ dialog.destroy
42
+ return result
43
+
44
+ end
45
+
46
+ end #DialogHelper
47
+
48
+ end #Toolkit
49
+
50
+ end #Freightrain
@@ -0,0 +1,32 @@
1
+
2
+ #TODO: use require all
3
+
4
+ require 'libglade2'
5
+ require File.dirname(__FILE__) + "/widgets/gtk_image.rb"
6
+ require File.dirname(__FILE__) + "/widgets/gtk_toggle_button.rb"
7
+ require File.dirname(__FILE__) + "/widgets/gtk_toggle_tool_button.rb"
8
+ require File.dirname(__FILE__) + "/widgets/gtk_widget.rb"
9
+ require File.dirname(__FILE__) + "/widgets/gtk_combo_box.rb"
10
+ require File.dirname(__FILE__) + "/widgets/gtk_calendar.rb"
11
+ require File.dirname(__FILE__) + "/widgets/gtk_treeview.rb"
12
+ require File.dirname(__FILE__) + "/widgets/gtk_treeviewcolumn.rb"
13
+ require File.dirname(__FILE__) + "/widgets/gtk_layout.rb"
14
+ require File.dirname(__FILE__) + "/interface_builder.rb"
15
+ require File.dirname(__FILE__) + "/dialog_helper.rb"
16
+
17
+ module Freightrain
18
+
19
+ module Toolkit
20
+
21
+ def self.start_main_loop
22
+ yield
23
+ Gtk.main
24
+ end
25
+
26
+ def self.quit
27
+ Gtk.main_quit
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,54 @@
1
+
2
+ module Freightrain
3
+
4
+ module Toolkit
5
+
6
+ class InterfaceBuilder
7
+
8
+ def initialize
9
+ @builder = Gtk::Builder.new
10
+ end
11
+
12
+ def file_found?(file_name)
13
+ return get_glade_file(file_name)
14
+ end
15
+
16
+ def create_objects_from_file(file_name)
17
+ @builder.add_from_file(get_glade_file(file_name))
18
+ @control = @builder.objects.first.toplevel
19
+ return @builder.objects.select do
20
+ |obj| obj.respond_to?(:name)
21
+ end.map
22
+ end
23
+
24
+ def create_object_accessors(widgets, view)
25
+ widgets.each do |widget|
26
+ name = widget.name
27
+ view.instance_eval "def #{name}; @widgets.select { |w| w.name == '#{name}' }.first ;end;"
28
+ end
29
+ end
30
+
31
+ def connect_signals()
32
+ @builder.connect_signals do |signal|
33
+ yield(signal)
34
+ end
35
+ end
36
+
37
+ def get_glade_file(file_name)
38
+ search_path = File.join(
39
+ Freightrain.app_path,
40
+ "views",
41
+ "**",
42
+ file_name.to_convention + ".glade")
43
+ results = Dir.glob(search_path)
44
+ return results.first
45
+ end
46
+
47
+ def control
48
+ return @control
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module Gtk
3
+
4
+ class Calendar
5
+
6
+ def date
7
+ return DateTime.civil(self.year, self.month + 1, self.day)
8
+ end
9
+
10
+ def date=(val)
11
+ self.day = val.day
12
+ self.month = val.month - 1
13
+ self.year = val.year
14
+ end
15
+
16
+ end
17
+ end
18
+
@@ -0,0 +1,41 @@
1
+
2
+ module Gtk
3
+
4
+ class ComboBox
5
+
6
+ def elements=(enumerable)
7
+ list_store = Gtk::ListStore.new(::Object, String)
8
+ enumerable.each do |item|
9
+ iterator = list_store.append
10
+ iterator[0] = item
11
+ iterator[1] = item.send(@text)
12
+ end
13
+ self.model = list_store
14
+ end
15
+
16
+ def selected
17
+ iter = self.active_iter
18
+ return iter[0] if iter
19
+ end
20
+
21
+ def selected=(object)
22
+ if self.model
23
+ self.model.each do |model, path, iter|
24
+ self.active_iter = iter if iter[0] == object
25
+ end
26
+ end
27
+ end
28
+
29
+ def bind(options)
30
+ if options[:property].to_sym == :elements
31
+ @path = options[:path]
32
+ @text = options[:text] || "to_s"
33
+ renderer = Gtk::CellRendererText.new
34
+ self.pack_start(renderer, true)
35
+ self.add_attribute(renderer,:text, 1)
36
+ end
37
+ super(options)
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module Gtk
3
+
4
+ class Image
5
+
6
+ def image=(value)
7
+ file = File.new(File.join(Dir.tmpdir,"tempimg#{self.object_id.to_s}"),'w')
8
+ file << value
9
+ file.close
10
+ self.pixbuf = Gdk::Pixbuf.new(File.join(Dir.tmpdir,"tempimg#{self.object_id.to_s}"))
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,99 @@
1
+
2
+ module Gtk
3
+
4
+ class Layout
5
+ include Freightrain::LayoutWidget
6
+
7
+ attr_writer :viewmodel
8
+ attr_reader :elements
9
+
10
+ def initialize
11
+ super
12
+
13
+ end
14
+
15
+ def setup
16
+ @signals ||= {}
17
+ selected_callback = @signals[:selected]
18
+ @signals[:selected] = lambda do |value|
19
+ elements.each do |item|
20
+ item.set_selection(item.value == value)
21
+ end
22
+ selected_callback.call(value) if selected_callback
23
+ end
24
+ @elements ||= []
25
+ @ready = true
26
+ end
27
+
28
+ #TODO:remove
29
+ def default_display_logic(elements, enumerable)
30
+ #TODO: clean this mess
31
+ height = elements.length
32
+ delta = enumerable.length - elements.length
33
+ delta.abs.times do
34
+ if delta > 0
35
+ item = self.get_new_item
36
+ self.put(item.control, 0, item.control.height_request * height)
37
+ elements << item
38
+ height += 1
39
+ else
40
+ item = self.children.last
41
+ self.remove(item)
42
+ elements.delete_if {|element| element.control == item }
43
+ end
44
+ end
45
+ height_factor = self.get_new_item.control.height_request
46
+ self.height = elements.length * height_factor
47
+ (0...elements.length).each do |index|
48
+ elements[index].value = enumerable[index]
49
+ end
50
+ end
51
+
52
+ def display_logic(enumerable)
53
+ if @display_logic
54
+ @display_logic.call(self, @elements, enumerable)
55
+ else
56
+ default_display_logic(@elements, enumerable)
57
+ end
58
+ end
59
+
60
+ def set_display_logic
61
+ @display_logic = lambda do |widget, elements, enumerable|
62
+ yield(widget, elements, enumerable)
63
+ end
64
+ end
65
+
66
+ def elements=(enumerable)
67
+ setup unless @ready
68
+ display_logic(enumerable)
69
+ end
70
+
71
+ def selected
72
+ return @elements.select { |element| element.selected == true}.first
73
+ end
74
+
75
+ def selected=(val)
76
+ @elements.each do |element|
77
+ element.set_selection(val == element.value)
78
+ end
79
+ end
80
+
81
+ def bind(options)
82
+ if options[:property].to_s == "elements"
83
+ @viewmodel = (options[:element].to_s + "_element_view_model").to_sym
84
+ options[:force] = true
85
+ end
86
+ super(options)
87
+ end
88
+
89
+ def get_new_item
90
+ item = Freightrain[@viewmodel]
91
+ item.signals.each do |key, signal|
92
+ signal.connect(@signals[key]) if @signals[key]
93
+ end
94
+ return item
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,13 @@
1
+
2
+ #HACK: think of a better way to do this
3
+
4
+ module Gtk
5
+
6
+ class ToggleButton
7
+
8
+ def active
9
+ return active?
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+
2
+ module Gtk
3
+
4
+ class ToggleToolButton
5
+
6
+ def active
7
+ return active?
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,80 @@
1
+
2
+ module Gtk
3
+
4
+ class TreeView
5
+
6
+ def create_column(column_definition)
7
+ renderer = Gtk::CellRendererText.new
8
+ title = column_definition.keys.first
9
+ attributes = {}
10
+ column_definition.values.first.each do |binding|
11
+ attributes[binding["property"]] = @paths[binding["path"]]
12
+ end
13
+ column = Gtk::TreeViewColumn.new(title, renderer, attributes)
14
+ self.append_column column
15
+ end
16
+
17
+ def bind(options)
18
+ if options[:property].to_sym == :elements
19
+ columns = normalize_column_bindings(options[:columns])
20
+ @paths = get_paths(columns)
21
+ columns.each do |column|
22
+ create_column(column)
23
+ end
24
+ end
25
+ super(options)
26
+ end
27
+
28
+ def elements=(enumerable)
29
+ types = [::Object]
30
+ @paths.length.times do
31
+ types << String #TODO: allow for different types
32
+ end
33
+ list_store = Gtk::ListStore.new(*types)
34
+ enumerable.each do |item|
35
+ iterator = list_store.append
36
+ iterator[0] = item
37
+ @paths.each do |key, index|
38
+ iterator[index] = item.send(key).to_s
39
+ end
40
+ end
41
+ self.model = list_store
42
+ end
43
+
44
+ private
45
+ def normalize_column_bindings(columns)
46
+ result = []
47
+ #step 1 : turn name/values only into arrays with 1 path
48
+ columns.each do |col|
49
+ unless col.values.first.kind_of?(Array)
50
+ result << {col.keys.first => [{"path" => col.values.first}]}
51
+ else
52
+ result << col
53
+ end
54
+ end
55
+ #step 2 : add property key if not present
56
+ result.each do |col|
57
+ col.values.first.each do |bindings|
58
+ unless bindings["property"]
59
+ bindings["property"] = "text"
60
+ end
61
+ end
62
+ end
63
+ return result
64
+ end
65
+
66
+ def get_paths(columns)
67
+ paths = {}
68
+ columns.each do |column|
69
+ column.values.each do |binding|
70
+ binding.each do |definition|
71
+ paths[definition["path"]] = paths.length + 1 unless paths[definition["path"]]
72
+ end
73
+ end
74
+ end
75
+ return paths
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,29 @@
1
+
2
+
3
+ module Gtk
4
+
5
+ class TreeViewColumn
6
+
7
+ attr_accessor :path
8
+
9
+ attr_writer :property
10
+ attr_writer :type
11
+
12
+ def type
13
+ return @type if @type
14
+ return String
15
+ end
16
+
17
+ def property
18
+ return @property if @property
19
+ return :text
20
+ end
21
+
22
+
23
+
24
+
25
+
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,47 @@
1
+
2
+ module Gtk
3
+
4
+ class Widget
5
+
6
+ def bind(binding_options)
7
+ @bindings ||= {}
8
+ binding = FreightBinding.new(self, binding_options)
9
+ #TODO: send out warning on override?
10
+ @bindings[binding_options[:property]] = binding
11
+ return binding
12
+ end
13
+
14
+ def bindings
15
+ @bindings ||= {}
16
+ return @bindings.values
17
+ end
18
+
19
+ def color_from_rgb(string)
20
+ red = ((string[0..1].hex.to_i + 1) * 256) - 1
21
+ green = ((string[2..3].hex.to_i + 1) * 256) - 1
22
+ blue = ((string[4..5].hex.to_i + 1) * 256) - 1
23
+ return Gdk::Color.new(red, green, blue)
24
+ end
25
+
26
+ def background=(color)
27
+ self.modify_bg(Gtk::STATE_NORMAL, color_from_rgb(color))
28
+ end
29
+
30
+ def foreground=(color)
31
+ self.modify_fg(Gtk::STATE_NORMAL, color_from_rgb(color))
32
+ end
33
+
34
+ def base_background=(color)
35
+ self.modify_base(Gtk::STATE_NORMAL, color_from_rgb(color))
36
+ end
37
+
38
+ def plug_in(region_widget)
39
+ self.remove(self.children.first) unless self.children.empty?
40
+ self << region_widget
41
+ end
42
+
43
+
44
+ end
45
+
46
+
47
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Freightrain
3
+
4
+ def self.auto_require!
5
+
6
+ views_folder = self.views_path
7
+ viewmodels_folder = File.join(self.app_path, "viewmodels")
8
+ services_folder = File.join(self.app_path, "services")
9
+ domain_folder = File.join(self.app_path, "domain")
10
+
11
+ require_all views_folder
12
+ require_all viewmodels_folder
13
+
14
+ if File.directory?(services_folder) && Dir.entries(services_folder).length > 2
15
+ require_all services_folder
16
+ end
17
+
18
+ if File.directory?(domain_folder) && Dir.entries(domain_folder).length > 2
19
+ require_all domain_folder
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,35 @@
1
+
2
+ module Freightrain
3
+
4
+ module BindingHost
5
+
6
+ def load_bindings_from_file(widgets)
7
+ loader = BindingLoader.new(self.class.name)
8
+ loader.each_binding do |widget_name, options|
9
+ widget = widgets.select { |widget| widget.name == widget_name }.first
10
+ widget.bind(options) if widget
11
+ end
12
+ end
13
+
14
+ def data_source=(source)
15
+ bindings.each do |binding|
16
+ binding.data_source = source
17
+ binding.update
18
+ end
19
+ end
20
+
21
+ def bindings
22
+ return widgets.each.map { |widget| widget.bindings }.flatten
23
+ end
24
+
25
+ def commit
26
+ bindings.each { |binding| binding.commit }
27
+ end
28
+
29
+ def update
30
+ bindings.each { |binding| binding.update}
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require 'yaml'
3
+
4
+ module Freightrain
5
+
6
+ class BindingLoader
7
+
8
+ def initialize(class_name)
9
+ @search_path = File.join(
10
+ Freightrain.views_path,
11
+ "**",
12
+ class_name.to_convention + ".bnd.yml")
13
+ end
14
+
15
+ def filename
16
+ result = Dir.glob(@search_path)
17
+ return result[0] || nil
18
+ end
19
+
20
+ def each_binding
21
+ return nil unless filename
22
+ binding_collection = YAML.load_file(filename) || []
23
+ binding_collection.each do |widget, binding|
24
+ binding = [binding] if binding.kind_of? Hash #needed if only 1 binding declared
25
+ binding.each do |binding_options|
26
+ options = {}
27
+ binding_options.each do |key, value|
28
+ options[key.to_sym] = value
29
+ end
30
+ yield(widget, options)
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module Freightrain
3
+
4
+ class BooleanConverter
5
+
6
+ def from(val)
7
+ return val != nil
8
+ end
9
+
10
+ def to(val)
11
+ return val == true #dummy
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module Freightrain
3
+
4
+ class ConverterFactory
5
+
6
+ def self.create(converter_name)
7
+ name = converter_name.to_s.capitalize
8
+ #first try to get one of the standard converters
9
+ if Freightrain.const_defined? "#{name}Converter"
10
+ return Freightrain.const_get("#{name}Converter").new
11
+ end
12
+ #then, if not found, try to get a registered custom converter
13
+ begin
14
+ return Freightrain["#{name}Converter".to_convention_sym]
15
+ rescue
16
+ return nil
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Freightrain
3
+
4
+ class DefaultConverter
5
+
6
+ def from(value)
7
+ return value
8
+ end
9
+
10
+ def to(value)
11
+ return value
12
+ end
13
+
14
+ end
15
+
16
+ end