wx_sugar 0.1.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.
@@ -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: []