glimmer-dsl-tk 0.0.35 → 0.0.39

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.
@@ -0,0 +1,202 @@
1
+ # Copyright (c) 2020-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/tk/widget_proxy'
23
+
24
+ module Glimmer
25
+ module Tk
26
+ class MenuItemProxy < WidgetProxy
27
+ ACCELERATOR_MODIFIER_EVENT_MAP = {
28
+ 'Command' => 'Command',
29
+ 'Cmd' => 'Command',
30
+ 'Meta' => 'Command',
31
+ 'Option' => 'Option',
32
+ 'Opt' => 'Option',
33
+ 'Alt' => 'Option',
34
+ 'Shift' => 'Shift',
35
+ 'Ctrl' => 'Control',
36
+ 'Control' => 'Control',
37
+ }
38
+
39
+ attr_reader :options
40
+
41
+ def initialize(underscored_widget_name, parent_proxy, args, &block)
42
+ @options = args.last.is_a?(Hash) ? args.last : {}
43
+ super
44
+ end
45
+
46
+ def accelerator=(value)
47
+ @accelerator = value
48
+ configure_menu_item_attribute(accelerator: value)
49
+ root_parent_proxy.bind(accelerator_event) do |event|
50
+ @command_block&.call(event)
51
+ end
52
+ end
53
+
54
+ def accelerator
55
+ @accelerator
56
+ end
57
+
58
+ def accelerator_event
59
+ accelerator_parts = @accelerator.split('+')
60
+ accelerator_parts.map do |accelerator_part|
61
+ ACCELERATOR_MODIFIER_EVENT_MAP[accelerator_part.capitalize] || accelerator_part.downcase
62
+ end.join('-')
63
+ end
64
+
65
+ def state=(value)
66
+ @state = value
67
+ configure_menu_item_attribute(state: value)
68
+ end
69
+
70
+ def state
71
+ @state
72
+ end
73
+
74
+ def label
75
+ @options[:label]
76
+ end
77
+
78
+ def image=(*args)
79
+ @image = image_argument(args)
80
+ configure_menu_item_attribute(image: @image)
81
+ end
82
+
83
+ def image
84
+ @image
85
+ end
86
+
87
+ def compound=(value)
88
+ @compound = value
89
+ configure_menu_item_attribute(compound: @compound)
90
+ end
91
+
92
+ def compound
93
+ @compound
94
+ end
95
+
96
+ def command_block=(proc)
97
+ @command_block = proc
98
+ configure_menu_item_attribute(command: @command_block)
99
+ end
100
+
101
+ def handle_listener(listener_name, &listener)
102
+ case listener_name.to_s.downcase
103
+ when 'command'
104
+ self.command_block = listener
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def command?
111
+ @args.first.nil? || @args.first == :command || @args.first.is_a?(Hash)
112
+ end
113
+
114
+ def radiobutton?
115
+ @args.first == :radiobutton
116
+ end
117
+
118
+ def checkbutton?
119
+ @args.first == :radiobutton
120
+ end
121
+
122
+ def separator?
123
+ @args.first == :separator
124
+ end
125
+
126
+ def about?
127
+ @args.first == :about
128
+ end
129
+
130
+ def preferences?
131
+ @args.first == :preferences
132
+ end
133
+
134
+ def help?
135
+ @args.first == :help
136
+ end
137
+
138
+ def quit?
139
+ @args.first == :quit
140
+ end
141
+
142
+ def variable(auto_create: true)
143
+ if @variable.nil? && auto_create
144
+ sibling_variable = sibling_radio_menu_items.map {|mi| mi.variable(auto_create: false)}.compact.first
145
+ @variable = sibling_variable.nil? ? ::TkVariable.new : sibling_variable
146
+ else
147
+ @variable
148
+ end
149
+ @variable
150
+ end
151
+
152
+ def selection=(value)
153
+ if value
154
+ variable.value = label
155
+ elsif checkbutton?
156
+ variable.value = ''
157
+ end
158
+ end
159
+
160
+ def selection
161
+ variable.value == label
162
+ end
163
+
164
+ # configures menu item attribute through parent menu
165
+ def configure_menu_item_attribute(attribute_value_hash)
166
+ if preferences? && attribute_value_hash[:command]
167
+ ::Tk.ip_eval("proc ::tk::mac::ShowPreferences {} {#{::Tk.install_cmd(attribute_value_hash[:command])}}")
168
+ elsif help? && attribute_value_hash[:command]
169
+ ::Tk.ip_eval("proc ::tk::mac::ShowHelp {} {#{::Tk.install_cmd(attribute_value_hash[:command])}}")
170
+ elsif quit? && attribute_value_hash[:command]
171
+ ::Tk.ip_eval("proc ::tk::mac::Quit {} {#{::Tk.install_cmd(attribute_value_hash[:command])}}")
172
+ else
173
+ @parent_proxy.tk.entryconfigure label, attribute_value_hash
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def sibling_radio_menu_items
180
+ @parent_proxy.children.select {|child| child.is_a?(MenuItemProxy) && radiobutton? && child != self}
181
+ end
182
+
183
+ def build_widget
184
+ @args.prepend(:command) if @args.first.is_a?(Hash)
185
+ @args.append({}) if !@args.last.is_a?(Hash)
186
+ @args.last.merge!(variable: variable, value: label) if radiobutton? || checkbutton?
187
+ case @parent_proxy
188
+ when MenuProxy
189
+ if @parent_proxy.application?
190
+ if OS.mac?
191
+ if about?
192
+ @parent_proxy.tk.add :command, :label => label
193
+ end
194
+ end
195
+ else
196
+ @parent_proxy.tk.add(*@args) unless help?
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,88 @@
1
+ # Copyright (c) 2020-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/tk/widget_proxy'
23
+
24
+ module Glimmer
25
+ module Tk
26
+ class MenuProxy < WidgetProxy
27
+ attr_reader :options
28
+
29
+ def initialize(underscored_widget_name, parent_proxy, args, &block)
30
+ @options = args.last.is_a?(Hash) ? args.last : {}
31
+ super
32
+ end
33
+
34
+ def post_add_content
35
+ case @parent_proxy
36
+ when ToplevelProxy
37
+ @parent_proxy.tk['menu'] = @tk
38
+ end
39
+ end
40
+
41
+ def label
42
+ @options[:label]
43
+ end
44
+
45
+ def help?
46
+ label == 'Help'
47
+ end
48
+
49
+ def window?
50
+ label == 'Window'
51
+ end
52
+
53
+ def system?
54
+ label == 'System'
55
+ end
56
+
57
+ def application?
58
+ @args.first == :application
59
+ end
60
+
61
+ private
62
+
63
+ def build_widget
64
+ if application?
65
+ if OS.mac?
66
+ @tk = ::TkSysMenu_Apple.new(@parent_proxy.tk)
67
+ @parent_proxy.tk.add :cascade, :menu => @tk
68
+ end
69
+ else
70
+ if @parent_proxy.parent_proxy.is_a?(ToplevelProxy) && (OS.mac? || OS.linux?) && help?
71
+ @tk = ::TkSysMenu_Help.new(@parent_proxy.tk)
72
+ elsif @parent_proxy.parent_proxy.is_a?(ToplevelProxy) && OS.mac? && window?
73
+ @tk = ::Tk::TkSysMenu_Window.new(@parent_proxy.tk)
74
+ elsif @parent_proxy.parent_proxy.is_a?(ToplevelProxy) && OS.windows? && system?
75
+ @tk = ::TkSysMenu_System.new(@parent_proxy.tk)
76
+ else
77
+ tk_widget_class = self.class.tk_widget_class_for(@keyword)
78
+ @tk = tk_widget_class.new(@parent_proxy.tk)
79
+ end
80
+ case @parent_proxy
81
+ when MenuProxy
82
+ @parent_proxy.tk.add(:cascade, {menu: @tk}.merge(@options))
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -36,6 +36,7 @@ module Glimmer
36
36
 
37
37
  def post_add_content
38
38
  set_attribute('iconphoto', File.expand_path('../../../icons/glimmer.png', __dir__)) if @tk.iconphoto.nil?
39
+ super
39
40
  end
40
41
 
41
42
  def open
@@ -46,22 +47,6 @@ module Glimmer
46
47
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Tk::RootExpression.new, keyword, *args, &block)
47
48
  end
48
49
 
49
- def set_attribute(attribute, *args)
50
- case attribute.to_s
51
- when 'iconphoto'
52
- args[0..-1] = [image_argument(args)]
53
- super
54
- when 'resizable'
55
- if args.size == 1 && !args.first.is_a?(Array)
56
- self.resizable = [args.first]*2
57
- else
58
- super
59
- end
60
- else
61
- super
62
- end
63
- end
64
-
65
50
  def handle_listener(listener_name, &listener)
66
51
  case listener_name.to_s.upcase
67
52
  when 'WM_OPEN_WINDOW', 'OPEN_WINDOW'
@@ -92,31 +92,31 @@ module Glimmer
92
92
  end
93
93
  end
94
94
 
95
- def add_selection_format(option, value, no_selection_default: :insert_word)
96
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| add_format(range_start, range_end, option, value) }
95
+ def add_selection_format(option, value, no_selection_default: :insert_word, focus: true)
96
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| add_format(range_start, range_end, option, value) }
97
97
  end
98
98
 
99
- def remove_selection_format(option, value, no_selection_default: :insert_word)
100
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| remove_format(range_start, range_end, option, value) }
99
+ def remove_selection_format(option, value, no_selection_default: :insert_word, focus: true)
100
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| remove_format(range_start, range_end, option, value) }
101
101
  end
102
102
 
103
- def toggle_selection_format(option, value, no_selection_default: :insert_word)
104
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
103
+ def toggle_selection_format(option, value, no_selection_default: :insert_word, focus: true)
104
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| toggle_format(range_start, range_end, option, value) }
105
105
  end
106
106
 
107
- def add_selection_font_format(option, value, no_selection_default: :insert_word)
108
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
107
+ def add_selection_font_format(option, value, no_selection_default: :insert_word, focus: true)
108
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| add_font_format(range_start, range_end, option, value) }
109
109
  end
110
110
 
111
- def remove_selection_font_format(option, value, no_selection_default: :insert_word)
112
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
111
+ def remove_selection_font_format(option, value, no_selection_default: :insert_word, focus: true)
112
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| remove_font_format(range_start, range_end, option, value) }
113
113
  end
114
114
 
115
- def toggle_selection_font_format(option, value, no_selection_default: :insert_word)
116
- process_selection_ranges(no_selection_default: no_selection_default) { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
115
+ def toggle_selection_font_format(option, value, no_selection_default: :insert_word, focus: true)
116
+ process_selection_ranges(no_selection_default: no_selection_default, focus: focus) { |range_start, range_end| toggle_font_format(range_start, range_end, option, value) }
117
117
  end
118
118
 
119
- def process_selection_ranges(no_selection_default: :insert_word, &processor)
119
+ def process_selection_ranges(no_selection_default: :insert_word, focus: true, &processor)
120
120
  regions = @tk.tag_ranges('sel')
121
121
  if regions.empty?
122
122
  case no_selection_default
@@ -131,6 +131,11 @@ module Glimmer
131
131
  range_end = region.last
132
132
  processor.call(range_start, range_end)
133
133
  end
134
+ if focus == true
135
+ @tk.focus
136
+ elsif focus.is_a?(Integer)
137
+ ::Tk.after(focus) { @tk.focus }
138
+ end
134
139
  end
135
140
 
136
141
  def applied_format?(region_start, region_end, option, value)
@@ -432,16 +437,11 @@ module Glimmer
432
437
  self.wrap = 'none'
433
438
  self.padx = 5
434
439
  self.pady = 5
435
- # on('Modified') { apply_all_tag }
436
440
  end
437
441
 
438
442
  def clone_font(font)
439
443
  ::TkFont.new(Hash[font.actual])
440
444
  end
441
-
442
- # def apply_all_tag
443
- # @tk.tag_add(ALL_TAG, '1.0', 'end')
444
- # end
445
445
  end
446
446
  end
447
447
  end
@@ -32,11 +32,40 @@ module Glimmer
32
32
  DEFAULT_HEIGHT = 95
33
33
 
34
34
  attr_reader :tk
35
+ attr_accessor :escapable
36
+ alias escapable? escapable
37
+
38
+ def post_add_content
39
+ if escapable?
40
+ on('KeyPress') do |event|
41
+ if event.keysym == 'Escape'
42
+ grab_release
43
+ destroy
44
+ end
45
+ end
46
+ end
47
+ end
35
48
 
36
49
  def has_attribute?(attribute, *args)
37
50
  %w[width height x y].include?(attribute.to_s) || super
38
51
  end
39
52
 
53
+ def set_attribute(attribute, *args)
54
+ case attribute.to_s
55
+ when 'iconphoto'
56
+ args[0..-1] = [image_argument(args)]
57
+ super
58
+ when 'resizable'
59
+ if args.size == 1 && !args.first.is_a?(Array)
60
+ self.resizable = [args.first]*2
61
+ else
62
+ super
63
+ end
64
+ else
65
+ super
66
+ end
67
+ end
68
+
40
69
  def width
41
70
  geometry.split(REGEX_GEOMETRY)[0].to_i
42
71
  end
@@ -114,6 +143,17 @@ module Glimmer
114
143
  destroy
115
144
  end
116
145
  end
146
+
147
+ def mac_style=(mac_class, mac_attribute_list = nil)
148
+ if OS.mac?
149
+ @mac_style = [mac_class, mac_attribute_list]
150
+ ::Tk.tk_call("::tk::unsupported::MacWindowStyle", "style", @tk, *@mac_style.compact)
151
+ end
152
+ end
153
+
154
+ def mac_style
155
+ @mac_style
156
+ end
117
157
  end
118
158
  end
119
159
  end
@@ -77,8 +77,7 @@ module Glimmer
77
77
  @args = args
78
78
  @keyword = underscored_widget_name
79
79
  @block = block
80
- tk_widget_class = self.class.tk_widget_class_for(underscored_widget_name)
81
- @tk = tk_widget_class.new(@parent_proxy.tk, *args)
80
+ build_widget
82
81
  # a common widget initializer
83
82
  @parent_proxy.post_initialize_child(self)
84
83
  initialize_defaults
@@ -121,7 +120,7 @@ module Glimmer
121
120
  end
122
121
 
123
122
  def self.widget_exists?(underscored_widget_name)
124
- !!tk_widget_class_for(underscored_widget_name)
123
+ !!tk_widget_class_for(underscored_widget_name) || (Glimmer::Tk.constants.include?("#{underscored_widget_name.camelcase(:upper)}Proxy".to_sym) && Glimmer::Tk.const_get("#{underscored_widget_name.camelcase(:upper)}Proxy".to_sym).respond_to?(:new))
125
124
  end
126
125
 
127
126
  def tk_widget_has_attribute_setter?(attribute)
@@ -213,8 +212,8 @@ module Glimmer
213
212
  raise "#{self} cannot handle attribute #{attribute} with args #{args.inspect}"
214
213
  end
215
214
  rescue => e
216
- Glimmer::Config.logger.debug {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
217
- Glimmer::Config.logger.debug {e.full_message}
215
+ Glimmer::Config.logger.error {"Failed to set attribute #{attribute} with args #{args.inspect}. Attempting to set through style instead..."}
216
+ Glimmer::Config.logger.error {e.full_message}
218
217
  apply_style(attribute => args.first)
219
218
  end
220
219
  end
@@ -460,14 +459,20 @@ module Glimmer
460
459
  parent_proxy.handle_listener(listener_name, &listener) if parent_proxy
461
460
  # TODO return a listener registration object that has a deregister method
462
461
  else
462
+ @listeners ||= {}
463
463
  begin
464
- @tk.bind(listener_name, &listener)
464
+ @listeners[listener_name] ||= []
465
+ @tk.bind(listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
466
+ @listeners[listener_name] << listener
465
467
  rescue => e
468
+ @listeners.delete(listener_name)
466
469
  Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
467
470
  Glimmer::Config.logger.debug {e.full_message}
468
471
  listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
469
472
  listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
470
- @tk.bind(listener_name, &listener)
473
+ @listeners[listener_name] ||= []
474
+ @tk.bind(listener_name) { |event| @listeners[listener_name].each {|l| l.call(event)} } if @listeners[listener_name].empty?
475
+ @listeners[listener_name] << listener
471
476
  end
472
477
  end
473
478
  end
@@ -501,11 +506,16 @@ module Glimmer
501
506
 
502
507
  private
503
508
 
509
+ def build_widget
510
+ tk_widget_class = self.class.tk_widget_class_for(@keyword)
511
+ @tk = tk_widget_class.new(@parent_proxy.tk, *args)
512
+ end
513
+
504
514
  def initialize_defaults
505
515
  options = {}
506
516
  options[:sticky] = 'nsew'
507
517
  options[:column_weight] = 1 if @parent_proxy.children.count == 1
508
- grid(options) unless @tk.is_a?(::Tk::Toplevel)
518
+ grid(options) unless @tk.is_a?(::Tk::Toplevel) || @tk.is_a?(::Tk::Menu) || @tk.nil? # TODO refactor by adding a griddable? method that could be overriden by subclasses to consult for this call
509
519
  end
510
520
  end
511
521
  end
@@ -29,14 +29,26 @@ require 'puts_debuggerer' if ENV['pd'].to_s.downcase == 'true'
29
29
  require 'tk'
30
30
  require 'os'
31
31
  require 'facets/hash/symbolize_keys'
32
+ require 'facets/string/underscore'
33
+ require 'facets/string/camelcase'
34
+ require 'delegate'
32
35
 
33
36
  # Internal requires
34
37
  # require 'ext/glimmer/config'
35
38
  # require 'ext/glimmer'
36
39
  require 'glimmer/dsl/tk/dsl'
40
+
37
41
  Glimmer::Config.loop_max_count = -1
42
+
38
43
  Glimmer::Config.excluded_keyword_checkers << lambda do |method_symbol, *args|
39
44
  method = method_symbol.to_s
40
45
  result = false
41
46
  result ||= method == 'load_iseq'
42
47
  end
48
+
49
+ ::TkOption.add '*tearOff', 0
50
+
51
+ class ::Tk::TkSysMenu_Window < Tk::Menu
52
+ include Tk::SystemMenu
53
+ SYSMENU_NAME = 'window'
54
+ end
@@ -81,7 +81,7 @@ class HelloButton
81
81
  ['center', 'top', 'bottom', 'left', 'right'].each do |compound_option|
82
82
  button {
83
83
  image File.expand_path('../../icons/glimmer.png', __dir__), subsample: 5
84
- text 'Text Image Button'
84
+ text "#{compound_option.capitalize} Image"
85
85
  compound compound_option
86
86
 
87
87
  command {
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2020-2021 Andy Maleh
1
+ `# Copyright (c) 2020-2021 Andy Maleh
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining
4
4
  # a copy of this software and associated documentation files (the