wx_sugar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,231 @@
1
+ # = WxSugar - Layout
2
+ #
3
+ # The *Layout* extension provides an easier interface to WxWidgets
4
+ # Sizers, in order to create GUI widget layouts that adapt as windows
5
+ # are resized and widgets added and deleted.
6
+ #
7
+ # == Introduction
8
+ #
9
+ # In most GUI applications, rather than specifying the size and position
10
+ # of widgets in fixed pixels, it's desirable to specify their *relative*
11
+ # position and size and let the GUI adapt the actual display to reflect:
12
+ #
13
+ # * The varying size of standard window chrome (title and status bars,
14
+ # standard buttons etc) across different OSes and themes
15
+ # * User resizing of application windows
16
+ # * Event-driven GUI updates (showing, altering or removing widgets)
17
+ #
18
+ # It is possible but very laborious to handle all of these
19
+ # manually. Fortunately, WxWidgets and WxRuby include the flexible and
20
+ # powerful _Sizer_ classes. Unfortunately, getting the desired effect
21
+ # with Sizers can be a tricky matter of trial and error. The +Arranger+
22
+ # module aims to simplify the use of sizer-based layouts.
23
+ #
24
+ # == Overview
25
+ #
26
+ # A limited number of WxWidgets windows can contain other controls;
27
+ # these include Wx::Panel, Wx::Frame (and descendants) and Wx::Dialog
28
+ # (and descendants). To create flexible, resizeable layouts of controls
29
+ # within these windows involves a few simple steps when this module is
30
+ # in effect.
31
+ #
32
+ # 1. Create the container window as normal
33
+ # 2. Declare the layout strategy desired for that window by calling one
34
+ # of its +arrange_+ methods
35
+ # 3. Add child widgets to the container using its +add+ method, passing
36
+ # any hints about how the child widget should be resized.
37
+ #
38
+ # Here's a simple example that lays out three checkboxes side-by-side,
39
+ # with some padding space around them
40
+ #
41
+ # require 'wx_extensions/layout'
42
+ # ...
43
+ # panel = Wx::Panel.new(parent, -1) # create the container
44
+ # panel.arrange_horizontally(:padding => 4) # specify layout strategy
45
+ # panel.add( Wx::Checkbox.new(panel, -1, 'item A') ) # add item,
46
+ # panel.add( Wx::Checkbox.new(panel, -1, 'item B') ) # another,
47
+ # panel.add( Wx::Checkbox.new(panel, -1, 'item C') ) # and another
48
+
49
+ require 'wx_sugar/class_definitions'
50
+
51
+ module WxSugar
52
+ module Arranger
53
+ def self.included(klass)
54
+ unless klass.instance_methods.include?('add')
55
+ # don't over-write methods in sizer
56
+ klass.module_eval { alias_method :add, :nest }
57
+ end
58
+ end
59
+
60
+ attr_accessor :padding
61
+
62
+ # Returns the underlying sizer currently being used by this
63
+ # container. User code should not normally need to call this method.
64
+ def current_sizer()
65
+ if @current_sizer
66
+ return @current_sizer
67
+ elsif sizer = self.get_sizer
68
+ return sizer
69
+ else
70
+ return @current_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
71
+ end
72
+ end
73
+
74
+ # Set the main or current sizer of this container window to be
75
+ # +a_sizer+. If no block is given, then +a_sizer+ is used as the main
76
+ # default sizer for this window. Note that this form should only be
77
+ # called once, *before* any child widgets have been added to the
78
+ # container.
79
+ #
80
+ # If a block is passed, then the +a_sizer+ is nested within the
81
+ # container window's main default sizer. For the duration of the block
82
+ # Widgets added to the container will be added to the nested sizer. If
83
+ # the nested form is called, +layout+ may contain a layout hint
84
+ # hash. This can contain the key +:proportion+, which should specify
85
+ # the integer resizing proportion for this nested sizer within the
86
+ # container's main arrangement.
87
+ def arrange(a_sizer, layout = {})
88
+ # run as a subordinate block
89
+ if block_given? and @current_sizer
90
+ superior_sizer, @current_sizer = @current_sizer, a_sizer
91
+ yield(self)
92
+ @current_sizer = superior_sizer
93
+ proportion = layout[:proportion] || 0
94
+ superior_sizer.add( a_sizer, proportion,
95
+ Wx::EXPAND|Wx::ALL|Wx::ADJUST_MINSIZE, padding)
96
+ # set as main sizer
97
+ else
98
+ @current_sizer = a_sizer
99
+ yield(self) if block_given?
100
+ self.set_sizer(a_sizer)
101
+ end
102
+ end
103
+
104
+ # Add +child+ to the container window's layout, sizing and placing it
105
+ # according to +layout_hints+
106
+ #
107
+ # Layout hints may contain the keys
108
+ # :proportion
109
+ # :minsize
110
+ def nest(child, layout_hints = {})
111
+ child = build_child(child)
112
+ layout = hints_to_constants(layout_hints)
113
+ proportion = layout_hints[:proportion] || 0
114
+ siz = self.current_sizer()
115
+ padding = layout_hints[:padding] || @padding
116
+ siz.add(child, proportion, layout, padding || 0)
117
+ siz.layout()
118
+
119
+ yield child if block_given?
120
+ return child
121
+ end
122
+
123
+
124
+ def arrange_vertically( layout = { }, &block )
125
+ arrange_linear( layout.merge( :direction => :vertical ), &block )
126
+ end
127
+
128
+ def arrange_horizontally( layout = { }, &block )
129
+ arrange_linear( layout.merge( :direction => :horizontal ), &block )
130
+ end
131
+
132
+ def arrange_linear( layout = { }, &block)
133
+ @padding = layout[:padding] if layout[:padding]
134
+ if layout[:direction].to_s.downcase == 'horizontal'
135
+ direction = Wx::HORIZONTAL
136
+ elsif layout[:direction].to_s.downcase == 'vertical'
137
+ direction = Wx::VERTICAL
138
+ else
139
+ Kernel.raise ArgumentError,
140
+ "Unknown direction '#{layout[:direction].inspect}'"
141
+ end
142
+
143
+ if layout[:label]
144
+ sb = Wx::StaticBox.new( self, -1, layout[:label] )
145
+ sizer = Wx::StaticBoxSizer.new( sb, direction )
146
+ elsif layout[:box]
147
+ sb = Wx::StaticBox.new( self, -1, '' )
148
+ sizer = Wx::StaticBoxSizer.new( sb, direction )
149
+ else
150
+ sizer = Wx::BoxSizer.new(direction)
151
+ end
152
+ arrange(sizer, layout, &block)
153
+ end
154
+
155
+ # takes hash arguments +layout+
156
+ #
157
+ # :rows - integer, number of rows (mandatory, see below)
158
+ # :cols - integer, number of columns (mandatory, see below)
159
+ # :vgap - integer, extra vertical space between each child (optional)
160
+ # :hgap - integer, extra horizontal space between each child (optional)
161
+ #
162
+ # At least one of +:rows+ and +:cols+ must be specified. If one is not
163
+ # specified, the other will be calculated dynamically based on the
164
+ # total number of child widgets added.
165
+ #
166
+ def arrange_grid(layout, &block)
167
+ unless layout[:rows] or layout[:cols]
168
+ Kernel.raise ArgumentError,
169
+ "At least one of :rows or :cols must be specified"
170
+ end
171
+
172
+ if layout[:padding]
173
+ layout[:vgap] = layout[:hgap] = layout[:padding]
174
+ end
175
+
176
+ # wxruby wants them in this order, and with nought if null
177
+ args = [ :rows, :cols, :vgap, :hgap ].map { | arg | layout[arg] or 0 }
178
+ sizer = Wx::FlexGridSizer.new(*args)
179
+ arrange( sizer, layout, &block )
180
+ end
181
+
182
+ # Construct a WxWidget as specified by +child+ to add to my arrangement
183
+ def build_child(child)
184
+ case child
185
+ when Proc # delayed constructor
186
+ child = child.call(self)
187
+ when Class # bare class
188
+ child = child.new(self)
189
+ when Wx::Window, Wx::Sizer # ready-to-go widget
190
+ child = child
191
+ else
192
+ Kernel.raise ArgumentError,
193
+ "Cannot add #{child.inspect} to #{self}"
194
+ end
195
+ end
196
+
197
+
198
+ # Convert a hash of layout hints to WxWidgets Sizer constants
199
+ def hints_to_constants(layout_hints)
200
+ layout = Wx::ALL
201
+
202
+ if layout_hints[:minsize]
203
+ layout = layout | Wx::ADJUST_MINSIZE|Wx::EXPAND
204
+ end
205
+ if layout_hints[:expand] || layout_hints[:proportion]
206
+ layout = layout | Wx::EXPAND
207
+ end
208
+
209
+
210
+ if layout_hints[:align]
211
+ begin
212
+ align_const = Wx::const_get('ALIGN_' << layout_hints[:align].to_s.upcase)
213
+ layout = layout | align_const
214
+ rescue NameError
215
+ Kernel.raise ArgumentError,
216
+ "Invalid align argument #{layout_hints[:align]}"
217
+ end
218
+ end
219
+ layout
220
+ end
221
+ end
222
+ end
223
+
224
+ win_classes = [ WxSugar::FRAME_CLASSES,
225
+ WxSugar::DIALOG_CLASSES,
226
+ WxSugar::SIZER_CLASSES,
227
+ WxSugar::MISC_WINDOW_CLASSES ].flatten
228
+
229
+ win_classes.each do | klass |
230
+ klass.class_eval { include WxSugar::Arranger }
231
+ end
@@ -0,0 +1,186 @@
1
+ module Wx::Extensions
2
+ module EasyMenuName
3
+ def clean_name(str)
4
+ str.gsub(/\&/, '')
5
+ end
6
+
7
+ def internal_name(str)
8
+ str.gsub(/\s+/, "_").gsub(/\W/, "").downcase
9
+ end
10
+ end
11
+
12
+ module EasyMenuBar
13
+ include EasyMenuName
14
+ attr_accessor :target, :source
15
+ def connect(source, target = source)
16
+ self.source = source
17
+ # source.set_menu_bar(self)
18
+ self.target = target
19
+ end
20
+
21
+ def add_menu(title)
22
+ menu = Wx::Menu.new()
23
+ menu.target = self.target
24
+ menu.source = self.source
25
+ yield menu
26
+ append(menu, title)
27
+ # these are here because wxruby 0.6.0 doesn't
28
+ # support get_menu and find_menue
29
+ menu_labels << internal_name(title)
30
+ menu_menus << menu
31
+ menu
32
+ end
33
+
34
+ def menu_menus()
35
+ @menu_menus||= []
36
+ end
37
+
38
+ def menu_labels()
39
+ @menu_labels ||= []
40
+ end
41
+
42
+ def get_menu(idx)
43
+ menu_menus[idx]
44
+ end
45
+
46
+ # only used if find_menu is not available in WxRuby
47
+ def find_menu(str)
48
+ menu_labels.index( internal_name(str) ) || Wx::NOT_FOUND
49
+ end
50
+
51
+ # Return the Menu item corresponding to +menu_id+. This can be a zero-based
52
+ # integer specifying the menu's offset within the MenuBar, or a string
53
+ # title, with or without accelerator key.
54
+ def [](menu_id)
55
+ case menu_id
56
+ when Integer
57
+ index = menu_id
58
+ when String
59
+ # a_MenuBar.find_menu missing in wxruby 0.6.0
60
+ index = find_menu(menu_id)
61
+ raise RuntimeError, "No menu called #{menu_id}" if index == Wx::NOT_FOUND
62
+ else
63
+ raise ArgumentError, "Bad menu specifier #{menu_id.inspect}"
64
+ end
65
+ get_menu(index)
66
+ end
67
+ end
68
+
69
+ module EasyMenu
70
+ include EasyMenuName
71
+ @base_id = 1000
72
+ def self.next_id()
73
+ @base_id = @base_id ? @base_id + 1 : 2000
74
+ end
75
+
76
+ # The target window for menu events from this source
77
+ attr_accessor :target, :source
78
+ def menu_ids(*args)
79
+ @menu_ids ||= {}
80
+ end
81
+
82
+ def next_id()
83
+ EasyMenu.next_id()
84
+ end
85
+
86
+ def [](ident)
87
+ case ident
88
+ when String, Symbol
89
+ menu_id = menu_ids[internal_name(ident)] or
90
+ raise RuntimeError, "Not found #{ident} #{menu_ids.inspect}"
91
+ when Fixnum
92
+ menu_id = ident
93
+ end
94
+ # find_item(menu_id)
95
+ end
96
+
97
+ # adds the item +command_str+ to the menu, with the optional
98
+ # shortcut key +command_key+, and sets it to run the associated
99
+ # block when the menu item is chosen. The menu item can later be
100
+ # referred to by the symbol with the commands name with special
101
+ # characters removed andspaces turned to underscores. For
102
+ # example, "Save Project" becomes :save_project
103
+ #
104
+ # The handler event can be specified in a number of ways:
105
+ # 1. Passing a block, which can optionally receive an event parameter
106
+ # 2. Passing a :handler option, which can be a method name in the target,
107
+ # or a Proc or BoundMethod object.
108
+ # 3. Simply defining a method in the +target+ called on_xxxx, where xxxx
109
+ # is the lower-case name of the menu item with special characters removed.
110
+ #
111
+ # So, if creating a menu item called 'Save Project', simply define a method
112
+ # called +on_save_project+ in the event target.
113
+ def add(command_str, options = {}, &block)
114
+ ident = internal_name(command_str)
115
+ const = options[:sys_id] || self.next_id()
116
+ menu_ids[ident] = const
117
+
118
+ # Find out how the handler is defined:
119
+ # Using an explicit block?
120
+ if block
121
+ handler = block
122
+ # Specifying a :handler in the options hash
123
+ elsif meth = options[:handler]
124
+ # .. a method object?
125
+ if meth.respond_to?(:call)
126
+ handler = meth
127
+ # .. or a method name?
128
+ else
129
+ handler = target.method(meth)
130
+ end
131
+ # or else try guessing name from among target's methods
132
+ else
133
+ handler = target.method('on_' << ident)
134
+ end
135
+
136
+ if options[:checked]
137
+ itemtype = Wx::ITEM_CHECK
138
+ elsif options[:radio]
139
+ itemtype = Wx::ITEM_RADIO
140
+ else
141
+ itemtype = Wx::ITEM_NORMAL
142
+ end
143
+
144
+ command_key = options[:shortcut] || ''
145
+ append(const, "#{command_str}\t#{command_key}", "", itemtype)
146
+ # define the WxWidgets handler, passing a event object if required
147
+ if handler.arity == 1
148
+ source.evt_menu(const) { | e | handler.call(e) }
149
+ else
150
+ source.evt_menu(const) { handler.call() }
151
+ end
152
+ return ident
153
+ end
154
+
155
+ def add_separator
156
+ append_separator
157
+ end
158
+
159
+ # pass a series of symbol idents eg :save_project, :close
160
+ def enable_items(*idents)
161
+ idents.each { | ident | self.enable( self[ident], true ) }
162
+ end
163
+ alias :enable_item :enable_items
164
+
165
+ def disable_items(*idents)
166
+ idents.each { | ident | self.enable( self[ident], false ) }
167
+ end
168
+ alias :disable_item :disable_items
169
+
170
+ def check_items(*idents)
171
+ idents.each { | ident | self.check( self[ident], true ) }
172
+ end
173
+ alias :check_item :check_items
174
+
175
+ def uncheck_items(*idents)
176
+ idents.each { | ident | self.check( self[ident], false ) }
177
+ end
178
+ alias :uncheck_item :uncheck_items
179
+ end
180
+ class Wx::MenuBar
181
+ include EasyMenuBar
182
+ end
183
+ class Wx::Menu
184
+ include EasyMenu
185
+ end
186
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: wx_sugar
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-09-07
8
+ summary: Syntax extensions for WxRuby.
9
+ require_paths:
10
+ - lib
11
+ email: alex@pressure.to
12
+ homepage: http://www.pressure.to/qda/
13
+ rubyforge_project: weft-qda
14
+ description: Ruby-ifies the ruby API for WxRuby.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.8.1
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Alex Fenton
29
+ files:
30
+ - lib/wx_sugar/accessors.rb
31
+ - lib/wx_sugar/all.rb
32
+ - lib/wx_sugar/class_definitions.rb
33
+ - lib/wx_sugar/delayed_constructors.rb
34
+ - lib/wx_sugar/event_connector.rb
35
+ - lib/wx_sugar/itemdata.rb
36
+ - lib/wx_sugar/keyword_classes.rb
37
+ - lib/wx_sugar/keyword_constructors.rb
38
+ - lib/wx_sugar/layout.rb
39
+ - lib/wx_sugar/menu.rb
40
+ test_files: []
41
+ rdoc_options: []
42
+ extra_rdoc_files: []
43
+ executables: []
44
+ extensions: []
45
+ requirements: []
46
+ dependencies: []