win_gui 0.2.5 → 0.2.9

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