glimmer-dsl-tk 0.0.27 → 0.0.31

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.27
1
+ 0.0.31
Binary file
@@ -0,0 +1,39 @@
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/data_binding/observer'
23
+
24
+ module Glimmer
25
+ module DataBinding
26
+ module Tk
27
+ # Decorator for Observer that ensures it is only called once
28
+ # (subsequent invocations of call method do nothing)
29
+ module OneTimeObserver
30
+ def call(value)
31
+ unless @called
32
+ super
33
+ @called = true
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
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'
23
+ require 'glimmer/dsl/expression'
24
+
25
+ module Glimmer
26
+ module DSL
27
+ module Tk
28
+ class BuiltInDialogExpression < Expression
29
+ def can_interpret?(parent, keyword, *args, &block)
30
+ keyword = "get_#{keyword}" if keyword.start_with?('open')
31
+ (keyword.start_with?('get') or keyword.start_with?('choose')) and
32
+ (
33
+ (block.nil? and ::Tk.respond_to?(keyword.camelcase)) or
34
+ keyword == 'choose_font'
35
+ )
36
+ end
37
+
38
+ def interpret(parent, keyword, *args, &block)
39
+ if args.first.is_a?(Hash)
40
+ options = args.first.symbolize_keys
41
+ options[:filetypes] = options.delete(:file_types) if options.keys.include?(:file_types)
42
+ options[:filetypes] = options[:filetypes].map { |key, value| "{#{key}} {#{value}}" } if options[:filetypes].is_a?(Hash)
43
+ options[:parent] = options[:parent].tk if options[:parent].is_a?(Glimmer::Tk::RootProxy)
44
+ args[0] = options
45
+ end
46
+ keyword = "get_#{keyword}" if keyword.start_with?('open') || keyword.start_with?('save') || keyword.start_with?('multiple')
47
+ if keyword == 'choose_font'
48
+ TkFont::Fontchooser.configure(:font => args, :command => block)
49
+ TkFont::Fontchooser.show
50
+ else
51
+ ::Tk.send(keyword.camelcase, *args)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -38,12 +38,13 @@ module Glimmer
38
38
  end
39
39
 
40
40
  def interpret(parent, keyword, *args, &block)
41
- parent.class
42
41
  model_binding = args[0]
43
42
  widget_binding_parameters = [parent, keyword]
44
43
  widget_binding = DataBinding::Tk::WidgetBinding.new(*widget_binding_parameters)
45
44
  #TODO make this options observer dependent and all similar observers in widget specific data binding handlers
46
- widget_binding.observe(model_binding)
45
+ registration = widget_binding.observe(model_binding)
46
+ parent.on('destroy') { registration.deregister }
47
+
47
48
  # TODO simplify this logic and put it where it belongs
48
49
  parent.add_observer(model_binding, keyword) if parent.respond_to?(:add_observer, [model_binding, keyword])
49
50
  widget_binding.call(model_binding.evaluate_property)
@@ -42,6 +42,7 @@ module Glimmer
42
42
  attribute
43
43
  shine_data_binding
44
44
  widget
45
+ built_in_dialog
45
46
  ]
46
47
  )
47
48
  end
@@ -33,11 +33,15 @@ module Glimmer
33
33
 
34
34
  def can_interpret?(parent, keyword, *args, &block)
35
35
  !EXCLUDED_KEYWORDS.include?(keyword) and
36
- parent.respond_to?(:tk) and
37
36
  Glimmer::Tk::WidgetProxy.widget_exists?(keyword)
37
+ (parent.respond_to?(:tk) or args.first.respond_to?(:tk))
38
38
  end
39
39
 
40
40
  def interpret(parent, keyword, *args, &block)
41
+ if keyword == 'toplevel' && args.first.respond_to?(:tk)
42
+ parent = args.first
43
+ args[0] = args.first.tk
44
+ end
41
45
  Glimmer::Tk::WidgetProxy.create(keyword, parent, args, &block)
42
46
  end
43
47
 
@@ -0,0 +1,112 @@
1
+ require "json"
2
+
3
+ module Glimmer
4
+ module Tk
5
+ class WidgetProxy
6
+ attr_accessor :on_drag_motion_block
7
+
8
+ def drag_source=(value)
9
+ @drag_source = value
10
+ if @drag_source
11
+ make_draggable
12
+ else
13
+ make_non_draggable
14
+ end
15
+ end
16
+
17
+ def on_drag_start_block=(block)
18
+ @on_drag_start_block = block
19
+ make_draggable
20
+ end
21
+
22
+ def on_drop_block=(value)
23
+ @on_drop_block = value
24
+ self.tk.bind("<DropEvent>", proc { |tk_event|
25
+ drop_event = DragAndDropEvent.json_create(JSON.parse("{" + tk_event.detail + "}"))
26
+ @on_drop_block.call(drop_event) if self.tk == drop_event.target
27
+ })
28
+ self.tk.bind("<DropCheckEvent>", proc { |tk_event|
29
+ drop_check_event = DragAndDropEvent.json_create(JSON.parse("{" + tk_event.detail + "}"))
30
+ drop_check_event.source.event_generate("<DropAcceptedEvent>")
31
+ })
32
+ end
33
+
34
+ def textvariable_defined?
35
+ tk_widget_has_attribute_setter?(:textvariable) && !tk.textvariable.nil?
36
+ end
37
+
38
+ def make_draggable
39
+ drag_event = nil
40
+ bind("<DropAcceptedEvent>", proc { |event| drag_event.drop_accepted = true })
41
+ bind("B1-Motion", proc { |tk_event|
42
+ if drag_event.nil?
43
+ tooltip = TkToplevel.new(root).overrideredirect(1) #create tooltip window to display dragged data
44
+ tooltip.geometry("+#{tk_event.x_root + 10}+#{tk_event.y_root - 2}")
45
+ drag_event = DragAndDropEvent.new(self.tk, nil, tooltip, tk_event.x_root, tk_event.y_root, nil, false)
46
+ if @drag_source
47
+ tk_event.widget.configure(:cursor => "hand2")
48
+ # Default data to drag is text
49
+ drag_event.data = if textvariable_defined? then tk.textvariable.value elsif has_attribute?(:text) then tk.text end
50
+ TkLabel.new(tooltip) { text drag_event.data }.pack
51
+ elsif !@on_drag_start_block.nil?
52
+ @on_drag_start_block.call(drag_event)
53
+ TkLabel.new(tooltip) { text drag_event.data }.pack if tooltip.winfo_children().length == 0
54
+ end
55
+ else
56
+ drag_event.x_root, drag_event.y_root = tk_event.x_root, tk_event.y_root
57
+ drag_event.drop_accepted = false
58
+ move_over_widget = tk_event.widget.winfo_containing(tk_event.x_root, tk_event.y_root)
59
+ drag_event.target = move_over_widget
60
+ move_over_widget.event_generate("<DropCheckEvent>", :data => drag_event.to_json)
61
+ if @on_drag_motion_block.nil?
62
+ # Default motion behavior:
63
+ # 1.Change cursor to show whether text can be dropped.
64
+ # 2.Move tooltip with dragged data.
65
+ if drag_event.drop_accepted
66
+ tk_event.widget.configure(:cursor => "hand1")
67
+ else
68
+ tk_event.widget.configure(:cursor => "hand2")
69
+ end
70
+ drag_event.tooltip.geometry("+#{tk_event.x_root + 10}+#{tk_event.y_root - 2}")
71
+ else
72
+ @on_drag_motion_block.call(drag_event)
73
+ end
74
+ end
75
+ })
76
+ bind("ButtonRelease-1", proc { |tk_event|
77
+ if drag_event
78
+ drag_event.target = tk_event.widget.winfo_containing(tk_event.x_root, tk_event.y_root)
79
+ drag_event.source.configure(:cursor => "")
80
+ drag_event.target.event_generate("<DropEvent>", :data => drag_event.to_json)
81
+ drag_event.tooltip.destroy
82
+ drag_event = nil
83
+ end
84
+ })
85
+ end
86
+
87
+ def make_non_draggable
88
+ @tk.bind_remove("B1-Motion")
89
+ @tk.bind_remove("ButtonRelease-1")
90
+ @tk.bind_remove("<DropAcceptedEvent>")
91
+ end
92
+
93
+ DragAndDropEvent = Struct.new(:source, :target, :tooltip, :x_root, :y_root, :data, :drop_accepted) do
94
+ def as_json(*)
95
+ klass = self.class.name
96
+ {
97
+ JSON.create_id => klass,
98
+ "v" => [values[0].object_id, values[1].object_id, values[2].object_id].concat(values.drop 3),
99
+ }
100
+ end
101
+
102
+ def to_json(*args)
103
+ as_json.to_json(*args)
104
+ end
105
+
106
+ def self.json_create(object)
107
+ new(*[ObjectSpace._id2ref(object["v"][0]), ObjectSpace._id2ref(object["v"][1]), ObjectSpace._id2ref(object["v"][2])].concat(object["v"].drop 3))
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -29,16 +29,6 @@ module Glimmer
29
29
  # Follows the Proxy Design Pattern
30
30
  class LabelProxy < WidgetProxy
31
31
  include TextVariableOwner
32
-
33
- FONTS_PREDEFINED = %w[default text fixed menu heading caption small_caption icon tooltip]
34
-
35
- def font=(value)
36
- if (value.is_a?(Symbol) || value.is_a?(String)) && FONTS_PREDEFINED.include?(value.to_s.downcase)
37
- @tk.font = "tk_#{value}_font".camelcase(:upper)
38
- else
39
- super
40
- end
41
- end
42
32
  end
43
33
  end
44
34
  end
@@ -19,18 +19,14 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- require 'glimmer/tk/widget_proxy'
22
+ require 'glimmer/tk/toplevel_proxy'
23
23
 
24
24
  module Glimmer
25
25
  module Tk
26
26
  # Proxy for TkRoot
27
27
  #
28
28
  # Follows the Proxy Design Pattern
29
- class RootProxy < WidgetProxy
30
- REGEX_GEOMETRY = /[x+-]/
31
- DEFAULT_WIDTH = 190
32
- DEFAULT_HEIGHT = 95
33
-
29
+ class RootProxy < ToplevelProxy
34
30
  def initialize(*args, &block)
35
31
  @tk = ::TkRoot.new
36
32
  @tk.minsize = DEFAULT_WIDTH, DEFAULT_HEIGHT
@@ -50,10 +46,6 @@ module Glimmer
50
46
  Glimmer::DSL::Engine.add_content(self, Glimmer::DSL::Tk::RootExpression.new, keyword, *args, &block)
51
47
  end
52
48
 
53
- def has_attribute?(attribute, *args)
54
- %w[width height x y].include?(attribute.to_s) || super
55
- end
56
-
57
49
  def set_attribute(attribute, *args)
58
50
  case attribute.to_s
59
51
  when 'iconphoto'
@@ -70,45 +62,8 @@ module Glimmer
70
62
  end
71
63
  end
72
64
 
73
- def width
74
- geometry.split(REGEX_GEOMETRY)[0].to_i
75
- end
76
-
77
- def height
78
- geometry.split(REGEX_GEOMETRY)[1].to_i
79
- end
80
-
81
- def x
82
- sign_number(x_sign, geometry.split(REGEX_GEOMETRY)[2].to_i)
83
- end
84
-
85
- def y
86
- sign_number(y_sign, geometry.split(REGEX_GEOMETRY)[3].to_i)
87
- end
88
-
89
- def width=(value)
90
- @width = value.to_i
91
- self.geometry = "#{value.to_i}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
92
- end
93
-
94
- def height=(value)
95
- @height = value.to_i
96
- self.geometry = "#{@width || DEFAULT_WIDTH}x#{value.to_i}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
97
- end
98
-
99
- def x=(value)
100
- self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}#{y_sign}#{abs_y}"
101
- end
102
-
103
- def y=(value)
104
- self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}"
105
- end
106
-
107
65
  def handle_listener(listener_name, &listener)
108
66
  case listener_name.to_s.upcase
109
- when 'WM_DELETE_WINDOW', 'DELETE_WINDOW'
110
- listener_name = 'WM_DELETE_WINDOW'
111
- @tk.protocol(listener_name, &listener)
112
67
  when 'WM_OPEN_WINDOW', 'OPEN_WINDOW'
113
68
  @on_open_window_procs ||= []
114
69
  @on_open_window_procs << listener
@@ -128,36 +83,6 @@ module Glimmer
128
83
  end
129
84
  ::Tk.mainloop
130
85
  end
131
-
132
- private
133
-
134
- def sign_number(sign, number)
135
- "#{sign}1".to_i * number
136
- end
137
-
138
- def abs_x
139
- geometry.split(REGEX_GEOMETRY)[2].to_i
140
- end
141
-
142
- def abs_y
143
- geometry.split(REGEX_GEOMETRY)[3].to_i
144
- end
145
-
146
- def x_sign
147
- geometry_signs[0]
148
- end
149
-
150
- def y_sign
151
- geometry_signs[1]
152
- end
153
-
154
- def geometry_signs
155
- geometry.chars.select {|char| char.match(/[+-]/)}
156
- end
157
-
158
- def initialize_defaults
159
- self.background = '#ececec' if OS.mac?
160
- end
161
86
  end
162
87
  end
163
88
  end
@@ -28,8 +28,7 @@ module Glimmer
28
28
  # Follows the Proxy Design Pattern
29
29
  class TextProxy < WidgetProxy
30
30
  def handle_listener(listener_name, &listener)
31
- listener_name = listener_name.to_s.downcase
32
- case listener_name
31
+ case listener_name.to_s.downcase
33
32
  when '<<modified>>', '<modified>', 'modified'
34
33
  modified_listener = Proc.new do |*args|
35
34
  listener.call(*args)
@@ -38,27 +37,23 @@ module Glimmer
38
37
  bind('<Modified>', modified_listener)
39
38
  when '<<selection>>', '<selection>', 'selection'
40
39
  bind('<Selection>', listener)
41
- else
40
+ when 'destroy'
42
41
  super
43
- end
44
- end
45
-
46
- def text=(value)
47
- if value != @text
48
- if @text && value.start_with?(@text)
49
- insert('end', value[@text.size..-1])
50
- else
51
- delete('1.0', 'end')
52
- insert('end', value)
42
+ else
43
+ @tk.tag_add('__all__', '1.0', 'end') unless @tk.tag_names.include?('__all__')
44
+ # TODO make listener pass an event that has a modifiers attribute for easy representation of :shift, :meta, :control, etc... while a letter button is pressed
45
+ begin
46
+ @tk.tag_bind('__all__', listener_name, &listener)
47
+ rescue => e
48
+ Glimmer::Config.logger.debug {"Unable to bind to #{listener_name} .. attempting to surround with <>"}
49
+ Glimmer::Config.logger.debug {e.full_message}
50
+ listener_name = "<#{listener_name}" if !listener_name.start_with?('<')
51
+ listener_name = "#{listener_name}>" if !listener_name.end_with?('>')
52
+ @tk.tag_bind('__all__', listener_name, &listener)
53
53
  end
54
- @text = value
55
54
  end
56
55
  end
57
-
58
- def text
59
- @text = get("1.0", 'end')
60
- end
61
-
56
+
62
57
  def add_selection_format(option, value)
63
58
  process_selection_ranges { |range_start, range_end| add_format(range_start, range_end, option, value) }
64
59
  end
@@ -158,7 +153,7 @@ module Glimmer
158
153
  end
159
154
 
160
155
  def applied_font_format_tags_and_regions(region_start, region_end)
161
- lines = text.split("\n")
156
+ lines = value.split("\n")
162
157
  tags_and_regions = []
163
158
  all_tag_names = @tk.tag_names - ['sel']
164
159
  (region_start.to_i..region_end.to_i).each do |line_number|
@@ -332,10 +327,19 @@ module Glimmer
332
327
  region1_parts.last.to_i >= region2_parts.last.to_i
333
328
  end
334
329
 
335
- def clone_font(font)
336
- ::TkFont.new(Hash[font.actual])
330
+ def insert_image(text_index, *image_args)
331
+ TkTextImage.new(@tk, 'insert', :image => image_argument(image_args))
337
332
  end
338
-
333
+
334
+ def get_open_file_to_insert_image(text_index = 'insert')
335
+ image_filename = Glimmer::DSL::Tk::BuiltInDialogExpression.new.interpret(nil, 'get_open_file', filetypes: {
336
+ 'PNG Images' => '.png',
337
+ 'Gif Images' => '.gif',
338
+ 'PPM Images' => '.ppm'
339
+ })
340
+ insert_image('insert', image_filename) unless image_filename.nil? || image_filename.to_s.empty?
341
+ end
342
+
339
343
  private
340
344
 
341
345
  def initialize_defaults
@@ -345,6 +349,10 @@ module Glimmer
345
349
  self.padx = 5
346
350
  self.pady = 5
347
351
  end
352
+
353
+ def clone_font(font)
354
+ ::TkFont.new(Hash[font.actual])
355
+ end
348
356
  end
349
357
  end
350
358
  end
@@ -0,0 +1,119 @@
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
+ # Proxy for TkToplevel
27
+ #
28
+ # Follows the Proxy Design Pattern
29
+ class ToplevelProxy < WidgetProxy
30
+ REGEX_GEOMETRY = /[x+-]/
31
+ DEFAULT_WIDTH = 190
32
+ DEFAULT_HEIGHT = 95
33
+
34
+ attr_reader :tk
35
+
36
+ def has_attribute?(attribute, *args)
37
+ %w[width height x y].include?(attribute.to_s) || super
38
+ end
39
+
40
+ def width
41
+ geometry.split(REGEX_GEOMETRY)[0].to_i
42
+ end
43
+
44
+ def height
45
+ geometry.split(REGEX_GEOMETRY)[1].to_i
46
+ end
47
+
48
+ def x
49
+ sign_number(x_sign, geometry.split(REGEX_GEOMETRY)[2].to_i)
50
+ end
51
+
52
+ def y
53
+ sign_number(y_sign, geometry.split(REGEX_GEOMETRY)[3].to_i)
54
+ end
55
+
56
+ def width=(value)
57
+ @width = value.to_i
58
+ self.geometry = "#{value.to_i}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
59
+ end
60
+
61
+ def height=(value)
62
+ @height = value.to_i
63
+ self.geometry = "#{@width || DEFAULT_WIDTH}x#{value.to_i}#{x_sign}#{abs_x}#{y_sign}#{abs_y}"
64
+ end
65
+
66
+ def x=(value)
67
+ self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}#{y_sign}#{abs_y}"
68
+ end
69
+
70
+ def y=(value)
71
+ self.geometry = "#{@width || DEFAULT_WIDTH}x#{@height || DEFAULT_HEIGHT}#{x_sign}#{abs_x}#{value.to_i > 0 ? '+' : '-'}#{value.to_i.abs}"
72
+ end
73
+
74
+ def handle_listener(listener_name, &listener)
75
+ case listener_name.to_s.upcase
76
+ when 'WM_DELETE_WINDOW', 'DELETE_WINDOW'
77
+ listener_name = 'WM_DELETE_WINDOW'
78
+ @tk.protocol(listener_name, &listener)
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def sign_number(sign, number)
87
+ "#{sign}1".to_i * number
88
+ end
89
+
90
+ def abs_x
91
+ geometry.split(REGEX_GEOMETRY)[2].to_i
92
+ end
93
+
94
+ def abs_y
95
+ geometry.split(REGEX_GEOMETRY)[3].to_i
96
+ end
97
+
98
+ def x_sign
99
+ geometry_signs[0]
100
+ end
101
+
102
+ def y_sign
103
+ geometry_signs[1]
104
+ end
105
+
106
+ def geometry_signs
107
+ geometry.chars.select {|char| char.match(/[+-]/)}
108
+ end
109
+
110
+ def initialize_defaults
111
+ self.background = '#ececec' if OS.mac?
112
+ on('DELETE_WINDOW') do
113
+ grab_release
114
+ destroy
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end