tk_component 0.1.0 → 0.1.1

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.
@@ -35,10 +35,10 @@ module TkComponent
35
35
  end
36
36
 
37
37
  def set_weights(row, col, weights = {})
38
- hw = weights[:h_weight]
39
- @row_weights[row] = ((rw = @row_weights[row]).present? ? [rw, hw].max : hw) if hw
40
- vw = weights[:v_weight]
41
- @column_weights[col] = ((cw = @column_weights[col]).present? ? [cw, vw].max : vw) if vw
38
+ vw = weights[:y_flex]
39
+ @row_weights[row] = ((rw = @row_weights[row]).present? ? [rw, vw].max : vw) if vw
40
+ hw = weights[:x_flex]
41
+ @column_weights[col] = ((cw = @column_weights[col]).present? ? [cw, hw].max : hw) if hw
42
42
  end
43
43
 
44
44
  def row_indexes
@@ -1,16 +1,19 @@
1
1
  module TkComponent
2
2
  module Builder
3
3
 
4
- TK_CMDS = %w(label entry button canvas text scale group).to_set.freeze
4
+ TK_CMDS = %w(label entry button radio_set radio_button canvas text scale group tree tree_node hscroll_bar vscroll_bar hpaned vpaned).to_set.freeze
5
5
  LAYOUT_CMDS = %w(frame hframe vframe row cell).to_set.freeze
6
- EVENT_CMDS = %w(on_change on_mouse_down on_mouse_up on_mouse_drag on_mouse_wheel on_click on_event).to_set.freeze
6
+ EVENT_CMDS = %w(on_change on_mouse_down on_mouse_up on_mouse_drag on_mouse_wheel on_click on_select on_item_open on_event).to_set.freeze
7
7
  TOKENS = (TK_CMDS + LAYOUT_CMDS + EVENT_CMDS).freeze
8
8
 
9
+ LAYOUT_OPTIONS = %i(column row rowspan columnspan sticky x_flex y_flex)
10
+
9
11
  class Node
10
12
  attr_accessor :name
11
13
  attr_accessor :options
12
14
  attr_accessor :sub_nodes
13
15
  attr_accessor :grid
16
+ attr_accessor :weights
14
17
  attr_accessor :grid_map
15
18
  attr_accessor :event_handlers
16
19
  attr_accessor :tk_item
@@ -29,14 +32,14 @@ module TkComponent
29
32
  @name = name
30
33
  @options = options.with_indifferent_access
31
34
  @sub_nodes = []
32
- @grid = {}
35
+ @grid = {}.with_indifferent_access
36
+ @weights = {}.with_indifferent_access
33
37
  @grid_map = GridMap.new
34
38
  @event_handlers = []
35
39
  @tk_item = nil
36
40
  end
37
41
 
38
42
  def short(level = 0)
39
- puts(" " * level + " #{@name}")
40
43
  @sub_nodes.each do |n|
41
44
  n.short(level + 4)
42
45
  end
@@ -44,10 +47,12 @@ module TkComponent
44
47
  end
45
48
 
46
49
  def insert_component(component_class, parent_component, options = {}, &block)
47
- c_node = node_from_command(:frame, &block)
50
+ layout_options = options.slice(*LAYOUT_OPTIONS)
51
+ c_node = node_from_command(:frame, layout_options, &block)
48
52
  comp = component_class.new(options.merge(parent: parent_component, parent_node: c_node))
49
53
  comp.generate(parent_component, options)
50
54
  parent_component.add_child(comp)
55
+ comp
51
56
  end
52
57
 
53
58
  def add_event_handler(name, lambda, options = {})
@@ -61,9 +66,25 @@ module TkComponent
61
66
  sub_nodes.each do |n|
62
67
  n.build(self, parent_component)
63
68
  end
69
+ apply_grid
70
+ built
71
+ end
72
+
73
+ def apply_grid
64
74
  self.tk_item.apply_internal_grid(grid_map)
65
75
  end
66
76
 
77
+ def built
78
+ self.tk_item.built
79
+ end
80
+
81
+ def rebuilt
82
+ end
83
+
84
+ def remove
85
+ self.tk_item.remove
86
+ end
87
+
67
88
  def prepare_option_events(component)
68
89
  option_events = options.extract!(*EVENT_CMDS)
69
90
  option_events.each do |k, v|
@@ -74,6 +95,7 @@ module TkComponent
74
95
  end
75
96
 
76
97
  def prepare_grid
98
+ self.grid_map = GridMap.new
77
99
  return unless self.sub_nodes.any?
78
100
  current_row = -1
79
101
  current_col = -1
@@ -90,13 +112,15 @@ module TkComponent
90
112
  current_col = 0 if current_col < 0
91
113
  current_row, current_col = grid_map.get_next_cell(current_row, current_col, going_down)
92
114
  binding.pry if n.options.nil?
93
- grid = n.options.extract!(:column, :row, :rowspan, :columnspan, :sticky)
94
- n.grid = grid.merge(column: current_col, row: current_row)
95
- rowspan = grid[:rowspan] || 1
96
- columnspan = grid[:columnspan] || 1
115
+ n.grid = {}.with_indifferent_access if n.grid.nil?
116
+ n.grid.merge!(n.options.extract!(:column, :row, :rowspan, :columnspan, :sticky))
117
+ n.grid.merge!(column: current_col, row: current_row)
118
+ rowspan = n.grid[:rowspan] || 1
119
+ columnspan = n.grid[:columnspan] || 1
97
120
  grid_map.fill(current_row, current_col, rowspan, columnspan, true)
98
- weights = n.options.extract!(:h_weight, :v_weight)
99
- grid_map.set_weights(current_row, current_col, weights)
121
+ n.weights = {}.with_indifferent_access if n.weights.nil?
122
+ n.weights.merge!(n.options.extract!(:x_flex, :y_flex))
123
+ grid_map.set_weights(current_row, current_col, n.weights)
100
124
  n.prepare_grid
101
125
  final_sub_nodes << n
102
126
  end
@@ -11,34 +11,43 @@ module TkComponent
11
11
  end
12
12
 
13
13
  def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
14
- tk_class = TK_CLASSES[name.to_sym]
15
- raise "Don't know how to create #{name}" unless tk_class
16
- @native_item = tk_class.new(parent_item.native_item)
14
+ @native_item = create_native_item(parent_item.native_item, name, options, grid, event_handlers)
17
15
  apply_options(options)
18
16
  set_grid(grid)
19
17
  set_event_handlers(event_handlers)
20
18
  end
21
19
 
22
- def apply_options(options)
20
+ def create_native_item(parent_native_item, name, options = {}, grid = {}, event_handlers = [])
21
+ native_item_class(parent_native_item, name, options, grid, event_handlers).new(parent_native_item)
22
+ end
23
+
24
+ def native_item_class(parent_native_item, name, options = {}, grid = {}, event_handlers = [])
25
+ tk_class = TK_CLASSES[name.to_sym]
26
+ raise "Don't know how to create #{name}" unless tk_class.present?
27
+ return tk_class
28
+ end
29
+
30
+ def remove
31
+ @native_item.destroy
32
+ end
33
+
34
+ def apply_options(options, to_item = self.native_item)
23
35
  options.each do |k,v|
24
- apply_option(k, v)
36
+ apply_option(k, v, to_item)
25
37
  end
26
38
  end
27
39
 
28
- def apply_option(option, value)
29
- self.native_item.public_send(option, value)
40
+ def apply_option(option, value, to_item = self.native_item)
41
+ to_item.public_send(option, value)
30
42
  end
31
43
 
32
- def set_grid(grid)
33
- self.native_item.grid(grid)
44
+ def set_grid(grid, to_item = self.native_item)
45
+ to_item.grid(grid)
34
46
  end
35
47
 
36
48
  def apply_internal_grid(grid_map)
37
- puts(grid_map)
38
49
  grid_map.column_indexes.each { |c| TkGrid.columnconfigure(self.native_item, c, weight: grid_map.column_weight(c)) }
39
50
  grid_map.row_indexes.each { |r| TkGrid.rowconfigure(self.native_item, r, weight: grid_map.row_weight(r)) }
40
- # grid_map.column_indexes.each { |c| TkGrid.columnconfigure(self.native_item, c, weight: 1) }
41
- # grid_map.row_indexes.each { |r| TkGrid.rowconfigure(self.native_item, r, weight: 1) }
42
51
  end
43
52
 
44
53
  def set_event_handlers(event_handlers)
@@ -55,10 +64,17 @@ module TkComponent
55
64
  Event.bind_event(event_handler.name, self, event_handler.options, event_handler.lambda)
56
65
  end
57
66
  end
67
+
68
+ def built
69
+ end
70
+
71
+ def focus
72
+ self.native_item.focus
73
+ end
58
74
  end
59
75
 
60
76
  module ValueTyping
61
- def apply_option(option, v)
77
+ def apply_option(option, v, to_item = self.native_item)
62
78
  case option.to_sym
63
79
  when :value
64
80
  self.value = v
@@ -90,15 +106,23 @@ module TkComponent
90
106
  attr_accessor :tk_variable
91
107
 
92
108
  def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
93
- @tk_variable = TkVariable.new
109
+ create_variable
94
110
  super
95
- self.native_item.public_send(variable_name, @tk_variable)
111
+ apply_variable
96
112
  end
97
113
 
98
114
  def variable_name
99
115
  :variable
100
116
  end
101
117
 
118
+ def apply_variable
119
+ self.native_item&.public_send(variable_name, @tk_variable)
120
+ end
121
+
122
+ def create_variable
123
+ @tk_variable = TkVariable.new
124
+ end
125
+
102
126
  delegate :value, to: :tk_variable
103
127
  delegate :"value=", to: :tk_variable
104
128
  end
@@ -127,8 +151,83 @@ module TkComponent
127
151
  end
128
152
  end
129
153
 
154
+ class TkRadioSet < TkItemWithVariable
155
+ # The variable for the radio set is only to be used by radio buttons inside it
156
+ # Thus, we don't try to link it to the actual item
157
+ def apply_variable
158
+ end
159
+ end
160
+
161
+ class TkRadioButton < TkItemWithVariable
162
+ # We need to use the tk_variable created by the parent_item
163
+ # So we set it here and skip creation below
164
+ def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
165
+ @tk_variable = parent_item.tk_variable
166
+ super
167
+ end
168
+
169
+ def create_variable
170
+ end
171
+
172
+ # It is unfortunate that native TK radio buttons use 'value' to
173
+ # spedify the value for each of them, colliding with the 'value'
174
+ # methods for our items with variables. Thus, we need to
175
+ # override the setting of the 'value' option to revert it to the
176
+ # default functionality
177
+ def apply_option(option, v, to_item = self.native_item)
178
+ case option.to_sym
179
+ when :value
180
+ to_item.public_send(option, v)
181
+ else
182
+ super
183
+ end
184
+ end
185
+ end
186
+
187
+ module Scrollable
188
+ ROOT_FRAME_OPTIONS = %i|width height relief borderwidth padx pady padding| + TkComponent::Builder::LAYOUT_OPTIONS
189
+
190
+ def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
191
+ return super unless (s_options = options.delete(:scrollers)) && s_options.present? && s_options != 'none'
192
+ frame_item = TK_CLASSES[:frame].new(parent_item.native_item) # Containing frame
193
+ real_native_item = create_native_item(frame_item, name, options, grid, event_handlers)
194
+ f_options = options.extract!(*ROOT_FRAME_OPTIONS)
195
+ apply_options(f_options, frame_item) # Apply the applicable options to the enclosing frame
196
+ @native_item = real_native_item
197
+ apply_options(options, real_native_item)
198
+ set_grid(grid, frame_item)
199
+ real_native_item.grid( :column => 0, :row => 0, :sticky => 'nwes')
200
+ if s_options.include?('x')
201
+ h_scrollbar = TK_CLASSES[:hscroll_bar].new(frame_item)
202
+ h_scrollbar.orient('horizontal')
203
+ h_scrollbar.command proc { |*args| real_native_item.xview(*args) }
204
+ real_native_item['xscrollcommand'] = proc { |*args| h_scrollbar.set(*args) }
205
+ h_scrollbar.grid( :column => 0, :row => 1, :sticky => 'wes')
206
+ end
207
+ if s_options.include?('y')
208
+ v_scrollbar = TK_CLASSES[:vscroll_bar].new(frame_item)
209
+ v_scrollbar.orient('vertical')
210
+ v_scrollbar.command proc { |*args| real_native_item.yview(*args) }
211
+ real_native_item['yscrollcommand'] = proc { |*args| v_scrollbar.set(*args) }
212
+ v_scrollbar.grid( :column => 1, :row => 0, :sticky => 'nse')
213
+ end
214
+ TkGrid.columnconfigure(frame_item, 0, :weight => 1)
215
+ TkGrid.columnconfigure(frame_item, 1, :weight => 0) if v_scrollbar.present?
216
+ TkGrid.rowconfigure(frame_item, 0, :weight => 1)
217
+ TkGrid.rowconfigure(frame_item, 1, :weight => 0) if h_scrollbar.present?
218
+ @native_item = real_native_item
219
+ set_event_handlers(event_handlers)
220
+ end
221
+
222
+ # We need to remove the parent native item, as it's the container we put in place initially
223
+ def remove
224
+ @native_item.winfo_parent.destroy
225
+ end
226
+ end
227
+
130
228
  class TkText < TkItem
131
229
  include ValueTyping
230
+ include Scrollable
132
231
 
133
232
  def value
134
233
  native_item.get('1.0', 'end')
@@ -138,6 +237,24 @@ module TkComponent
138
237
  native_item.replace('1.0', 'end', text)
139
238
  end
140
239
 
240
+ def selected_text
241
+ ranges = native_item.tag_ranges('sel')
242
+ return nil if ranges.empty?
243
+ native_item.get(ranges.first.first, ranges.first.last)
244
+ end
245
+
246
+ def current_line
247
+ native_item.get('insert linestart', 'insert lineend')
248
+ end
249
+
250
+ def append_text(text)
251
+ native_item.insert('end', text)
252
+ end
253
+
254
+ def select_range(from, to)
255
+ native_item.tag_add('sel', from, to)
256
+ end
257
+
141
258
  def set_event_handler(event_handler)
142
259
  case event_handler.name
143
260
  when :change
@@ -159,11 +276,214 @@ module TkComponent
159
276
  end
160
277
  end
161
278
 
279
+ class TkTree < TkItem
280
+ include Scrollable
281
+ @column_defs = []
282
+
283
+ def apply_options(options, to_item = self.native_item)
284
+ super
285
+ return unless @column_defs.present?
286
+ cols = @column_defs.map { |c| c[:key] }
287
+ to_item.columns(cols[1..-1].join(' ')) unless cols == ['#0']
288
+ @column_defs.each.with_index do |cd, idx|
289
+ key = idx == 0 ? '#0' : cd[:key]
290
+ column_conf = cd.slice(:width, :anchor)
291
+ to_item.column_configure(key, column_conf) unless column_conf.empty?
292
+ heading_conf = cd.slice(:text)
293
+ to_item.heading_configure(key, heading_conf) unless heading_conf.empty?
294
+ end
295
+ end
296
+
297
+ def apply_option(option, v, to_item = self.native_item)
298
+ case option.to_sym
299
+ when :column_defs
300
+ @column_defs = v
301
+ when :heading
302
+ @column_defs = [ { key: '#0', text: v } ]
303
+ else
304
+ super
305
+ end
306
+ end
307
+
308
+ def set_event_handler(event_handler)
309
+ case event_handler.name
310
+ when :select
311
+ Event.bind_event('<TreeviewSelect>', self, event_handler.options, event_handler.lambda)
312
+ when :item_open
313
+ Event.bind_event('<TreeviewOpen>', self, event_handler.options, event_handler.lambda)
314
+ else
315
+ super
316
+ end
317
+ end
318
+
319
+ def scroll_to_selection
320
+ scroll_to_item(@native_item.selection.first)
321
+ end
322
+
323
+ # Right now it only works well for non-nested trees
324
+ def scroll_to_item(tree_item)
325
+ return unless tree_item.present?
326
+ items = @native_item.children('')
327
+ rel_pos = items.index(tree_item).to_f / items.size.to_f
328
+ @native_item.after(200) { @native_item.yview_moveto(rel_pos) }
329
+ end
330
+ end
331
+
332
+ class TkTreeNode < TkItem
333
+ def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
334
+ item_options = options.dup
335
+ parent_node = item_options.delete(:parent) || ''
336
+ parent_native_item = (parent_node == '' ? '' : parent_node.native_item)
337
+ at = item_options.delete(:at)
338
+ selected = item_options.delete(:selected)
339
+ @native_item = parent_item.native_item.insert(parent_native_item, at, item_options)
340
+ parent_item.native_item.selection_add(@native_item) if selected
341
+ set_event_handlers(event_handlers)
342
+ end
343
+ end
344
+
345
+ class ScrollBar < TkItem
346
+ def apply_option(option, v, to_item = self.native_item)
347
+ case option.to_sym
348
+ when :linked_to
349
+ @linked_to = v
350
+ else
351
+ super
352
+ end
353
+ end
354
+
355
+ def set_event_handler(event_handler)
356
+ case event_handler.name
357
+ when :change
358
+ Event.Event.bind_command(event_handler.name, self, event_handler.options, event_handler.lambda)
359
+ else
360
+ super
361
+ end
362
+ end
363
+
364
+ def apply_options(options, to_item = self.native_item)
365
+ options.merge!(orient: orient)
366
+ super
367
+ end
368
+
369
+ def set_event_handlers(event_handlers)
370
+ bind_linked_to
371
+ super
372
+ end
373
+
374
+ def bind_linked_to
375
+ return unless @linked_to.present?
376
+ items = @linked_to.is_a?(Array) ? @linked_to.map(&:native_item) : [ @linked_to.native_item ]
377
+ self.native_item.command proc { |*args|
378
+ items.each do |item|
379
+ item.send(scroll_command, *args)
380
+ end
381
+ }
382
+ items.each do |item|
383
+ item.send(linked_scroll_command, proc { |*args| self.native_item.send(linked_scroll_event, *args) })
384
+ end
385
+ end
386
+
387
+ def orient
388
+ raise "#{self.class.to_s} shouldn't be instantiated directly. Use 'H' or 'V' subclasses"
389
+ end
390
+
391
+ def scroll_command
392
+ raise "#{self.class.to_s} shouldn't be instantiated directly. Use 'H' or 'V' subclasses"
393
+ end
394
+
395
+ def linked_scroll_command
396
+ raise "#{self.class.to_s} shouldn't be instantiated directly. Use 'H' or 'V' subclasses"
397
+ end
398
+
399
+ def linked_scroll_event
400
+ raise "#{self.class.to_s} shouldn't be instantiated directly. Use 'H' or 'V' subclasses"
401
+ end
402
+ end
403
+
404
+ class HScrollbar < ScrollBar
405
+ def orient
406
+ 'horizontal'
407
+ end
408
+
409
+ def scroll_command
410
+ :xview
411
+ end
412
+
413
+ def linked_scroll_command
414
+ :xscrollcommand
415
+ end
416
+
417
+ def linked_scroll_event
418
+ :set
419
+ end
420
+ end
421
+
422
+ class VScrollbar < ScrollBar
423
+ def orient
424
+ 'vertical'
425
+ end
426
+
427
+ def scroll_command
428
+ :yview
429
+ end
430
+
431
+ def linked_scroll_command
432
+ :yscrollcommand
433
+ end
434
+
435
+ def linked_scroll_event
436
+ :set
437
+ end
438
+ end
439
+
440
+ class PanedWindow < TkItem
441
+ def create_native_item(parent_native_item, name, options = {}, grid = {}, event_handlers = [])
442
+ native_item_class(parent_native_item, name, options, grid, event_handlers).new(parent_native_item, orient: orient)
443
+ end
444
+
445
+ def built
446
+ # We need to synchronize children items to the panned window
447
+ added_panes = self.native_item.winfo_children - self.native_item.panes
448
+ removed_panes = self.native_item.panes - self.native_item.winfo_children
449
+ added_panes.each do |child|
450
+ self.native_item.add(child, weight: 1)
451
+ end
452
+ removed_panes.each do |child|
453
+ self.native_item.forget(child)
454
+ end
455
+ end
456
+
457
+ def orient
458
+ raise "#{self.class.to_s} shouldn't be instantiated directly. Use 'H' or 'V' subclasses"
459
+ end
460
+ end
461
+
462
+ class HPanedWindow < PanedWindow
463
+ def orient
464
+ 'horizontal'
465
+ end
466
+ end
467
+
468
+ class VPanedWindow < PanedWindow
469
+ def orient
470
+ 'vertical'
471
+ end
472
+ end
473
+
162
474
  class TkWindow < TkItem
163
475
  def initialize(parent_item, name, options = {}, grid = {}, event_handlers = [])
164
- @native_item = TkRoot.new { title options[:title] }
476
+ if (options.delete(:root))
477
+ @native_item = TkRoot.new { title options[:title] }
478
+ else
479
+ @native_item = TkToplevel.new { title options[:title] }
480
+ end
165
481
  apply_options(options)
166
482
  end
483
+
484
+ def focus
485
+ self.native_item.set_focus
486
+ end
167
487
  end
168
488
 
169
489
  TK_CLASSES = {
@@ -174,10 +494,17 @@ module TkComponent
174
494
  label: Tk::Tile::Label,
175
495
  entry: Tk::Tile::Entry,
176
496
  button: Tk::Tile::Button,
497
+ radio_set: Tk::Tile::Frame,
498
+ radio_button: Tk::Tile::RadioButton,
177
499
  canvas: Tk::Canvas,
178
500
  text: ::TkText,
179
501
  scale: Tk::Tile::Scale,
180
- group: Tk::Tile::LabelFrame
502
+ group: Tk::Tile::LabelFrame,
503
+ tree: Tk::Tile::Treeview,
504
+ hscroll_bar: Tk::Tile::Scrollbar,
505
+ vscroll_bar: Tk::Tile::Scrollbar,
506
+ hpaned: Tk::Tile::Paned,
507
+ vpaned: Tk::Tile::Paned
181
508
  }
182
509
 
183
510
  ITEM_CLASSES = {
@@ -188,10 +515,19 @@ module TkComponent
188
515
  label: TkComponent::Builder::TkItem,
189
516
  entry: TkComponent::Builder::TkEntry,
190
517
  button: TkComponent::Builder::TkItem,
518
+ radio_set: TkComponent::Builder::TkRadioSet,
519
+ radio_button: TkComponent::Builder::TkRadioButton,
191
520
  canvas: TkComponent::Builder::TkItem,
192
521
  text: TkComponent::Builder::TkText,
193
522
  scale: TkComponent::Builder::TkScale,
194
- group: TkComponent::Builder::TkItem
523
+ group: TkComponent::Builder::TkItem,
524
+ tree: TkComponent::Builder::TkTree,
525
+ tree_node: TkComponent::Builder::TkTreeNode,
526
+ hscroll_bar: TkComponent::Builder::HScrollbar,
527
+ vscroll_bar: TkComponent::Builder::VScrollbar,
528
+ hpaned: TkComponent::Builder::HPanedWindow,
529
+ vpaned: TkComponent::Builder::VPanedWindow
530
+
195
531
  }
196
532
  end
197
533
  end