reactive-wx 0.2.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.
Files changed (46) hide show
  1. data/LICENSE +20 -0
  2. data/Manifest +45 -0
  3. data/README +22 -0
  4. data/Rakefile +5 -0
  5. data/lib/reactive-wx.rb +19 -0
  6. data/lib/reactive-wx/accessors.rb +15 -0
  7. data/lib/reactive-wx/accessors/control_with_items.rb +22 -0
  8. data/lib/reactive-wx/accessors/date_picker_ctrl.rb +17 -0
  9. data/lib/reactive-wx/accessors/text_ctrl.rb +17 -0
  10. data/lib/reactive-wx/binder.rb +49 -0
  11. data/lib/reactive-wx/default_handler.rb +50 -0
  12. data/lib/reactive-wx/helpers.rb +10 -0
  13. data/lib/reactive-wx/helpers/asset_helper.rb +21 -0
  14. data/lib/reactive-wx/helpers/exception_helper.rb +82 -0
  15. data/lib/reactive-wx/helpers/forms.rb +40 -0
  16. data/lib/reactive-wx/request.rb +13 -0
  17. data/lib/reactive-wx/wx_ext.rb +42 -0
  18. data/lib/reactive-wx/wx_ext/arranger.rb +33 -0
  19. data/lib/reactive-wx/wx_ext/aui_toolbar.rb +22 -0
  20. data/lib/reactive-wx/wx_ext/event_binder.rb +11 -0
  21. data/lib/reactive-wx/wx_ext/form_grid_sizer.rb +54 -0
  22. data/lib/reactive-wx/wx_ext/message_dialog.rb +62 -0
  23. data/lib/reactive-wx/wx_ext/panel_dialog.rb +14 -0
  24. data/lib/reactive-wx/wx_ext/semi_modal.rb +233 -0
  25. data/lib/reactive-wx/wx_ext/toolbar.rb +29 -0
  26. data/lib/reactive-wx/wx_ext/window.rb +16 -0
  27. data/reactive_app_generators/wx/USAGE +6 -0
  28. data/reactive_app_generators/wx/templates/application_helper.wx.rb +8 -0
  29. data/reactive_app_generators/wx/templates/layout.wx.erb +2 -0
  30. data/reactive_app_generators/wx/templates/main_controller.rb +10 -0
  31. data/reactive_app_generators/wx/templates/main_helper.wx.rb +9 -0
  32. data/reactive_app_generators/wx/templates/run.wx.erb +22 -0
  33. data/reactive_app_generators/wx/templates/show.wx.erb +2 -0
  34. data/reactive_app_generators/wx/wx_generator.rb +54 -0
  35. data/reactive_generators/view/USAGE +11 -0
  36. data/reactive_generators/view/templates/create.wx.rb +0 -0
  37. data/reactive_generators/view/templates/delete.wx.rb +0 -0
  38. data/reactive_generators/view/templates/destroy.wx.rb +0 -0
  39. data/reactive_generators/view/templates/edit.wx.rb +0 -0
  40. data/reactive_generators/view/templates/index.wx.rb +0 -0
  41. data/reactive_generators/view/templates/layout.wx.rb +0 -0
  42. data/reactive_generators/view/templates/new.wx.rb +0 -0
  43. data/reactive_generators/view/templates/show.wx.rb +0 -0
  44. data/reactive_generators/view/templates/update.wx.rb +0 -0
  45. data/reactive_generators/view/view_generator.rb +25 -0
  46. metadata +134 -0
@@ -0,0 +1,13 @@
1
+ module Reactive # :nodoc:
2
+ module WxOutput # :nodoc:
3
+
4
+ class Request < Reactive::Request # :nodoc:
5
+ attr_accessor :local_assigns
6
+
7
+ def default_format
8
+ :wx
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # wxRuby extensions
3
+ # Currently at experimental stage, sparingly tested on Win32 only.
4
+ #
5
+ # Author: Pascal Hurni
6
+ # License: MIT
7
+ #
8
+
9
+ module WxExtensions # :nodoc: all
10
+ end
11
+
12
+ require 'wx'
13
+ require 'reactive-wx/wx_ext/window'
14
+ require 'reactive-wx/wx_ext/event_binder'
15
+ require 'reactive-wx/wx_ext/message_dialog'
16
+ require 'reactive-wx/wx_ext/panel_dialog'
17
+ require 'reactive-wx/wx_ext/semi_modal'
18
+ require 'reactive-wx/wx_ext/toolbar'
19
+ require 'reactive-wx/wx_ext/aui_toolbar'
20
+ require 'reactive-wx/wx_ext/form_grid_sizer'
21
+ require 'reactive-wx/wx_ext/arranger'
22
+
23
+ # WARNING: module WxRubyStyleAccessors is a mixin which also redefines method_missing. So it catches first.
24
+ # Thus, we first extend Dialog so that our sugar is before in the chain!
25
+ Wx::Dialog.send(:include, WxExtensions::Window)
26
+ Wx::Window.send(:include, WxExtensions::Window)
27
+
28
+ Wx::EvtHandler.send(:include, WxExtensions::EventBinder)
29
+ Wx::Dialog.send(:include, WxExtensions::SemiModalDialog)
30
+ silence_warnings { Wx::const_set(:MessageDialog, WxExtensions::MessageDialog) }
31
+ Wx::ToolBar.send(:include, WxExtensions::ToolBar)
32
+ Wx::AuiToolBar.send(:include, WxExtensions::AuiToolBar) if defined? Wx::AuiToolBar
33
+
34
+ # WARNING: module WxSugar::Arranger was monkey patched in wx_ext/arranger.rb
35
+
36
+ unless Wx::Window.instance_methods.include? 'bring_to_front'
37
+ # simple solution for feature request #1227
38
+ class Wx::Window
39
+ alias_method :bring_to_front, :raise
40
+ remove_method :raise
41
+ end
42
+ end
@@ -0,0 +1,33 @@
1
+ # TODO: Currently uses monkey patching, because WxSugar::Arranger is already a mix-in module.
2
+ module WxSugar # :nodoc: all
3
+ module Arranger
4
+ # takes hash arguments +layout+
5
+ #
6
+ # :rows - integer, number of rows (mandatory, see below)
7
+ # :cols - integer, number of columns (mandatory, see below)
8
+ # :vgap - integer, extra vertical space between each child (optional)
9
+ # :hgap - integer, extra horizontal space between each child (optional)
10
+ #
11
+ # At least one of +:rows+ and +:cols+ must be specified. If one is not
12
+ # specified, the other will be calculated dynamically based on the
13
+ # total number of child widgets added.
14
+ #
15
+ def arrange_formgrid(layout, &block)
16
+ Kernel.raise ArgumentError, "Pass eihter :cols or :rows, not both!" if layout[:cols] && layout[:rows]
17
+ # if layout[:padding]
18
+ # layout[:vgap] = layout[:hgap] = layout[:padding]
19
+ # end
20
+
21
+ # wxruby wants them in this order, and with nought if null
22
+ args = [ :vgap, :hgap ].map { | arg | layout[arg] or 0 }
23
+ sizer = WxExtensions::FormGridSizer.new(layout[:rows], layout[:cols], *args)
24
+ arrange( sizer, layout, &block )
25
+ end
26
+
27
+ def hints_to_constants_with_formgrid(layout_hints)
28
+ hints_to_constants_without_formgrid(layout_hints) + (layout_hints[:span] ? 0x10000000 : 0)
29
+ end
30
+
31
+ alias_method_chain :hints_to_constants, :formgrid
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module WxExtensions
2
+ module AuiToolBar
3
+ def self.included(base) #:nodoc:
4
+ # base.send :alias_method_chain, :initialize, :extension
5
+ base.send :alias_method_chain, :add_tool, :extension
6
+ end
7
+
8
+ def initialize_with_extension(*args)
9
+ initialize_without_extension(*args)
10
+ if block_given?
11
+ yield self
12
+ realize
13
+ end
14
+ end
15
+
16
+ def add_tool_with_extension(*args, &block)
17
+ item = add_tool_without_extension(*args)
18
+ evt_tool(item.get_id, &block) if block
19
+ item
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module WxExtensions
2
+ # Adds a cooked way of connecting events to handler.
3
+ # Will be injected into Wx::EvtHandler
4
+ module EventBinder
5
+ def cooked_connect(event_name, window_or_id, meth = nil, &block)
6
+ handler = acquire_handler(meth, block)
7
+ id = acquire_id(window_or_id)
8
+ connect(id, Wx::ID_ANY, self.class.event_type_for_name("evt_#{event_name}".to_sym), &handler)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,54 @@
1
+ module WxExtensions
2
+ class FormGridSizer < Wx::GridBagSizer
3
+ def initialize(rows, cols, hgap, vgap)
4
+ super(hgap, vgap)
5
+
6
+ @cols, @rows = cols, rows
7
+ @cols = 1 if @cols.nil? && @rows.nil?
8
+ #puts "#{self} arrange: c:#{@cols} r:#{@rows}"
9
+
10
+ @span = @cols ? Wx::GBSpan.new(1,2) : Wx::GBSpan.new(2,1)
11
+ @defaultspan = Wx::GBSpan.new(1,1)
12
+
13
+ @row = @col = 0
14
+
15
+ if @cols
16
+ 1.step(@cols*2, 2) {|i| add_growable_col(i, 1); "puts grow: #{i}"}
17
+ else
18
+ add_growable_col(1, 1)
19
+ end
20
+ end
21
+
22
+ def add(child, prop = 0, flag = 0, border = 0, userdata = nil)
23
+ span = (flag & 0x10000000 == 0x10000000) || child.is_a?(FormGridSizer)
24
+ #puts "#{self} putting at r:#{@row}, c:#{@col} #{child.class} #{flag} #{span}"
25
+ item = super(child, Wx::GBPosition.new(@row, @col), span ? @span : @defaultspan, flag, border, userdata)
26
+ add_growable_row(@row, prop) if span
27
+ move_next
28
+ move_next if span
29
+ item
30
+ end
31
+
32
+ protected
33
+
34
+ def move_next()
35
+ if @cols
36
+ if (@col += 1) >= @cols * 2
37
+ @row += 1
38
+ @col = 0
39
+ end
40
+ else
41
+ if (@col += 1) % 2 == 0
42
+ if (@row += 1) % @rows == 0
43
+ @row = 0
44
+ #puts "grow: #{@col+1}"
45
+ add_growable_col(@col+1, 1)
46
+ else
47
+ @col -=2
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ module WxExtensions
2
+ # A class to allow modeless message dialogs
3
+ # The code is ugly as it is a quick translation of the generic MessageDialog C source code
4
+ class MessageDialog < Wx::Dialog
5
+
6
+ def initialize(parent, *args)
7
+ message = args[0] if args[0]
8
+ caption = args[1] ? args[1] : 'Message'
9
+ style = args[2] ? args[2] : Wx::OK | Wx::CENTRE
10
+ pos = args[3] ? args[3] : Wx::DEFAULT_POSITION
11
+ if (options = args.last).is_a? Hash
12
+ message = options[:message] if options[:message]
13
+ caption = options[:caption] if options[:caption]
14
+ style = options[:style] if options[:style]
15
+ pos = options[:pos] if options[:pos]
16
+ end
17
+
18
+ dialog_style = (style & (Wx::OK|Wx::CANCEL|Wx::YES|Wx::NO|Wx::HELP|Wx::NO_DEFAULT)) == 0 ? Wx::CAPTION : Wx::DEFAULT_DIALOG_STYLE
19
+ super(parent, Wx::ID_ANY, caption, pos, Wx::DEFAULT_SIZE, dialog_style)
20
+ @parent = parent # for SemiModalDialog
21
+
22
+ top_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
23
+ icon_text = Wx::BoxSizer.new(Wx::HORIZONTAL)
24
+
25
+ if (style & Wx::ICON_MASK) != 0
26
+ @@icon_map ||= {Wx::ICON_ERROR => Wx::ART_ERROR, Wx::ICON_INFORMATION => Wx::ART_INFORMATION, Wx::ICON_WARNING => Wx::ART_WARNING, Wx::ICON_QUESTION => Wx::ART_QUESTION}
27
+ @@icon_map.default ||= Wx::ART_ERROR
28
+ icon_text.add(Wx::StaticBitmap.new(self, Wx::ID_ANY, Wx::ArtProvider.get_icon(@@icon_map[style & Wx::ICON_MASK], Wx::ART_MESSAGE_BOX)), 0, Wx::CENTER)
29
+ end
30
+
31
+ icon_text.add(Wx::StaticText.new(self, Wx::ID_ANY, message.gsub('&','&&')), 0, Wx::ALIGN_CENTER | Wx::LEFT, 10)
32
+ top_sizer.add(icon_text, 1, Wx::CENTER | Wx::LEFT|Wx::RIGHT|Wx::TOP, 10)
33
+
34
+ center_flag = Wx::EXPAND
35
+ center_flag = Wx::ALIGN_CENTRE if (style & Wx::YES_NO) != 0
36
+ center_flag = Wx::ALIGN_CENTRE if RUBY_PLATFORM =~ /win/
37
+ sizer_btn = create_button_sizer(style & (Wx::OK|Wx::CANCEL|Wx::YES|Wx::NO|Wx::HELP|Wx::NO_DEFAULT))
38
+ top_sizer.add(sizer_btn, 0, center_flag | Wx::ALL, 10)
39
+
40
+ set_auto_layout(true)
41
+ set_sizer(top_sizer)
42
+
43
+ top_sizer.set_size_hints(self)
44
+ top_sizer.fit(self)
45
+ size = get_size
46
+ if size.get_width < size.get_height*3/2
47
+ size.set_width(size.get_height*3/2)
48
+ set_size(size)
49
+ end
50
+
51
+ centre(Wx::BOTH | Wx::CENTER_FRAME)
52
+
53
+ evt_button(Wx::ID_YES) { end_modal(Wx::ID_YES) }
54
+ evt_button(Wx::ID_NO) { end_modal(Wx::ID_NO) }
55
+ evt_button(Wx::ID_CANCEL) do
56
+ # Allow cancellation via ESC/Close button except if only YES and NO are specified.
57
+ end_modal(Wx::ID_CANCEL) if (style & Wx::YES_NO) != Wx::YES_NO || (style & Wx::CANCEL) != 0
58
+ end
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,14 @@
1
+ module WxExtensions
2
+ class PanelDialog < Wx::Dialog
3
+ attr_reader :panel
4
+
5
+ def initialize(*args)
6
+ super(*args, &nil) # We explicitely pass a nil block because WxSugar would call the block (We'll call it with a return value...)
7
+ arrange_vertically
8
+ @panel = yield self
9
+ nest(@panel, :proportion => 1)
10
+ # TODO: set size to the best size
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,233 @@
1
+ module WxExtensions
2
+ # This class acts like a Wx::Panel but will be the root for semi-modal windows (or dialogs)
3
+ # When any children of this RootPanel invokes semi-modal, all the children of this panel will be disabled,
4
+ # and only the semi-modal window will be active.
5
+ class RootPanel < Wx::Panel
6
+
7
+ # Look for the topmost RootPanel in the ancestors
8
+ def self.find_root_panel(current)
9
+ # If current is already a top level, look for a RootPanel in his parent
10
+ current = current.get_parent if current.is_a? Wx::TopLevelWindow
11
+
12
+ root_panel = nil
13
+ begin
14
+ case current
15
+ when nil
16
+ break
17
+ when RootPanel
18
+ root_panel = current
19
+ when Wx::TopLevelWindow
20
+ break
21
+ end
22
+ current = current.get_parent
23
+ end while current
24
+
25
+ raise StandardError.new("There are no RootPanel in the ancestors!") if root_panel.nil?
26
+ root_panel
27
+ end
28
+
29
+ def disable_children
30
+ @screener = Wx::Frame.new(self, :style => #Wx::FRAME_TOOL_WINDOW |
31
+ Wx::FRAME_FLOAT_ON_PARENT |
32
+ Wx::FRAME_NO_TASKBAR |
33
+ #Wx::NO_BORDER |
34
+ 0,
35
+ :size => self.size, :pos => self.get_screen_position)
36
+ @screener.set_background_colour(Wx::SystemSettings.get_colour(Wx::SYS_COLOUR_ACTIVECAPTION))
37
+ @screener.set_transparent(128)
38
+ @screener.show
39
+
40
+ top_level = find_top_level
41
+
42
+ # Hooks to handle moving and resizing
43
+ resize_block = proc do |event|
44
+ update_screener
45
+ @user_resize_block.call if @user_resize_block
46
+ event.skip
47
+ end
48
+ top_level.evt_moving() {|event| update_screener; event.skip}
49
+ top_level.evt_move() {|event| update_screener; event.skip}
50
+ evt_sizing(&resize_block)
51
+ evt_size(&resize_block)
52
+
53
+ # Hooks for handling showing/hiding
54
+ @screener.evt_activate() do |event|
55
+ @user_visible_block.call(true) if @user_visible_block && event.get_active
56
+ event.skip
57
+ end
58
+ evt_set_focus() do |event|
59
+ @user_visible_block.call(true) if @user_visible_block
60
+ event.skip # TODO: Not sure if this is wanted... to be checked on the different platforms
61
+ end
62
+ @timer = Wx::Timer.every(100) do
63
+ # TODO: This timer may tick after window deletion (when the app exits) and thus crash on the *is_shown* method. DONE with evt_window_destroy
64
+ if (shown = is_shown) != @shown
65
+ @shown = shown
66
+ # handle screener
67
+ if @screener
68
+ update_screener if shown
69
+ @screener.show(shown)
70
+ end
71
+ # handler user window
72
+ @user_resize_block.call if @user_resize_block && shown
73
+ @user_visible_block.call(shown) if @user_visible_block
74
+ end
75
+ end
76
+ evt_window_destroy() do |event|
77
+ @timer.stop if @timer
78
+ event.skip
79
+ end
80
+
81
+ # Hook to handle client closing
82
+ # TODO: BUG: event handler used to be on the top_level window, now on the root panel. Test it. This change moves wxruby to be unstable
83
+ evt_close() do |event|
84
+ veto = nil
85
+ veto = @user_close_block.call(event) if @user_close_block
86
+ veto ? event.veto : event.skip
87
+ end
88
+
89
+ =begin only when the patched version of wxAui will be commited in wxWidgets trunk
90
+ # Is the TopLevelWindow of this root panel managed by Aui?
91
+ handler = top_level.get_event_handler
92
+ begin
93
+ # puts "PROCESSING handler #{handler}"
94
+ aui_manager = case handler
95
+ when Wx::AuiManager
96
+ handler
97
+ when Wx::AuiFloatingFrame
98
+ handler.get_owner_manager
99
+ else
100
+ nil
101
+ end
102
+ if aui_manager
103
+ # puts "MANAGED by #{aui_manager}"
104
+ pi = aui_manager.get_pane(self)
105
+ # puts "WITH PI #{pi}"
106
+ if pi
107
+ @aui_manager = aui_manager
108
+ @pi_floatable = pi.is_floatable
109
+ @pi_dockable = pi.is_dockable
110
+ pi.set_floatable(false).set_dockable(false)
111
+ #@aui_manager.update
112
+ end
113
+ break
114
+ end
115
+ end while handler = handler.get_next_handler
116
+ =end
117
+ end
118
+
119
+ def enable_children
120
+ if @aui_manager
121
+ if pi = @aui_manager.get_pane(self)
122
+ pi.set_floatable(@pi_floatable).set_dockable(@pi_dockable)
123
+ end
124
+ end
125
+
126
+ top_level = find_top_level
127
+ top_level.disconnect(Wx::ID_ANY, Wx::ID_ANY, :evt_moving)
128
+ top_level.disconnect(Wx::ID_ANY, Wx::ID_ANY, :evt_move)
129
+ disconnect(Wx::ID_ANY, Wx::ID_ANY, :evt_close)
130
+ disconnect(Wx::ID_ANY, Wx::ID_ANY, :evt_sizing)
131
+ disconnect(Wx::ID_ANY, Wx::ID_ANY, :evt_size)
132
+
133
+ @timer.stop if @timer
134
+ @user_resize_block = nil
135
+ @user_visible_block = nil
136
+
137
+ @screener.destroy
138
+ @screener = nil
139
+ end
140
+
141
+ # The passed block will be called when the RootPanel is resized.
142
+ def on_resize(&block)
143
+ @user_resize_block = block
144
+ end
145
+
146
+ # The passed block will be called when the RootPanel is activated or gains focus.
147
+ # The block should receive one argument which will be a boolean indicating if it should be made visible.
148
+ # Note that setting your window visible may not be enough, #raise should put it in front.
149
+ def on_visible(&block)
150
+ @user_visible_block = block
151
+ end
152
+
153
+ # The passed block will be called when the TopLevel window of this RootPanel is requested
154
+ # to close. The return code of the block is a boolean value indicating if the close should be vetoed.
155
+ # (Thus the normal block is: { not close }
156
+ def on_close(&block)
157
+ @user_close_block = block
158
+ end
159
+
160
+ protected
161
+
162
+ def update_screener
163
+ @screener.set_size(Wx::Rect.new(get_screen_position, size))
164
+ end
165
+
166
+ # don't memoize this method, because the top level may change when docking/floating this panel
167
+ def find_top_level
168
+ # Look for the first top_level window in the ancestors
169
+ top_level = self
170
+ top_level = top_level.get_parent until top_level.is_top_level
171
+ top_level
172
+ end
173
+
174
+ end
175
+
176
+ # Extension for the Dialog class to allow semi-modal dialogs
177
+ module SemiModalDialog
178
+
179
+ def self.included(base) #:nodoc:
180
+ base.send :alias_method_chain, :end_modal, :semi_handling
181
+ base.send :alias_method_chain, :is_modal, :semi_handling
182
+ end
183
+
184
+ # shows the dialog semi-modally.
185
+ # This method will return immediately, you should pass a block to allow processing
186
+ # when the dialog is dismissed. The block will be passed the returned ID
187
+ # Example:
188
+ # dlg.show_semi_modal do |id|
189
+ # case id
190
+ # when Wx::ID_OK
191
+ # ...
192
+ # when Wx::ID_CANCEL
193
+ # ...
194
+ # end
195
+ # end
196
+ def show_semi_modal(destroy_on_close = true, &block)
197
+ @root_panel = RootPanel.find_root_panel(@parent || self) # @parent may be set by MessageDialog
198
+
199
+ @semi_modal_block = block
200
+ @showing_semi_modal = true
201
+ @destroy_on_close = destroy_on_close
202
+
203
+ # FIXME: Until wxRuby 2.0.0 fix the issue #, we have to handle the dismiss ourselves
204
+ evt_close { end_modal_with_semi_handling(Wx::ID_CANCEL) }
205
+ evt_button(Wx::ID_CANCEL) { end_modal_with_semi_handling(Wx::ID_CANCEL) }
206
+ # END OF FIXME
207
+
208
+ @root_panel.on_visible {|visible| show(visible); bring_to_front if visible }
209
+ @root_panel.on_resize { set_size(get_rect.centre_in(@root_panel.get_screen_rect)) }
210
+ @root_panel.on_close { !close }
211
+ @root_panel.disable_children
212
+
213
+ # Show dialog
214
+ centre_on_parent
215
+ show
216
+ end
217
+
218
+ def is_modal_with_semi_handling
219
+ @showing_semi_modal || is_modal_without_semi_handling
220
+ end
221
+
222
+ def end_modal_with_semi_handling(code)
223
+ return end_modal_without_semi_handling(code) unless @showing_semi_modal
224
+
225
+ @root_panel.enable_children
226
+
227
+ @semi_modal_block.call(code) if @semi_modal_block
228
+ @showing_semi_modal = false
229
+ @destroy_on_close ? destroy : hide
230
+ end
231
+
232
+ end
233
+ end