glimmer-dsl-tk 0.0.35 → 0.0.39

Sign up to get free protection for your applications and to get access to all the features.
@@ -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