simple_tk 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.
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: []