win_gui 0.2.5 → 0.2.9

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/HISTORY CHANGED
@@ -25,3 +25,19 @@
25
25
  == 0.2.5 / 2010-06-04
26
26
 
27
27
  * Code brought to about Book-level quality
28
+
29
+ == 0.2.6 / 2010-06-04
30
+
31
+ * Docs improved
32
+
33
+ == 0.2.7 / 2010-06-04
34
+
35
+ * more punctuation added to String#to_key
36
+
37
+ == 0.2.8 / 2010-06-04
38
+
39
+ * Window#click api improved with position and mouse_button type
40
+
41
+ == 0.2.9 / 2010-06-04
42
+
43
+ * Window: Full feature parity with Book code
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.5
1
+ 0.2.9
data/lib/extension.rb CHANGED
@@ -16,10 +16,14 @@ class String
16
16
  [WinGui.const_get(:VK_OEM_COMMA)]
17
17
  when '.'
18
18
  [WinGui.const_get(:VK_OEM_PERIOD)]
19
+ when ';'
20
+ [WinGui.const_get(:VK_OEM_1)]
19
21
  when ':'
20
22
  [:VK_SHIFT, :VK_OEM_1].map {|s| WinGui.const_get s}
21
23
  when "\\"
22
24
  [WinGui.const_get(:VK_OEM_102)]
25
+ when "\n"
26
+ [WinGui.const_get(:VK_RETURN)]
23
27
  else
24
28
  raise "Can't convert unknown character: #{self}"
25
29
  end
@@ -1,5 +1,10 @@
1
1
  require 'win/gui'
2
2
 
3
+ # Module contains Win32 API gui-related functions as both module and instance methods.
4
+ # See documentation of Win::Gui module of *win* gem for a full scope of available functions.
5
+ # In addition, module defines several higher-level convenience methods that can be useful
6
+ # when dealing with GUI-related tasks under windows (such as testing automation).
7
+ #
3
8
  module WinGui
4
9
  include Win::Gui
5
10
  extend Win::Gui
@@ -13,16 +18,22 @@ module WinGui
13
18
  # Default timeout for dialog operations (in sec)
14
19
  LOOKUP_TIMEOUT = 3
15
20
 
16
- # Window class identifying standard modal dialog window
21
+ # Windows class identifying standard modal dialog window
17
22
  DIALOG_WINDOW_CLASS = '#32770'
18
23
 
19
24
  # Module defines convenience methods on top of straightforward Win32 API functions:
20
25
 
21
26
  # Finds top-level dialog window by title and yields found dialog window to attached block.
22
27
  # We work with dialog window in a block, and then we wait for it to close before proceeding.
23
- # That is, unless your block returns nil, in which case dialog is ignored and method immediately returns nil
24
- # If no block is given, method just returns found dialog window (or nil if dialog is not found)
25
- def dialog(opts={})
28
+ # That is, unless your block returns nil, in which case dialog is ignored and method immediately returns nil.
29
+ # If no block is given, method just returns found dialog window (or nil if dialog is not found).
30
+ # Options:
31
+ # :title:: dialog title
32
+ # :class:: dialog class - default DIALOG_WINDOW_CLASS
33
+ # :timeout:: timeout (seconds) - default LOOKUP_TIMEOUT (3)
34
+ # :raise:: raise this exception instead of returning nil if nothing found
35
+ #
36
+ def dialog(opts={}) # :yields: dialog_window
26
37
  dialog = Window.top_level( {class: DIALOG_WINDOW_CLASS, timeout: LOOKUP_TIMEOUT}.merge opts )
27
38
  dialog.set_foreground_window if dialog
28
39
  wait = block_given? ? yield(dialog) : false
@@ -30,10 +41,11 @@ module WinGui
30
41
  dialog
31
42
  end
32
43
 
33
- # Emulates combinations of (any amount of) keys pressed one after another (Ctrl+Alt+P) and then released
34
- # *keys should be a sequence of a virtual-key codes. These codes must be a value in the range 1 to 254.
44
+ # Emulates combinations of (any amount of) keys pressed one after another (Ctrl+Alt+P) and then released.
45
+ # *keys should be a sequence of a virtual-key codes (value in the range 1 to 254).
35
46
  # For a complete list, see msdn:Virtual Key Codes.
36
47
  # If alphanumerical char is given instead of virtual key code, only lowercase letters result (no VK_SHIFT!).
48
+ #
37
49
  def keystroke(*keys)
38
50
  return if keys.empty?
39
51
  key = String === keys.first ? keys.first.upcase.ord : keys.first.to_i
@@ -44,28 +56,11 @@ module WinGui
44
56
  keybd_event key, 0, KEYEVENTF_KEYUP, 0
45
57
  end
46
58
 
47
- # types text message into a window currently holding the focus
59
+ # Types text message into a window currently holding the focus
60
+ #
48
61
  def type_in(message)
49
62
  message.scan(/./m) do |char|
50
63
  keystroke(*char.to_key)
51
64
  end
52
65
  end
53
-
54
-
55
- # DialogWndClass = '#32770'
56
- # def dialog(title, seconds=5)
57
- # close, dlg = begin
58
- # sleep 0.25
59
- # w = Gui.top_level(title, seconds, DialogWndClass)
60
- # Gui.set_foreground_window w.handle
61
- # sleep 0.25
62
- #
63
- # [yield(w), w]
64
- # rescue TimeoutError
65
- # end
66
- #
67
- # dlg.wait_for_close if dlg && close
68
- # return dlg
69
- # end
70
-
71
66
  end
@@ -11,12 +11,11 @@ module WinGui
11
11
 
12
12
  attr_reader :handle
13
13
 
14
- # Looks up window handle using code specified in attached block (either with or without :timeout)
15
- # Returns either Window instance (for a found handle) or nil if nothing found
14
+ # Looks up window handle using code specified in attached block (either with or without :timeout).
15
+ # Returns either Window instance (for a found handle) or nil if nothing found.
16
16
  # Private method to dry up other window lookup methods
17
- # :yields:
18
17
  #
19
- def self.lookup_window(opts)
18
+ def self.lookup_window(opts) # :yields: index, position
20
19
  # Need this to avoid handle considered local in begin..end block
21
20
  handle = yield
22
21
  if opts[:timeout]
@@ -32,60 +31,97 @@ module WinGui
32
31
  Window.new(handle) if handle
33
32
  end
34
33
 
35
- # Find top level window by title/class, returns wrapped Window object or nil.
36
- # If timeout option given, waits for window to appear within timeout, returns nil if it didn't
34
+ # Finds top level window by title/class, returns wrapped Window object or nil (raises exception if asked to).
35
+ # If timeout option given, waits for window to appear within timeout, returns nil if it didn't.
37
36
  # Options:
38
37
  # :title:: window title
39
38
  # :class:: window class
40
39
  # :timeout:: timeout (seconds)
40
+ # :raise:: raise this exception instead of returning nil if nothing found
41
+ #
41
42
  def self.top_level(opts={})
42
43
  lookup_window(opts) { WinGui.find_window opts[:class], opts[:title] }
43
44
  end
44
45
 
45
- # Find DIRECT child window (control) by either control ID or window class/title.
46
+ # Finds DIRECT child window (control) by either control ID or window class/title.
47
+ # Options:
48
+ # :id:: integer control id (such as IDOK, IDCANCEL, etc)
49
+ # :title:: window title
50
+ # :class:: window class
51
+ # :timeout:: timeout (seconds)
52
+ # :raise:: raise this exception instead of returning nil if nothing found
53
+ #
46
54
  def child(opts={})
47
55
  self.class.lookup_window(opts) do
48
56
  opts[:id] ? get_dlg_item(opts[:id]) : find_window_ex(0, opts[:class], opts[:title])
49
57
  end
50
58
  end
51
59
 
52
- # Return array of Windows that are descendants (not only DIRECT children) of a given Window
60
+ # Returns array of Windows that are descendants (not only DIRECT children) of a given Window
61
+ #
53
62
  def children
54
63
  enum_child_windows.map{|child_handle| Window.new child_handle}
55
64
  end
56
65
 
57
- # Emulate click of the control identified by opts (:id, :title, :class)
58
- # Return true if click was presumably successful, false if it failed (control was not found)
66
+ # Emulates click of the control identified by opts (:id, :title, :class).
67
+ # Beware of keyboard shortcuts in button titles! So, use "&Yes" instead of just "Yes".
68
+ # Returns screen coordinates of click point if successful, nil if control was not found
69
+ # :id:: integer control id (such as IDOK, IDCANCEL, etc)
70
+ # :title:: window title
71
+ # :class:: window class
72
+ # :raise:: raise this exception instead of returning nil if nothing found
73
+ # :position/point/where:: location where the click is to be applied - default :center
74
+ # :mouse_button/button/which:: mouse button which to click - default :right
75
+ #
59
76
  def click(opts={})
60
77
  control = child(opts)
61
78
  if control
62
79
  left, top, right, bottom = control.get_window_rect
63
- center = [(left + right) / 2, (top + bottom) / 2]
64
- WinGui.set_cursor_pos *center
65
- WinGui.mouse_event WinGui::MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
66
- WinGui.mouse_event WinGui::MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
67
- true
80
+
81
+ where = opts[:point] || opts[:where] || opts[:position]
82
+ point = case where
83
+ when Array
84
+ where # Explicit screen coords
85
+ when :random
86
+ [left + rand(right - left), top + rand(bottom - top)] # Random point within control window
87
+ else
88
+ [(left + right) / 2, (top + bottom) / 2] # Center of a control window
89
+ end
90
+
91
+ WinGui.set_cursor_pos *point
92
+
93
+ button = opts[:mouse_button] || opts[:mouse] || opts[:which]
94
+ down, up = (button == :right) ?
95
+ [WinGui::MOUSEEVENTF_RIGHTDOWN, WinGui::MOUSEEVENTF_RIGHTUP] :
96
+ [WinGui::MOUSEEVENTF_LEFTDOWN, WinGui::MOUSEEVENTF_LEFTUP]
97
+
98
+ WinGui.mouse_event down, 0, 0, 0, 0
99
+ WinGui.mouse_event up, 0, 0, 0, 0
100
+ point
68
101
  else
69
- false
102
+ nil
70
103
  end
71
104
  end
72
105
 
106
+ # Waits for this window to close with timeout (default CLOSE_TIMEOUT).
107
+ #
73
108
  def wait_for_close(timeout=CLOSE_TIMEOUT )
74
109
  timeout(timeout) do
75
110
  sleep SLEEP_DELAY while window_visible?
76
111
  end
77
112
  end
78
113
 
79
- # We alias convenience method shut_window (from Win::Gui::WIndow) with even more convenient
114
+ # We alias convenience method shut_window (from Win::Gui::Window) with even more convenient
80
115
  # window.close
81
116
  # Please keep in mind that Win32 API has another function CloseWindow that merely MINIMIZES window.
82
117
  # If you want to invoke this function, you can do it like this:
83
118
  # window.close_window
119
+ #
84
120
  def close
85
121
  shut_window
86
122
  end
87
123
 
88
- # Since Window instances wrap actual window handles, they should support WinGui functions
124
+ # Since Window instances wrap actual window handles, they should directly support Win32 API functions
89
125
  # manipulating these handles. Therefore, when unsupported instance method is invoked, we check if
90
126
  # WinGui responds to such method, and if yes, call it with our window handle as a first argument.
91
127
  # This gives us all handle-related WinGui functions as instance methods for Window instances, like so:
@@ -95,6 +131,11 @@ module WinGui
95
131
  # Of course, if we unvoke WinGui function that DOESN'T accept handle as a first arg this way, we are screwed.
96
132
  # Call such functions only like this:
97
133
  # WinGui.function(*args)
134
+ # TODO: Such setup is problematic if WinGui is included into Window ancestor chain.
135
+ # TODO: in this case, all WinGui functions become available as instance methods, and method_missing never fires.
136
+ # TODO: it may be a better solution to explicitly define all needed instance methods.
137
+ # TODO: instead of showing off cool meta-programming skillz.
138
+ #
98
139
  def method_missing(name, *args, &block)
99
140
  if WinGui.respond_to? name
100
141
  # puts "Window #{@handle} calling: #{name} #{@handle} #{args} &#{block}"
@@ -21,16 +21,20 @@ module WinGuiTest
21
21
  ' '.to_key.should == " ".unpack('C')
22
22
  end
23
23
 
24
- it 'transforms .,:\\ into [equivalent key code]' do
24
+ it 'transforms \n into [VK_RETURN]' do
25
+ "\n".to_key.should == [VK_RETURN]
26
+ end
27
+
28
+ it 'transforms .,:;\\ into [equivalent key code]' do
25
29
  ','.to_key.should == [VK_OEM_COMMA]
26
30
  '.'.to_key.should == [VK_OEM_PERIOD]
27
31
  ':'.to_key.should == [VK_SHIFT, VK_OEM_1]
32
+ ';'.to_key.should == [VK_OEM_1]
28
33
  "\\".to_key.should == [VK_OEM_102]
29
34
  end
30
35
 
31
36
  it 'raises error if char is not implemented punctuation' do
32
37
  ('!'..'+').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
33
- (';'..'@').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
34
38
  (']'..'`').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
35
39
  ('{'..'~').each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
36
40
  ['-', '/', '['].each {|char| lambda {char.to_key}.should raise_error ERROR_CONVERSION }
@@ -169,27 +169,47 @@ module WinGuiTest
169
169
  end # describe #children
170
170
 
171
171
  describe '#click' do
172
- it 'emulates clicking of the control identified by id, returns true' do
172
+ it 'emulates left click of the control identified by id, returns click coords' do
173
173
  with_dialog(:save) do |dialog|
174
- dialog.click(id: IDCANCEL).should == true
175
- sleep 0.5
174
+ point = dialog.click(id: IDCANCEL)
175
+ point.should be_an Array
176
+ sleep 0.3
176
177
  dialog.window?.should == false
177
178
  end
178
179
  end
179
180
 
180
- it 'emulates clicking of the control identified by title, returns true' do
181
+ it 'emulates left click of the control identified by title, returns click coords' do
181
182
  with_dialog(:save) do |dialog|
182
- dialog.click(title: "Cancel").should == true
183
- sleep 0.5
183
+ point = dialog.click(title: "Cancel")
184
+ point.should be_an Array
185
+ sleep 0.3
184
186
  dialog.window?.should == false
185
187
  end
186
188
  end
187
189
 
188
- it 'returns false if the specified control was not found' do
190
+ it 'emulates right click of the control identified by id, returns click coords' do
189
191
  with_dialog(:save) do |dialog|
190
- dialog.click(title: "Shpancel").should == false
191
- dialog.click(id: 66).should == false
192
- sleep 0.5
192
+ point = dialog.click(id: IDCANCEL, mouse_button: :right)
193
+ point.should be_an Array
194
+ sleep 0.3
195
+ dialog.window?.should == true
196
+ end
197
+ end
198
+
199
+ it 'emulates right click of the control identified by title, returns click coords' do
200
+ with_dialog(:save) do |dialog|
201
+ point = dialog.click(title: "Cancel", mouse_button: :right)
202
+ point.should be_an Array
203
+ sleep 0.3
204
+ dialog.window?.should == true
205
+ end
206
+ end
207
+
208
+ it 'returns nil if the specified control was not found' do
209
+ with_dialog(:save) do |dialog|
210
+ dialog.click(title: "Shpancel").should == nil
211
+ dialog.click(id: 66).should == nil
212
+ sleep 0.3
193
213
  dialog.window?.should == true
194
214
  end
195
215
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 5
9
- version: 0.2.5
8
+ - 9
9
+ version: 0.2.9
10
10
  platform: ruby
11
11
  authors:
12
12
  - arvicco