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