reactive-wx 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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