reactive_view_wx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/History.txt +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +37 -0
  4. data/README.txt +19 -0
  5. data/Rakefile +16 -0
  6. data/lib/action_view_init.rb +13 -0
  7. data/lib/base.rb +59 -0
  8. data/lib/binder.rb +20 -0
  9. data/lib/helpers/model_edit_ctrl_class.rb +124 -0
  10. data/lib/helpers/model_list_ctrl_class.rb +82 -0
  11. data/lib/helpers/model_main_frame_class.rb +132 -0
  12. data/lib/helpers/model_show_ctrl_class.rb +35 -0
  13. data/lib/reactive_view_wx.rb +22 -0
  14. data/lib/request.rb +14 -0
  15. data/lib/template.rb +114 -0
  16. data/lib/version.rb +13 -0
  17. data/lib/xrc_pepper.rb +253 -0
  18. data/reactive_generators/application_view/USAGE +7 -0
  19. data/reactive_generators/application_view/application_view_generator.rb +49 -0
  20. data/reactive_generators/application_view/templates/application_helper.rb +40 -0
  21. data/reactive_generators/application_view/templates/application_layout.rb +2 -0
  22. data/reactive_generators/application_view/templates/application_view.rb +9 -0
  23. data/reactive_generators/application_view/templates/assets/delete16.png +0 -0
  24. data/reactive_generators/application_view/templates/assets/edit16.png +0 -0
  25. data/reactive_generators/application_view/templates/assets/show16.png +0 -0
  26. data/reactive_generators/application_view/templates/record_toolbar.rb +46 -0
  27. data/reactive_generators/view/USAGE +16 -0
  28. data/reactive_generators/view/templates/create.rb +1 -0
  29. data/reactive_generators/view/templates/delete.rb +3 -0
  30. data/reactive_generators/view/templates/destroy.rb +0 -0
  31. data/reactive_generators/view/templates/edit.rb +8 -0
  32. data/reactive_generators/view/templates/index.rb +4 -0
  33. data/reactive_generators/view/templates/layout.rb +2 -0
  34. data/reactive_generators/view/templates/new.rb +8 -0
  35. data/reactive_generators/view/templates/show.rb +4 -0
  36. data/reactive_generators/view/templates/update.rb +1 -0
  37. data/reactive_generators/view/view_generator.rb +69 -0
  38. metadata +146 -0
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.1.0 / 2008-04-28
2
+
3
+ * Initial release, note that this is an early alpha version used for proof-of-concept. Everything has a great chance to change! [PHI]
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2008 Pascal Hurni
2
+ Copyright (c) 2004-2007 David Heinemeier Hansson
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,37 @@
1
+ History.txt
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/action_view_init.rb
7
+ lib/base.rb
8
+ lib/binder.rb
9
+ lib/helpers/model_edit_ctrl_class.rb
10
+ lib/helpers/model_list_ctrl_class.rb
11
+ lib/helpers/model_main_frame_class.rb
12
+ lib/helpers/model_show_ctrl_class.rb
13
+ lib/reactive_view_wx.rb
14
+ lib/request.rb
15
+ lib/template.rb
16
+ lib/version.rb
17
+ lib/xrc_pepper.rb
18
+ reactive_generators/application_view/USAGE
19
+ reactive_generators/application_view/application_view_generator.rb
20
+ reactive_generators/application_view/templates/application_helper.rb
21
+ reactive_generators/application_view/templates/application_layout.rb
22
+ reactive_generators/application_view/templates/application_view.rb
23
+ reactive_generators/application_view/templates/assets/delete16.png
24
+ reactive_generators/application_view/templates/assets/edit16.png
25
+ reactive_generators/application_view/templates/assets/show16.png
26
+ reactive_generators/application_view/templates/record_toolbar.rb
27
+ reactive_generators/view/USAGE
28
+ reactive_generators/view/templates/create.rb
29
+ reactive_generators/view/templates/delete.rb
30
+ reactive_generators/view/templates/destroy.rb
31
+ reactive_generators/view/templates/edit.rb
32
+ reactive_generators/view/templates/index.rb
33
+ reactive_generators/view/templates/layout.rb
34
+ reactive_generators/view/templates/new.rb
35
+ reactive_generators/view/templates/show.rb
36
+ reactive_generators/view/templates/update.rb
37
+ reactive_generators/view/view_generator.rb
data/README.txt ADDED
@@ -0,0 +1,19 @@
1
+ == ReactiveViewWx
2
+
3
+ Please visit www.ruby-reactive.org for further informations.
4
+
5
+ == Description:
6
+
7
+ This is a view provider for Reactive, the desktop application framework, it provides views based
8
+ on the wxWidgets library. For this the ruby port wxRuby is used.
9
+
10
+
11
+ == Requirements:
12
+
13
+ * wxruby
14
+ * wx_sugar
15
+
16
+ == Install:
17
+
18
+ * sudo gem install reactive_view_wx
19
+
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/version.rb'
6
+
7
+ Hoe.new('reactive_view_wx', Reactive::View::Wx::VERSION::STRING) do |p|
8
+ p.developer('Pascal Hurni', 'phi@ruby-reactive.org')
9
+ p.extra_deps << ['reactive', '>= 0.1.0']
10
+ p.extra_deps << ['activesupport', '>= 1.4.0']
11
+ p.extra_deps << ['actionpack', '>= 2.0.0']
12
+ p.extra_deps << ['wxruby', '>= 1.9.4']
13
+ p.extra_deps << ['wx_sugar', '>= 0.1.19']
14
+ end
15
+
16
+ # vim: syntax=Ruby
@@ -0,0 +1,13 @@
1
+
2
+ require 'action_view/template_handler'
3
+ require 'action_view/template_handlers/builder'
4
+ require 'action_view/template_handlers/erb'
5
+ require 'action_view/template_handlers/rjs' # Not needed, but referenced in action_view/base
6
+
7
+ require 'action_view/base'
8
+ require 'action_view/partials'
9
+ require 'action_view/template_error'
10
+
11
+ ActionView::Base.class_eval do
12
+ include ActionView::Partials
13
+ end
data/lib/base.rb ADDED
@@ -0,0 +1,59 @@
1
+ module Reactive
2
+ module View
3
+ module Wx
4
+
5
+ class InvalidForm < Error; end
6
+
7
+ class Request < Reactive::Request
8
+ attr_accessor :local_assigns
9
+ end
10
+
11
+ class Base < View::Base
12
+ cattr_accessor :template_class
13
+
14
+ @@template_class = Template
15
+
16
+ class << self
17
+ def do_request(hash, wx_event = nil)
18
+ # params may be of two forms, a simple params hash or a complex hash with embedded hashes. To distinguish: if the hash contains a :link key it is a complex one.
19
+ params, options = hash.has_key?(:link) ? [hash[:link], hash] : [hash, {}]
20
+ new_params = params.dup
21
+
22
+ # handle forms params
23
+ forms = options[:forms]
24
+ forms = [forms] unless forms.is_a?(Array)
25
+ forms.compact.each do |form|
26
+ form_name, window = case form
27
+ when :self
28
+ [:self, self]
29
+ when ::Wx::Window
30
+ raise InvalidForm, "Window #{form} has no name!" unless name = form.get_name
31
+ [name, form]
32
+ when String
33
+ raise InvalidForm, "No window named #{form}" unless win = ::Wx::Window.find_window_by_name(form, self)
34
+ [form, win]
35
+ else
36
+ raise InvalidForm, "Unhandled param, passed form: #{form.inspect}"
37
+ end
38
+ form_param = {}
39
+ window.get_children.each {|child| next unless child.get_name =~ /^[\da-z_]+$/ ;form_param[child.get_name] = child.value} #TODO: Handle sub-children recursively
40
+ new_params[form_name] = form_param
41
+ end
42
+
43
+ # handle late update of params
44
+ continue = (late_proc = options[:late]) ? late_proc.call(new_params, wx_event) : true
45
+
46
+ # set up the request
47
+ request = Request.new(new_params)
48
+ request.local_assigns = options[:locals]
49
+
50
+ # let the request run
51
+ Base.handle_request(request) if continue
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
59
+ end
data/lib/binder.rb ADDED
@@ -0,0 +1,20 @@
1
+ module Reactive
2
+ module View
3
+ module Wx
4
+
5
+ module Binder
6
+ def bind(control, events)
7
+ events.each do |event, hash|
8
+ listen(event, control) {|wx_event| do_request(hash, wx_event)}
9
+ end
10
+ end
11
+
12
+ def do_request(hash, wx_event = nil)
13
+ Base.do_request(hash, wx_event)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,124 @@
1
+ require 'wx_sugar'
2
+
3
+ module Reactive::View::Wx
4
+ module Helpers
5
+
6
+ class ModelEditCtrl < Wx::Panel
7
+ attr_writer :error_color, :normal_color
8
+ attr_reader :model
9
+
10
+ def selected_records
11
+ [@record]
12
+ end
13
+
14
+ def initialize(parent, record, submit_link, options = {})
15
+ super(parent, options)
16
+ @model, @record = record.class, record
17
+ @book = parent
18
+
19
+ arrange_vertically(:padding => 5)
20
+ nest(container = Wx::ScrolledWindow.new(self, :style => Wx::TAB_TRAVERSAL | Wx::SUNKEN_BORDER, :name => @model.to_s.demodulize.underscore), :proportion => 1)
21
+ container.set_scroll_rate(2,2)
22
+ fill_controls(container, record)
23
+ show_errors(record)
24
+ fill_buttons(self, container, submit_link)
25
+ end
26
+
27
+ def show_errors(record)
28
+ @model.columns.each do |column|
29
+ next if column.primary
30
+ if control = find_window_by_name(column.name)
31
+ if errors = record.errors.on(column.name)
32
+ control.set_background_colour(error_color)
33
+ control.set_tool_tip(errors.to_a.join("\n"))
34
+ control.refresh
35
+ else
36
+ control.set_background_colour(normal_color)
37
+ control.set_tool_tip(nil)
38
+ control.refresh
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def error_color
45
+ @error_color ||= Wx::Colour.new(255, 160, 160)
46
+ end
47
+
48
+ def normal_color
49
+ @normal_color ||= Wx::SystemSettings.get_colour(Wx::SYS_COLOUR_WINDOW)
50
+ end
51
+
52
+ protected
53
+
54
+ def association?(column, record)
55
+ record.class.reflect_on_association(column.name.to_sym)
56
+ end
57
+
58
+ def form_override?(column, record)
59
+ return true if respond_to?("form_for_#{column.name}")
60
+ association?(column, record) ? false : respond_to?("form_for_type_#{column.type}")
61
+ end
62
+
63
+ def input_override?(column, record)
64
+ return true if respond_to?("input_for_#{column.name}")
65
+ association?(column, record) ? false : respond_to?("input_for_type_#{column.type}")
66
+ end
67
+
68
+ def form_for_column(column, record, window)
69
+ if respond_to?("form_for_#{column.name}")
70
+ send("form_for_#{column.name}", column, record, window)
71
+ elsif respond_to?("form_for_type_#{column.type}")
72
+ send("form_for_type_#{column.type}", column, record, window)
73
+ end
74
+ end
75
+
76
+ def input_for_column(column, record, window)
77
+ if respond_to?("input_for_#{column.name}")
78
+ send("input_for_#{column.name}", column, record, window)
79
+ elsif respond_to?("input_for_type_#{column.type}")
80
+ send("input_for_type_#{column.type}", column, record, window)
81
+ end
82
+ end
83
+
84
+ def input_for_type_text(column, record, window)
85
+ window.nest(Wx::TextCtrl.new(window, :value => record.send(column.name).to_s, :name => column.name, :style => Wx::TE_MULTILINE), :proportion => 1, :align => 'CENTER_VERTICAL')
86
+ end
87
+
88
+ def fill_controls(parent, record)
89
+ parent.arrange_grid(:cols => 2, :padding => 5, :proportion => 1) do |sizer|
90
+ sizer.add_growable_col(1, 1)
91
+ @model.columns.each do |column|
92
+ next if column.primary
93
+ if form_override?(column, record)
94
+ form_for_column(column, record, parent)
95
+ else
96
+ if input_override?(column, record)
97
+ parent.nest(Wx::StaticText.new(parent, :label => "#{column.human_name} :", :name => 'STATIC'), :pad => 'ALIGN_CENTER_VERTICAL,ALIGN_RIGHT')
98
+ input_for_column(column, record, parent)
99
+ else
100
+ next if association?(column, record)
101
+ parent.nest(Wx::StaticText.new(parent, :label => "#{column.human_name} :", :name => 'STATIC'), :pad => 'ALIGN_CENTER_VERTICAL,ALIGN_RIGHT')
102
+ parent.nest(Wx::TextCtrl.new(parent, :value => record.send(column.name).to_s, :name => column.name), :proportion => 1, :align => 'CENTER_VERTICAL')
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def fill_buttons(parent, form_container, submit_link)
110
+ parent.arrange(Wx::StdDialogButtonSizer.new) do |sizer|
111
+ ok_button = Wx::Button.new(parent, :label => 'Save', :name => 'BUTTON')
112
+ bind ok_button, :button => {:link => submit_link, :forms => form_container, :locals => {:book => @book, :page => parent}}
113
+ sizer.set_affirmative_button(ok_button)
114
+ cancel_button = Wx::Button.new(parent, :label => 'Cancel', :name => 'BUTTON')
115
+ evt_button(cancel_button) { @book.delete_page(@book.get_page_index(parent)) }
116
+ sizer.set_cancel_button(cancel_button)
117
+ sizer.realize
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,82 @@
1
+ module Reactive::View::Wx
2
+ module Helpers
3
+
4
+ class ModelListCtrl < Wx::ListCtrl
5
+ attr_reader :model
6
+
7
+ # A proxy class for the item_data functions.
8
+ class SelectedItemDataCollection < WxSugar::EnumerableControl::ItemCollection
9
+ include Enumerable
10
+
11
+ def each
12
+ i = cwi.get_next_item(-1, Wx::LIST_NEXT_ALL, Wx::LIST_STATE_SELECTED)
13
+ while i >= 0
14
+ yield cwi.get_item_data(i)
15
+ i = cwi.get_next_item(i, Wx::LIST_NEXT_ALL, Wx::LIST_STATE_SELECTED)
16
+ end
17
+ end
18
+
19
+ # Retrieves the item data for item +i+
20
+ def [](i)
21
+ super :get_item_data, i
22
+ end
23
+
24
+ # Sets the item data for +i+ to be +obj+
25
+ def []=(i, obj)
26
+ super :set_item_data, i, obj
27
+ end
28
+ end
29
+
30
+ collection :selected_data, SelectedItemDataCollection
31
+
32
+ # returns the currently selected record
33
+ # (Used to determine allowed toolbar actions)
34
+ def selected_records
35
+ selected_data.collect
36
+ end
37
+
38
+ def initialize(parent, model, collection, options = {})
39
+ super(parent, options.merge(:style => Wx::LC_REPORT))
40
+ @model = model
41
+
42
+ evt_list_col_click(self) {|event| sort_items{|a,b| nil_aware_comparaison(a.send(@columns[event.get_column].name), b.send(@columns[event.get_column].name))} }
43
+ bind self, :list_item_activated => {:link => {:controller => model.to_s.downcase.pluralize, :action => 'show'}, :late => proc{|params, event| params[:id] = get_item_data(event.get_index).id} }
44
+
45
+ @columns = @model.content_columns
46
+ @other_columns = @model.content_columns.dup
47
+ @first_column = @other_columns.shift
48
+ set_columns(@model.content_columns.map(&:human_name))
49
+
50
+ append_items(collection) if collection
51
+ end
52
+
53
+ def set_columns(columns)
54
+ index = -1
55
+ columns.each {|column| insert_column(index+=1, column)}
56
+ end
57
+
58
+ def append_item(record)
59
+ index = get_item_count
60
+ insert_item(index, record.send(@first_column.name).to_s)
61
+ set_item_data(index, record)
62
+ col_index = 0
63
+ @other_columns.each {|column| set_item(index, col_index+=1, record.send(column.name).to_s)}
64
+ end
65
+
66
+ def append_items(collection)
67
+ collection.each {|record| append_item(record)}
68
+ end
69
+
70
+ protected
71
+
72
+ def nil_aware_comparaison(a,b)
73
+ return 0 if a.nil? && b.nil?
74
+ return -1 if a.nil?
75
+ return 1 if b.nil?
76
+ a <=> b
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,132 @@
1
+ module Reactive::View::Wx
2
+ module Helpers
3
+
4
+ module PageStatus
5
+ attr_reader :status_text
6
+
7
+ def status_text=(v)
8
+ @status_text = v || ''
9
+ if get_parent.get_page(get_parent.get_selection) == self # The parent is the notebook
10
+ Wx::get_app.get_top_window.get_status_bar.set_status_text(@status_text)
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ class ModelMainFrame < Wx::Frame
17
+ attr_reader :notebook, :manager
18
+
19
+ def initialize(*args)
20
+ super
21
+
22
+ @manager = Wx::AuiManager.new
23
+ @manager.set_managed_window(self)
24
+
25
+ create_status_bar
26
+ status_bar.set_status_text("Ready")
27
+ set_min_size( Wx::Size.new(400,300) )
28
+
29
+ pi = Wx::AuiPaneInfo.new
30
+ pi.set_name('notebook').center_pane
31
+ @manager.add_pane(create_notebook, pi)
32
+
33
+ @manager.update
34
+
35
+ evt_erase_background { | e | on_erase_background(e) }
36
+ evt_size{ | e | on_size(e) }
37
+
38
+ evt_auinotebook_page_changed(Wx::ID_ANY) { | e | on_page_changed(e) }
39
+ end
40
+
41
+ # get the selected records of the current page
42
+ def selected_records
43
+ page = current_page
44
+ page ? page.selected_records : []
45
+ end
46
+
47
+ # get the model of the current page
48
+ def model
49
+ page = current_page
50
+ page && page.model
51
+ end
52
+
53
+ def add_pane(options = {})
54
+ pi = Wx::AuiPaneInfo.new
55
+ options.each {|setter, value| pi.send("set_#{setter}", value)}
56
+ if control = yield(pi)
57
+ @manager.add_pane(control, pi)
58
+ @manager.update
59
+ end
60
+ end
61
+
62
+ def add_page(options = {})
63
+ # Handle default options
64
+ options[:select] = default_page_select if options[:select].nil?
65
+ options[:bitmap] = default_page_bitmap if options[:bitmap].nil?
66
+ options[:caption] = default_page_caption if options[:caption].nil?
67
+
68
+ if control = yield(@notebook)
69
+ control.extend PageStatus
70
+ @notebook.add_page(control, options[:caption], options[:select], options[:bitmap])
71
+ end
72
+ end
73
+
74
+ def close_page(window)
75
+ index = @notebook.get_page_index(window)
76
+ @notebook.delete_page(index) if index
77
+ end
78
+
79
+ def current_page
80
+ @notebook.get_page(@notebook.get_selection)
81
+ end
82
+
83
+ def art_provider
84
+ Wx::ArtProvider
85
+ end
86
+
87
+ protected
88
+
89
+ def default_page_select
90
+ true
91
+ end
92
+
93
+ def default_page_bitmap
94
+ @page_bmp
95
+ end
96
+
97
+ def default_page_caption
98
+ "Page"
99
+ end
100
+
101
+ def on_erase_background(event)
102
+ event.skip
103
+ end
104
+
105
+ def on_size(event)
106
+ event.skip
107
+ end
108
+
109
+ def on_page_changed(event)
110
+ new_status = @notebook.get_page(event.get_selection).status_text || ''
111
+ get_status_bar.set_status_text(new_status)
112
+ end
113
+
114
+ def create_notebook
115
+ client_size = get_client_size
116
+
117
+ @notebook = Wx::AuiNotebook.new( self, Wx::ID_ANY,
118
+ Wx::Point.new(client_size.x, client_size.y),
119
+ Wx::Size.new(430, 200),
120
+ Wx::AUI_NB_DEFAULT_STYLE|Wx::AUI_NB_TAB_EXTERNAL_MOVE|Wx::NO_BORDER)
121
+
122
+ @page_bmp = Wx::ArtProvider::get_bitmap( Wx::ART_NORMAL_FILE,
123
+ Wx::ART_OTHER,
124
+ Wx::Size.new(16,16) )
125
+
126
+ @notebook
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+ end