simple_tk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 688f0fe3a07729c95ffe968c6d1bb55a846ef7d9
4
+ data.tar.gz: b96b21a78f9c6244bc66cb07018c5f356c003473
5
+ SHA512:
6
+ metadata.gz: 524e8e9e20024a885938ea049b292c04a393c5ebbfe7acdcc1600d1fc5a3f48ac313a257a858cee2ae287f95a2fe72b939411338f2bc2545417493ddb55f5de1
7
+ data.tar.gz: 656d163a645705cf9964754a7c96a510d75de50616c190a38b21ff06887817ecbd0cf9cd2e5df00cf27674d639a5232c60d00c7e390ba3e37e9d0ec591bb15a4
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simple_tk.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Beau Barker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # SimpleTk
2
+
3
+ I wanted a really simple GUI library for Ruby. Not because I think Ruby should be used for GUI
4
+ applications, but because I think it should be able to tbe used for GUI applications.
5
+
6
+ Really I wanted something to make a quick configuration app using an existing Ruby gem I had
7
+ written. I started out with a simple console app, but I decided I wanted something even simpler
8
+ for the eventual day when somebody else has to use it.
9
+
10
+ So I searched. First I found Shoes. Which would be acceptable except Shoes 3 does not have a gem
11
+ and Shoes 4 requires JRuby. Ok moving on, we have several other libraries that interface with other
12
+ libraries. And then Tk, which is from the Ruby code base. So time to learn Tk, which turns out has
13
+ a lot of repeated steps and code. Well that's easy enough to correct. I wrote a quick wrapper and
14
+ built my config app.
15
+
16
+ I extracted that wrapper and built it up to support more than just labels, entries, and buttons.
17
+ This gem is the end result of that action.
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'simple_tk'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install simple_tk
34
+
35
+ ## Usage
36
+
37
+ You can build windows using either a block passed to a window constructor or by calling the methods
38
+ on the created window. Call `SimpleTk.run` to run your application. You can use `SimpleTk.alert` and
39
+ `SimpleTk.ask` for basic message boxes. In all cases, the Tk library is available if you need more
40
+ advanced options.
41
+
42
+ ```ruby
43
+ require 'simple_tk'
44
+
45
+ win = SimpleTk::Window.new(title: "My Program", window_geometry: "400x300") do
46
+ add_label :label1, "Hello World!", :column => 0, :row => 0
47
+ add_frame(:frame1, :column => 0, :row => 1) do
48
+ add_button(:btn1, "Button 1", :position => [ 0, 0 ]) do
49
+ SimpleTk.alert "You clicked Button 1!"
50
+ end
51
+ add_button :btn2,
52
+ "Button 2",
53
+ :position => { x: 1, y: 0 },
54
+ :command => ->{ SimpleTk.alert "You clicked Button 2!" }
55
+ end
56
+ end
57
+
58
+ win.add_label :label2, "Goodby World!", :column => 0, :row => 2
59
+
60
+ win.config_column 0, weight: 1
61
+
62
+ (0..win.row_count).each do |row|
63
+ win.config_row row, weight: 1
64
+ end
65
+
66
+ # Use the Tk 'bind' method on the :btn2 object.
67
+ win.object[:frame1].object[:btn2].bind("3") do
68
+ SimpleTk.alert "You right-clicked on Button 2!"
69
+ end
70
+
71
+ SimpleTk.run
72
+ ```
73
+
74
+
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at https://github.com/barkerest/simple_tk.
79
+
80
+
81
+ ## License
82
+
83
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
84
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "simple_tk"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,16 @@
1
+
2
+ module SimpleTk
3
+ ##
4
+ # A general error from the SimpleTk library.
5
+ class SimpleTkError < StandardError
6
+
7
+ end
8
+
9
+ ##
10
+ # A duplicate name was used.
11
+ class DuplicateNameError < SimpleTkError
12
+
13
+ end
14
+
15
+
16
+ end
@@ -0,0 +1,79 @@
1
+ module SimpleTk
2
+ ##
3
+ # A helper class to make hash instance variables more accessible.
4
+ class GetSetHelper
5
+
6
+ ##
7
+ # Creates a helper for a specific hash in a parent object.
8
+ #
9
+ # This helper does not support adding items to the connected hash.
10
+ #
11
+ # Parameters:
12
+ # parent::
13
+ # The parent object this helper accesses.
14
+ # hash_ivar::
15
+ # The name (as a symbol) of the instance variable in the parent object to access.
16
+ # (eg - :@data)
17
+ # auto_symbol::
18
+ # Should names be converted to symbols in the accessors, defaults to true.
19
+ # getter::
20
+ # The method or proc to call when getting a value.
21
+ # If this is a Symbol then it defines the method name of the retrieved value to call.
22
+ # If this is a Proc then it will be called with the retrieved value as an argument.
23
+ # If this is nil then the retrieved value is returned.
24
+ # setter::
25
+ # The method or proc to call when setting a value.
26
+ # If this is a Symbol then it defines the method name of the retrieved value to call with the new value.
27
+ # If this ia a Proc then it will be called with the retrieved value and the new value as arguments.
28
+ # If this is nil then the hash value is replaced with the new value.
29
+ #
30
+ # GetSetHelper.new(my_obj, :@data)
31
+ # GetSetHelper.new(my_obj, :@data, true, :value, :value=)
32
+ # GetSetHelper.new(my_obj, :@data, true, ->(v){ v.value }, ->(i,v){ i.value = v })
33
+ def initialize(parent, hash_ivar, auto_symbol = true, getter = nil, setter = nil)
34
+ @parent = parent
35
+ @hash_ivar = hash_ivar
36
+ @auto_symbol = auto_symbol
37
+ @getter = getter
38
+ @setter = setter
39
+ end
40
+
41
+ ##
42
+ # Gets the value for the given name.
43
+ def [](name)
44
+ name = name.to_sym if @auto_symbol
45
+ h = @parent.instance_variable_get(@hash_ivar)
46
+ if h.include?(name)
47
+ v = h[name]
48
+ if @getter.is_a?(Symbol)
49
+ v.send @getter
50
+ elsif @getter.is_a?(Proc)
51
+ @getter.call v
52
+ else
53
+ v
54
+ end
55
+ else
56
+ raise IndexError
57
+ end
58
+ end
59
+
60
+ ##
61
+ # Sets the value for the given name.
62
+ def []=(name, value)
63
+ name = name.to_sym if @auto_symbol
64
+ h = @parent.instance_variable_get(@hash_ivar)
65
+ if h.include?(name)
66
+ if @setter.is_a?(Symbol)
67
+ h[name].send @setter, value
68
+ elsif @setter.is_a?(Proc)
69
+ @setter.call h[name], value
70
+ else
71
+ h[name] = value
72
+ end
73
+ else
74
+ raise IndexError
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleTk
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module SimpleTk
3
+
4
+ ##
5
+ # Provides :disable, :disabled?, and :enable methods to widgets.
6
+ module DisableHelpers
7
+
8
+ ##
9
+ # Disables this widget.
10
+ def disable
11
+ self.state('disabled')
12
+ end
13
+
14
+ ##
15
+ # Is this widget disabled?
16
+ def disabled?
17
+ self.instate('disabled')
18
+ end
19
+
20
+ ##
21
+ # Enables this widget.
22
+ def enable
23
+ self.state('!disabled')
24
+ end
25
+
26
+ end
27
+
28
+
29
+
30
+ end
@@ -0,0 +1,667 @@
1
+ require 'simple_tk/errors'
2
+ require 'simple_tk/get_set_helper'
3
+ require 'simple_tk/widget_helpers'
4
+
5
+ require 'tk'
6
+
7
+ module SimpleTk
8
+ ##
9
+ # A class representing a window that all the other controls get piled into.
10
+ #
11
+ # We use the "grid" layout manager. Each widget gets assigned to the grid
12
+ # using specific column and row settings.
13
+ #
14
+ # To simplify things the Window class allows either "free" mode or "flow"
15
+ # mode placement. In "free" mode, you must specify the :column and :row
16
+ # option for each widget you add. In "flow" mode, the :column and :row
17
+ # are computed for you automatically.
18
+ #
19
+ # There are multiple ways to give the Window the grid coordinates for the
20
+ # new widget.
21
+ #
22
+ # You can explicitly set the :column, :row, :columnspan, and :rowspan options.
23
+ # The :col option is equivalent to :column and the :colspan option is equivalent
24
+ # to the :columnspan option. The :columnspan and :rowspan options default to
25
+ # a value of 1 if you do not specify them.
26
+ # add_label :xyz, 'XYZ', :column => 1, :row => 2, :columnspan => 1, :rowspan => 1
27
+ # add_label :xyz, 'XYZ', :col => 1, :row => 2, :colspan => 1, :rowspan => 1
28
+ #
29
+ # You can also set the :position option. The :pos option is equivalent to the
30
+ # :position option. The argument to this option is either an Array or a Hash.
31
+ # If an Array is specified, it should have either 2 or 4 values.
32
+ class Window
33
+
34
+ ##
35
+ # Allows you to get or set the linked object for this window.
36
+ #
37
+ # A linked object can be used to provide command callbacks.
38
+ attr_accessor :linked_object
39
+
40
+ ##
41
+ # Creates a new window.
42
+ #
43
+ # Some valid options:
44
+ # title::
45
+ # The title to put on the window.
46
+ # Defaults to "My Application".
47
+ # parent::
48
+ # The parent window for this window.
49
+ # Use your main window for any popup windows you create.
50
+ # Defaults to nil.
51
+ # padding::
52
+ # The padding for the content frame.
53
+ # This can be a single value to have the same padding all around.
54
+ # It can also be two numbers to specify horizontal and vertical padding.
55
+ # If you specify four numbers, they are for left, top, right, and bottom padding.
56
+ # Defaults to "3".
57
+ # sticky::
58
+ # The stickiness for the content frame.
59
+ # This defaults to "nsew" and most likely you don't want to change it, but the
60
+ # option is available.
61
+ #
62
+ # Window options can be prefixed with 'window_' or put into a hash under a 'window' option.
63
+ # SimpleTk::Window.new(window: { geometry: '300x300' })
64
+ # SimpleTk::Window.new(window_geometry: '300x300')
65
+ #
66
+ # Any options not listed above or explicitly assigned to the window are applied to the
67
+ # content frame.
68
+ #
69
+ # If you provide a block it will be executed in the scope of the window.
70
+ def initialize(options = {}, &block)
71
+ @stkw_object_list = {}
72
+ @stkw_var_list = {}
73
+
74
+ options = {
75
+ title: 'My Application',
76
+ padding: '3',
77
+ sticky: 'nsew'
78
+ }.merge(options.inject({}) { |m,(k,v)| m[k.to_sym] = v; m})
79
+
80
+ title = options.delete(:title) || 'My Application'
81
+ parent = options.delete(:parent)
82
+ is_frame = options.delete(:stk___frame)
83
+
84
+ win_opt, options = split_window_content_options(options)
85
+
86
+ @stkw_object_list[:stk___root] =
87
+ if is_frame
88
+ parent
89
+ elsif parent
90
+ TkToplevel.new(parent)
91
+ else
92
+ TkRoot.new
93
+ end
94
+
95
+ unless is_frame
96
+ root.title title
97
+ TkGrid.columnconfigure root, 0, weight: 1
98
+ TkGrid.rowconfigure root, 0, weight: 1
99
+ apply_options root, win_opt
100
+ end
101
+
102
+ @stkw_object_list[:stk___content] = Tk::Tile::Frame.new(root)
103
+ apply_options content, options
104
+
105
+ @stkw_config = {
106
+ placement: :free,
107
+ row_start: -1,
108
+ col_start: -1,
109
+ col_count: -1,
110
+ cur_col: -1,
111
+ cur_row: -1,
112
+ base_opt: { },
113
+ max_col: 0,
114
+ max_row: 0
115
+ }
116
+
117
+ if block_given?
118
+ instance_eval &block
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Gets the number of columns in use.
124
+ def column_count
125
+ @stkw_config[:max_col]
126
+ end
127
+
128
+ ##
129
+ # Gets the number of rows in use.
130
+ def row_count
131
+ @stkw_config[:max_row]
132
+ end
133
+
134
+ ##
135
+ # Allows you to configure the grid options for all cells in a column.
136
+ def config_column(col, options = {})
137
+ TkGrid.columnconfigure content, col, options
138
+ end
139
+
140
+ ##
141
+ # Allows you to configure the grid options for all cells in a row.
142
+ def config_row(row, options = {})
143
+ TkGrid.rowconfigure content, row, options
144
+ end
145
+
146
+ ##
147
+ # Gets all of the children on this window.
148
+ def children
149
+ TkWinfo.children(content)
150
+ end
151
+
152
+ ##
153
+ # Gets objects from the window.
154
+ def object
155
+ @object_helper ||= SimpleTk::GetSetHelper.new(self, :@stkw_object_list, true, nil, ->(i,v) { } )
156
+ end
157
+
158
+ ##
159
+ # Gets or sets variable values for this window.
160
+ def var
161
+ @var_helper ||= SimpleTk::GetSetHelper.new(self, :@stkw_var_list, true, :value, :value=)
162
+ end
163
+
164
+ ##
165
+ # Binds a method or Proc to a window event.
166
+ #
167
+ # event::
168
+ # The event to bind to (eg - "1", "Return", "Shift-2")
169
+ # proc::
170
+ # A predefined proc to bind to the event, only used if no block is provided.
171
+ #
172
+ # If a block is provided it will be bound, otherwise the +proc+ argument is looked at.
173
+ # If the proc argument specifies a Proc it will be bound. If the proc argument is a Symbol
174
+ # and the Window or +linked_object+ responds to the proc, then the method from the Window
175
+ # or +linked_object+ will be bound.
176
+ def on(event, proc = nil, &block)
177
+ cmd = get_command proc, &block
178
+ root.bind(event) { cmd.call }
179
+ end
180
+
181
+ ##
182
+ # Adds a new label to the window.
183
+ #
184
+ # name::
185
+ # A unique label for this item, cannot be nil.
186
+ # label_text::
187
+ # The text to put in the label.
188
+ # If this is a Symbol, then a variable will be created with this value as its name.
189
+ # Otherwise the label will be given the string value as a static label.
190
+ # options::
191
+ # Options for the label. Common options are :column, :row, and :sticky.
192
+ def add_label(name, label_text, options = {})
193
+ add_widget Tk::Tile::Label, name, label_text, options
194
+ end
195
+
196
+ ##
197
+ # Adds a new image to the window.
198
+ #
199
+ # name::
200
+ # A unique label for this item, cannot be nil.
201
+ # image_path::
202
+ # The path to the image, can be relative or absolute.
203
+ # options::
204
+ # Options for the image. Common options are :column, :row, and :sticky.
205
+ def add_image(name, image_path, options = {})
206
+ image = TkPhotoImage.new(file: image_path)
207
+ add_widget Tk::Tile::Label, name, nil, options.merge(image: image)
208
+ end
209
+
210
+ ##
211
+ # Adds a new button to the window.
212
+ #
213
+ # name::
214
+ # A unique label for this item, cannot be nil.
215
+ # label_text::
216
+ # The text to put in the button.
217
+ # If this is a Symbol, then a variable will be created with this value as its name.
218
+ # Otherwise the button will be given the string value as a static label.
219
+ # options::
220
+ # Options for the button. Common options are :column, :row, :sticky, and :command.
221
+ #
222
+ # If a block is provided, it will be used for the button command. Otherwise the :command option
223
+ # will be used. This can be a Proc or Symbol. If it is a Symbol it should reference a method in
224
+ # the current Window object or in the +linked_object+.
225
+ #
226
+ # If no block or proc is provided, a default proc that prints to $stderr is created.
227
+ def add_button(name, label_text, options = {}, &block)
228
+ options = options.dup
229
+ proc = get_command(options.delete(:command), &block) || ->{ $stderr.puts "Button '#{name}' does nothing when clicked." }
230
+ add_widget Tk::Tile::Button, name, label_text, options, &proc
231
+ end
232
+
233
+ ##
234
+ # Adds a new image button to the window.
235
+ #
236
+ # name::
237
+ # A unique label for this item, cannot be nil.
238
+ # image_path::
239
+ # The path to the image, can be relative or absolute.
240
+ # options::
241
+ # Options for the image button. Common options are :column, :row, :sticky, and :command.
242
+ #
243
+ # If a block is provided, it will be used for the button command. Otherwise the :command option
244
+ # will be used. This can be a Proc or Symbol. If it is a Symbol it should reference a method in
245
+ # the current Window object or in the +linked_object+.
246
+ #
247
+ # If no block or proc is provided, a default proc that prints to $stderr is created.
248
+ def add_image_button(name, image_path, options = {}, &block)
249
+ options = options.dup
250
+ image = TkPhotoImage.new(file: image_path)
251
+ proc = get_command(options.delete(:command), &block) || ->{ $stderr.puts "Button '#{name}' does nothing when clicked." }
252
+ add_widget Tk::Tile::Button, name, nil, options.merge(image: image), &proc
253
+ end
254
+
255
+ ##
256
+ # Adds a new text box to the window.
257
+ #
258
+ # name::
259
+ # A unique label for this item, cannot be nil.
260
+ # options::
261
+ # Options for the text box. Common options are :column, :row, :sticky, :width, :value, and :password.
262
+ #
263
+ # Accepts a block or command the same as a button. Unlike the button there is no default proc assigned.
264
+ # The proc, if specified, will be called for each keypress. It should return 1 (success) or 0 (failure).
265
+ def add_text_box(name, options = {}, &block)
266
+ options = options.dup
267
+ if options.delete(:password)
268
+ options[:show] = '*'
269
+ end
270
+ proc = get_command(options.delete(:command), &block)
271
+ item = add_widget Tk::Tile::Entry, name, nil, options.merge(create_var: :textvariable)
272
+ # FIXME: Should the command be processed for validation or bound to a specific event?
273
+ if proc
274
+ item.send :validate, 'key'
275
+ item.send(:validatecommand) { proc.call }
276
+ end
277
+ item
278
+ end
279
+
280
+ ##
281
+ # Adds a new check box to the window
282
+ #
283
+ # name::
284
+ # A unique label for this item, cannot be nil.
285
+ # label_text::
286
+ # The text to put in the button.
287
+ # If this is a Symbol, then a variable will be created with this value as its name.
288
+ # Otherwise the button will be given the string value as a static label.
289
+ # options::
290
+ # Options for the check box. Common options are :column, :row, :sticky, and :value.
291
+ #
292
+ # Accepts a block or command the same as a button. Unlike a button there is no default proc assigned.
293
+ def add_check_box(name, label_text, options = {}, &block)
294
+ options = options.dup
295
+ proc = get_command(options.delete(:command), &block)
296
+ add_widget Tk::Tile::Checkbutton, name, label_text, options.merge(create_var: :variable), &proc
297
+ end
298
+
299
+ ##
300
+ # Adds a new image check box to the window
301
+ #
302
+ # name::
303
+ # A unique label for this item, cannot be nil.
304
+ # image_path::
305
+ # The path to the image, can be relative or absolute.
306
+ # options::
307
+ # Options for the check box. Common options are :column, :row, :sticky, and :value.
308
+ #
309
+ # Accepts a block or command the same as a button. Unlike a button there is no default proc assigned.
310
+ def add_image_check_box(name, image_path, options = {}, &block)
311
+ options = options.dup
312
+ image = TkPhotoImage.new(file: image_path)
313
+ proc = get_command(options.delete(:command), &block)
314
+ add_widget Tk::Tile::Checkbutton, name, nil, options.merge(image: image, create_var: :variable), &proc
315
+ end
316
+
317
+ ##
318
+ # Adds a new radio button to the window.
319
+ #
320
+ # name::
321
+ # A unique label for this item, cannot be nil.
322
+ # group::
323
+ # The name of the variable this radio button uses.
324
+ # label_text::
325
+ # The text to put in the button.
326
+ # If this is a Symbol, then a variable will be created with this value as its name.
327
+ # Otherwise the button will be given the string value as a static label.
328
+ # options::
329
+ # Options for the check box. Common options are :column, :row, :sticky, :value, and :selected.
330
+ #
331
+ # Accepts a block or command the same as a button. Unlike a button there is no default proc assigned.
332
+ def add_radio_button(name, group, label_text, options = {}, &block)
333
+ raise ArgumentError, 'group cannot be blank' if group.to_s.strip == ''
334
+ options = options.dup
335
+ group = group.to_sym
336
+ @stkw_var_list[group] ||= TkVariable.new
337
+ options[:variable] = @stkw_var_list[group]
338
+ options[:value] ||= name.to_s.strip
339
+ if options.delete(:selected)
340
+ @stkw_var_list[group] = options[:value]
341
+ end
342
+ proc = get_command(options.delete(:command), &block)
343
+ add_widget Tk::Tile::Radiobutton, name, label_text, options, &proc
344
+ end
345
+
346
+ ##
347
+ # Adds a new radio button to the window.
348
+ #
349
+ # name::
350
+ # A unique label for this item, cannot be nil.
351
+ # group::
352
+ # The name of the variable this radio button uses.
353
+ # image_path::
354
+ # The path to the image, can be relative or absolute.
355
+ # options::
356
+ # Options for the check box. Common options are :column, :row, :sticky, :value, and :selected.
357
+ #
358
+ # Accepts a block or command the same as a button. Unlike a button there is no default proc assigned.
359
+ def add_image_radio_button(name, group, image_path, options = {}, &block)
360
+ raise ArgumentError, 'group cannot be blank' if group.to_s.strip == ''
361
+ options = options.dup
362
+ group = group.to_sym
363
+ @stkw_var_list[group] ||= TkVariable.new
364
+ options[:variable] = @stkw_var_list[group]
365
+ options[:value] ||= name.to_s.strip
366
+ if options.delete(:selected)
367
+ @stkw_var_list[group] = options[:value]
368
+ end
369
+ proc = get_command(options.delete(:command), &block)
370
+ image = TkPhotoImage.new(file: image_path)
371
+ add_widget Tk::Tile::Radiobutton, name, nil, options.merge(image: image), &proc
372
+ end
373
+
374
+ ##
375
+ # Adds a new combo box to the window.
376
+ #
377
+ # name::
378
+ # A unique label for this item, cannot be nil.
379
+ # options::
380
+ # Options for the check box. Common options are :column, :row, :sticky, :value, and :values.
381
+ #
382
+ # The :values option will define the items in the combo box, the :value option sets the selected option if set.
383
+ #
384
+ # Accepts a block or command the same as a button. Unlike a button there is no default proc assigned.
385
+ def add_combo_box(name, options = {}, &block)
386
+ options = options.dup
387
+ proc = get_command(options.delete(:command), &block)
388
+ item = add_widget TK::Tile::Combobox, name, nil, options.merge(create_var: :textvariable)
389
+ if proc
390
+ item.bind('<ComboboxSelected>') { proc.call }
391
+ end
392
+ item
393
+ end
394
+
395
+ ##
396
+ # Adds a new frame to the window.
397
+ #
398
+ # name::
399
+ # A unique label for this item, cannot be nil.
400
+ # options::
401
+ # Options for the frame. Common options are :column, :row, and :padding.
402
+ #
403
+ # A frame can be used as a sub-window. The returned object has all of the same methods as a Window
404
+ # so you can add other widgets to it and use it as a grid within the parent grid.
405
+ #
406
+ # If you provide a block, it will be executed in the scope of the frame.
407
+ def add_frame(name, options = {}, &block)
408
+ name = name.to_sym
409
+ raise DuplicateNameError if @stkw_object_list.include?(name)
410
+ options = fix_position(options.dup)
411
+ @stkw_object_list[name] = SimpleTk::Window.new(options.merge(parent: content, stk___frame: true), &block)
412
+ end
413
+
414
+ ##
415
+ # Sets the placement mode for this window.
416
+ #
417
+ # mode::
418
+ # This can be either :free or :flow.
419
+ # The default mode for a new window is :free.
420
+ # Frames do not inherit this setting from the parent window.
421
+ # In free placement mode, you must provide the :column and :row for every widget.
422
+ # In flow placement mode, you must not provide the :column or :row for any widget.
423
+ # Flow placement will add each widget to the next column moving to the next row as
424
+ # needed.
425
+ # options::
426
+ # Options are ignored in free placement mode.
427
+ # In flow placement mode, you must provide :first_column, :first_row, and either
428
+ # :last_column or :column_count. If you specify both :last_column and :column_count
429
+ # then :last_column will be used. The :first_column and :first_row must be greater
430
+ # than or equal to zero. The :last_column can be equal to the first column but
431
+ # the computed :column_count must be at least 1.
432
+ def set_placement_mode(mode, options = {})
433
+ raise ArgumentError, 'mode must be :free or :flow' unless [ :free, :flow ].include?(mode)
434
+ if mode == :flow
435
+ first_col = options[:first_column]
436
+ col_count =
437
+ if (val = options[:last_column])
438
+ val - first_col + 1
439
+ elsif (val = options[:column_count])
440
+ val
441
+ else
442
+ nil
443
+ end
444
+ first_row = options[:first_row]
445
+ raise ArgumentError, ':first_column must be provided for flow placement' unless first_col
446
+ raise ArgumentError, ':first_row must be provided for flow placement' unless first_row
447
+ raise ArgumentError, ':last_column or :column_count must be provided for flow placement' unless col_count
448
+ raise ArgumentError, ':first_column must be >= 0' unless first_col >= 0
449
+ raise ArgumentError, ':first_row must be >= 0' unless first_row >= 0
450
+ raise ArgumentError, ':column_count must be > 0' unless col_count > 0
451
+
452
+ @stkw_config.merge!(
453
+ placement: :flow,
454
+ col_start: first_col,
455
+ row_start: first_row,
456
+ col_count: col_count,
457
+ cur_row: first_row,
458
+ cur_col: first_col
459
+ )
460
+ elsif mode == :free
461
+ @stkw_config.merge!(
462
+ placement: :free,
463
+ col_start: -1,
464
+ row_start: -1,
465
+ col_count: -1,
466
+ cur_row: -1,
467
+ cur_col: -1
468
+ )
469
+ end
470
+ end
471
+
472
+ ##
473
+ # Executes a block with the specified options preset.
474
+ def with_options(options = {}, &block)
475
+ return unless block
476
+ backup_options = @stkw_config[:base_opt]
477
+ @stkw_config[:base_opt] = @stkw_config[:base_opt].merge(options)
478
+ begin
479
+ instance_eval &block
480
+ ensure
481
+ @stkw_config[:base_opt] = backup_options
482
+ end
483
+ end
484
+
485
+ private
486
+
487
+ def root
488
+ @stkw_object_list[:stk___root]
489
+ end
490
+
491
+ def content
492
+ @stkw_object_list[:stk___content]
493
+ end
494
+
495
+ def fix_position(options)
496
+ col = options.delete(:column) || options.delete(:col)
497
+ row = options.delete(:row)
498
+ width = options.delete(:columnspan) || options.delete(:colspan) || options.delete(:columns) || 1
499
+ height = options.delete(:rowspan) || options.delete(:rows) || 1
500
+ pos = options.delete(:position) || options.delete(:pos)
501
+ skip = options.delete(:skip)
502
+
503
+ if pos.is_a?(Array)
504
+ col = pos[0] || col
505
+ row = pos[1] || row
506
+ width = pos[2] || width
507
+ height = pos[3] || height
508
+ elsif pos.is_a?(Hash)
509
+ col = pos[:column] || pos[:col] || pos[:x] || col
510
+ row = pos[:row] || pos[:y] || row
511
+ width = pos[:width] || pos[:w] || pos[:columnspan] || pos[:colspan] || width
512
+ height = pos[:height] || pos[:h] || pos[:rowspan] || height
513
+ end
514
+
515
+ if @stkw_config[:placement] == :free
516
+ raise ArgumentError, ':column must be set for free placement mode' unless col
517
+ raise ArgumentError, ':row must be set for free placement mode' unless row
518
+ raise ArgumentError, ':skip must not be set for free placement mode' if skip
519
+ elsif @stkw_config[:placement] == :flow
520
+ raise ArgumentError, ':column cannot be set for flow placement mode' if col
521
+ raise ArgumentError, ':row cannot be set for flow placement mode' if row
522
+ raise ArgumentError, ":columnspan cannot be more than #{@stkw_config[:col_count]} in flow placement mode" if width > @stkw_config[:col_count]
523
+ raise ArgumentError, ':rowspan cannot be more than 1 in flow placement mode' if height > 1
524
+
525
+ # skip cells if the user requested it.
526
+ if skip
527
+ @stkw_config[:cur_col] += skip
528
+ while @stkw_config[:cur_col] >= @stkw_config[:col_start] + @stkw_config[:col_count]
529
+ @stkw_config[:cur_row] += 1
530
+ @stkw_config[:cur_col] -= @stkw_config[:col_count]
531
+ end
532
+ end
533
+
534
+ if width > @stkw_config[:col_count] - @stkw_config[:cur_col] + 1
535
+ @stkw_config[:cur_row] += 1
536
+ @stkw_config[:cur_col] = @stkw_config[:col_start]
537
+ end
538
+ col = @stkw_config[:cur_col]
539
+ row = @stkw_config[:cur_row]
540
+ @stkw_config[:cur_col] += width
541
+ if @stkw_config[:cur_col] >= @stkw_config[:col_start] + @stkw_config[:col_count]
542
+ @stkw_config[:cur_col] = @stkw_config[:col_start]
543
+ @stkw_config[:cur_row] += 1
544
+ end
545
+ end
546
+
547
+ options[:column] = col
548
+ options[:row] = row
549
+ options[:columnspan] = width
550
+ options[:rowspan] = height
551
+
552
+ end_col = options[:column] + options[:columnspan] - 1
553
+ end_row = options[:row] + options[:rowspan] - 1
554
+
555
+ # track the columns and rows.
556
+ @stkw_config[:max_col] = end_col if end_col > @stkw_config[:max_col]
557
+ @stkw_config[:max_row] = end_row if end_row > @stkw_config[:max_row]
558
+
559
+ options
560
+ end
561
+
562
+ def add_widget(klass, name, label_text, options = {}, &block)
563
+ raise ArgumentError, 'name cannot be blank' if name.to_s.strip == ''
564
+ name = name.to_sym
565
+ raise DuplicateNameError if @stkw_object_list.include?(name)
566
+ options = options.dup
567
+ cmd = get_command options.delete(:command), &block
568
+ if cmd
569
+ options[:command] = cmd
570
+ end
571
+
572
+ options = fix_position(options)
573
+
574
+ if (attrib = options.delete(:create_var))
575
+ @stkw_var_list[name] ||= TkVariable.new
576
+ if attrib.is_a?(Symbol)
577
+ options[attrib] = @stkw_var_list[name]
578
+ end
579
+ init_val = options.delete(:value)
580
+ var[name] = init_val if init_val
581
+ end
582
+
583
+ @stkw_object_list[name] = klass.new(content)
584
+ if label_text
585
+ set_text @stkw_object_list[name], label_text
586
+ end
587
+ apply_options @stkw_object_list[name], options
588
+
589
+ # add disable, disabled?, and enable methods.
590
+ @stkw_object_list[name].extend SimpleTk::DisableHelpers
591
+
592
+ # return the object after creation
593
+ @stkw_object_list[name]
594
+ end
595
+
596
+ def get_command(proc, &block)
597
+ if block
598
+ block
599
+ elsif proc.is_a?(Proc)
600
+ proc
601
+ elsif proc.is_a?(Symbol) && self.respond_to?(proc, true)
602
+ self.method(proc)
603
+ elsif linked_object && proc.is_a?(Symbol) && linked_object.respond_to?(proc, true)
604
+ linked_object.method(proc)
605
+ else
606
+ nil
607
+ end
608
+ end
609
+
610
+ def set_text(object, label_text)
611
+ if label_text.is_a?(Symbol)
612
+ @stkw_var_list[label_text] ||= TkVariable.new
613
+ object.textvariable @stkw_var_list[label_text]
614
+ else
615
+ object.text label_text
616
+ end
617
+ end
618
+
619
+ def apply_options(object, options)
620
+ widget_opt, grid_opt = split_widget_grid_options(options)
621
+ widget_opt.each do |key,val|
622
+ if val.is_a?(Proc)
623
+ object.send key, &val
624
+ else
625
+ object.send key, val
626
+ end
627
+ end
628
+ object.grid(grid_opt) unless grid_opt.empty?
629
+ end
630
+
631
+ def split_widget_grid_options(options)
632
+ wopt = options.dup
633
+ gopt = {}
634
+
635
+ [
636
+ :row, :column, :columnspan, :rowspan,
637
+ :padx, :pady, :ipadx, :ipady,
638
+ :sticky,
639
+ ].each do |attr|
640
+ val = wopt.delete(attr)
641
+ if val
642
+ gopt[attr] = val
643
+ end
644
+ end
645
+
646
+ [ wopt, gopt ]
647
+ end
648
+
649
+ def split_window_content_options(options)
650
+ options = options.dup
651
+ wopt = options.delete(:window) || { }
652
+ copt = { }
653
+
654
+ options.each do |k,v|
655
+ ks = k.to_s
656
+ if ks =~ /^window_/
657
+ wopt[ks[7..-1].to_sym] = v
658
+ else
659
+ copt[k] = v
660
+ end
661
+ end
662
+
663
+ [ wopt, copt ]
664
+ end
665
+
666
+ end
667
+ end
data/lib/simple_tk.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'simple_tk/version'
2
+ require 'simple_tk/get_set_helper'
3
+ require 'simple_tk/window'
4
+
5
+ ##
6
+ # A real cheap wrapper around the Ruby Tk bindings.
7
+ module SimpleTk
8
+
9
+ ##
10
+ # Runs the Tk application.
11
+ def self.run
12
+ Tk.mainloop
13
+ end
14
+
15
+ ##
16
+ # Creates a basic alert message box.
17
+ #
18
+ # message::
19
+ # The message to display.
20
+ # icon::
21
+ # The icon to display (one of :info, :error, :question, or :warning).
22
+ #
23
+ # Returns true.
24
+ def self.alert(message, icon = :info)
25
+ Tk::messageBox message: message, icon: icon.to_s
26
+ true
27
+ end
28
+
29
+ ##
30
+ # Creates a basic yes/no message box.
31
+ #
32
+ # message::
33
+ # The message to display.
34
+ # icon::
35
+ # The icon to display (one of :info, :error, :question, or :warning).
36
+ # ok_cancel::
37
+ # Set to true to make the buttons OK and Cancel instead of Yes and No.
38
+ #
39
+ # Returns true for 'Yes' (or 'OK') or false for 'No' (or 'Cancel').
40
+ def self.ask(message, icon = :question, ok_cancel = false)
41
+ %w(ok yes).include?(Tk::messageBox(message: message, type: ok_cancel ? 'okcancel' : 'yesno', icon: icon))
42
+ end
43
+
44
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_tk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Beau Barker
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description:
70
+ email:
71
+ - beau@barkerest.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - lib/simple_tk.rb
85
+ - lib/simple_tk/errors.rb
86
+ - lib/simple_tk/get_set_helper.rb
87
+ - lib/simple_tk/version.rb
88
+ - lib/simple_tk/widget_helpers.rb
89
+ - lib/simple_tk/window.rb
90
+ homepage: http://www.barkerest.com/
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.6.11
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: A simple interface to the Ruby Tk bindings.
114
+ test_files: []