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.
- data/lib/wx_sugar/accessors.rb +44 -0
- data/lib/wx_sugar/all.rb +56 -0
- data/lib/wx_sugar/class_definitions.rb +114 -0
- data/lib/wx_sugar/delayed_constructors.rb +25 -0
- data/lib/wx_sugar/event_connector.rb +151 -0
- data/lib/wx_sugar/itemdata.rb +80 -0
- data/lib/wx_sugar/keyword_classes.rb +392 -0
- data/lib/wx_sugar/keyword_constructors.rb +268 -0
- data/lib/wx_sugar/layout.rb +231 -0
- data/lib/wx_sugar/menu.rb +186 -0
- metadata +46 -0
@@ -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: []
|