freightrain 0.5.9

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.
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