glimmer-dsl-tk 0.0.27 → 0.0.31

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.
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