win_gui 0.2.17 → 0.2.18

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -73,3 +73,7 @@
73
73
  == 0.2.17 / 2010-10-18
74
74
 
75
75
  * Gemspec changed for Bundler
76
+
77
+ == 0.2.18 / 2011-02-17
78
+
79
+ * Full Regexp support for Window find operations
data/README.rdoc CHANGED
@@ -7,7 +7,8 @@ url:: http://github.com/arvicco/win_gui
7
7
  WinGui is a module that provides higher-level abstractions/wrappers around GUI-related
8
8
  Win32 API functions. It uses Win gem as a basis, which in turn uses FFI.
9
9
  So (in theory) it should work for any Ruby implementation supporting FFI. In practice,
10
- it's only been tested under mingw and cygwin flavors of Ruby 1.9.1.
10
+ it's only been tested under mingw and cygwin flavors of Ruby 1.9. Porting to JRuby is
11
+ under way, waiting for its 1.9 compatibility to mature.
11
12
 
12
13
  == SUMMARY:
13
14
 
@@ -32,26 +33,24 @@ For example, here is how you deal with typical GUI-related tasks using Win:
32
33
  end
33
34
  close_window(window_handle)
34
35
 
35
- Ideally, there should be a thin wrapper class around window handle, and the code above should be more like this:
36
+ This works fine, but such functional style is does not feel like object-oriented Ruby.
37
+ Ideally, there should be a thin wrapper class around window handle, and the code above
38
+ should be more like this:
39
+
36
40
  require 'win_gui'
37
41
  include WinGui
38
42
 
39
- window = Window.find(:first, :class => 'WinClass')
43
+ window = Window.find(:title => /PartTitle/)
40
44
  puts window.handle, window.title, window.thread, window.process
41
- window.each_child {|child| puts child.handle, child.title, child.thread, child.process }
45
+ window.children.each {|child| puts child.handle, child.title, child.thread, child.process }
42
46
  window.close
43
47
 
44
- This library will try to provide such wrappers and convenience methods that will make working with
45
- Windows GUI-related code much more fun than it is right now.
48
+ WinGui library strives to provide such wrappers and convenience methods that will make
49
+ working with Windows GUI-related code much more fun than it is right now.
46
50
 
47
51
  == REQUIREMENTS:
48
52
 
49
- Only works with Ruby 1.9.1+ compatible implementations since Win gem uses some of latest Ruby features.
50
-
51
- == FEATURES/PROBLEMS:
52
-
53
- This project is quite new, so it may be not suitable for production-quality systems yet.
54
- Contributors always welcome!
53
+ Only works with Ruby 1.9 compatible implementations since Win gem uses some of latest Ruby features.
55
54
 
56
55
  == INSTALL:
57
56
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.17
1
+ 0.2.18
@@ -14,7 +14,7 @@ module WinGui
14
14
  # Returns either Window instance (for a found handle) or nil if nothing found.
15
15
  # Private method to dry up other window lookup methods
16
16
  #
17
- def lookup_window(opts) # :yields: index, position
17
+ def lookup_window opts # :yields: index, position
18
18
  # Need this to avoid handle considered local var in begin..end block
19
19
  handle = yield
20
20
  if opts[:timeout]
@@ -30,40 +30,63 @@ module WinGui
30
30
  Window.new(handle) if handle
31
31
  end
32
32
 
33
+ def lookup_window_in_collection opts, &collection_proc
34
+ class_name = opts[:class]
35
+ title = opts[:title]
36
+ id = opts[:id]
37
+ class_regexp = class_name.is_a? Regexp
38
+ title_regexp = title.is_a? Regexp
39
+
40
+ lookup_window(opts) do
41
+ collection_proc.call.each do |handle|
42
+ win = Window.new handle
43
+
44
+ id_match = !id || win.id == id
45
+ title_match = !title || win.title == title ||
46
+ title_regexp && win.title =~ title
47
+ class_match = !class_name || win.class_name == class_name ||
48
+ class_regexp && win.class_name =~ class_name
49
+ return win if class_match && title_match && id_match
50
+ end
51
+ nil
52
+ end
53
+ end
54
+
33
55
  # Finds top level window by title/class, returns wrapped Window object or nil (raises exception if asked to).
34
56
  # If timeout option given, waits for window to appear within timeout, returns nil if it didn't.
35
57
  # Options:
36
- # :title:: window title
37
- # :class:: window class
58
+ # :title:: window title (String or Regexp)
59
+ # :class:: window class (String or Regexp)
38
60
  # :timeout:: timeout (seconds)
39
61
  # :raise:: raise this exception instead of returning nil if nothing found
40
62
  #
41
- def top_level(opts={})
42
- lookup_window(opts) { WinGui.find_window opts[:class], opts[:title] }
63
+ def top_level opts={}
64
+ if opts[:class].is_a?(Regexp) or opts[:title].is_a?(Regexp)
65
+ lookup_window_in_collection(opts) { WinGui.enum_windows }
66
+ else
67
+ lookup_window(opts) { WinGui.find_window opts[:class], opts[:title] }
68
+ end
43
69
  end
44
- alias_method :find, :top_level
70
+
71
+ alias find top_level
45
72
  end
46
73
 
47
74
  # Finds child window (control) by either control ID or window class/title.
48
75
  # By default, only direct children are searched.
49
76
  # Options:
50
77
  # :id:: integer control id (such as IDOK, IDCANCEL, etc)
51
- # :title:: window title
52
- # :class:: window class
78
+ # :title:: window title (String or Regexp)
79
+ # :class:: window class (String or Regexp)
53
80
  # :indirect:: search all descendants, not only direct children
54
81
  # :timeout:: timeout (seconds)
55
82
  # :raise:: raise this exception instead of returning nil if nothing found
56
- # TODO: add the ability to nail indirect children as well
57
83
  #
58
84
  def child(opts={})
59
85
  if opts[:indirect]
60
- self.class.lookup_window opts do
61
- found = children.find do |child|
62
- (opts[:id] ? child.id == opts[:id] : true) &&
63
- (opts[:class] ? child.class_name == opts[:class] : true) &&
64
- (opts[:title] ? child.title == opts[:title] : true)
65
- end
66
- found.handle if found
86
+ self.class.lookup_window_in_collection(opts) { enum_child_windows }
87
+ elsif opts[:class].is_a?(Regexp) or opts[:title].is_a?(Regexp)
88
+ self.class.lookup_window_in_collection(opts) do
89
+ enum_child_windows.select { |handle| child? handle }
67
90
  end
68
91
  else
69
92
  self.class.lookup_window opts do
@@ -106,9 +129,9 @@ module WinGui
106
129
  WinGui.set_cursor_pos *point
107
130
 
108
131
  button = opts[:mouse_button] || opts[:mouse] || opts[:which]
109
- down, up = (button == :right) ?
110
- [WinGui::MOUSEEVENTF_RIGHTDOWN, WinGui::MOUSEEVENTF_RIGHTUP] :
111
- [WinGui::MOUSEEVENTF_LEFTDOWN, WinGui::MOUSEEVENTF_LEFTUP]
132
+ down, up = (button == :right) ?
133
+ [WinGui::MOUSEEVENTF_RIGHTDOWN, WinGui::MOUSEEVENTF_RIGHTUP] :
134
+ [WinGui::MOUSEEVENTF_LEFTDOWN, WinGui::MOUSEEVENTF_LEFTUP]
112
135
 
113
136
  WinGui.mouse_event down, 0, 0, 0, 0
114
137
  WinGui.mouse_event up, 0, 0, 0, 0
@@ -150,6 +173,8 @@ module WinGui
150
173
  get_window_thread_process_id.last
151
174
  end
152
175
 
176
+ alias pid process
177
+
153
178
  # Control ID associated with the window (only makes sense for controls)
154
179
  def id
155
180
  get_dlg_ctrl_id
@@ -1,5 +1,104 @@
1
1
  require "spec_helper.rb"
2
2
 
3
+ shared_examples_for 'normal window finder' do
4
+ it 'finds top-level window by title and wraps it in a Window object' do
5
+ window = Window.top_level(:title => title, timeout: 1)
6
+ window.handle.should == @win.handle
7
+ end
8
+
9
+ it 'finds top-level window by class and wraps it in a Window object' do
10
+ window = Window.top_level(:class => class_name, timeout: 1)
11
+ window.handle.should == @win.handle
12
+ end
13
+
14
+ it 'finds top-level window by both class and title' do
15
+ window = Window.top_level(:class => class_name, :title => title)
16
+ window.handle.should == @win.handle
17
+ end
18
+
19
+ it 'finds top-level window by both class and title' do
20
+ window = Window.top_level(:class => class_name, :title => title)
21
+ window.handle.should == @win.handle
22
+ end
23
+
24
+ it 'returns nil if either window title or class do not match' do
25
+ Window.top_level(:class => class_name, :title => impossible).should == nil
26
+ Window.top_level(:class => impossible, :title => title).should == nil
27
+ end
28
+
29
+ it 'returns nil immediately if top-level window not found' do
30
+ start = Time.now
31
+ Window.top_level(:title => impossible).should == nil
32
+ (Time.now - start).should be_within(0.05).of 0
33
+ end
34
+
35
+ it 'returns nil after timeout if top-level window not found' do
36
+ start = Time.now
37
+ Window.top_level(:title => impossible, :timeout => 0.3).should == nil
38
+ (Time.now - start).should be_within(0.05).of 0.3
39
+ end
40
+
41
+ it 'raises exception if asked to' do
42
+ expect { Window.top_level(:title => impossible, :raise => "Horror!") }.to raise_error "Horror!"
43
+ end
44
+ end
45
+
46
+ shared_examples_for 'child window finder' do
47
+
48
+ it 'finds ANY child window without specific args' do
49
+ use { @child = @win.child(opts) }
50
+ @child.should_not == nil
51
+ @win.child?(@child.handle).should == true
52
+ end
53
+
54
+ it 'returns nil immediately if specific child not found' do
55
+ start = Time.now
56
+ @win.child(opts.merge(:title => impossible)).should == nil
57
+ (Time.now - start).should be_within(0.05).of 0
58
+ end
59
+
60
+ it 'returns nil after timeout if specific child not found' do
61
+ start = Time.now
62
+ @win.child(opts.merge(:title => impossible, :timeout => 0.5)).should == nil
63
+ (Time.now - start).should be_within(0.05).of 0.5
64
+ end
65
+
66
+ it 'finds child window by class and returns it as a Window object (no timeout)' do
67
+ child = @win.child opts.merge(:class => class_name)
68
+ child.should_not == nil
69
+ @win.child?(child.handle).should == true
70
+ end
71
+
72
+ it 'finds child window by class and returns it as a Window object (with timeout)' do
73
+ child = @win.child opts.merge(:class => class_name, :timeout => 0.5)
74
+ child.should_not == nil
75
+ @win.child?(child.handle).should == true
76
+ end
77
+
78
+ it 'finds child with specific text and returns it as a Window object' do
79
+ with_dialog(:save) do |dialog|
80
+ child = dialog.child opts.merge(:title => cancel)
81
+ child.should_not == nil
82
+ dialog.child?(child.handle).should == true
83
+ child.get_dlg_ctrl_id.should == IDCANCEL
84
+
85
+ child = dialog.child opts.merge(:title => "&Save")
86
+ child.should_not == nil
87
+ dialog.child?(child.handle).should == true
88
+ child.get_dlg_ctrl_id.should == IDOK
89
+ end
90
+ end
91
+
92
+ it 'finds child control with a given ID and returns it as a Window object' do
93
+ with_dialog(:save) do |dialog|
94
+ child = dialog.child opts.merge(:id => IDCANCEL)
95
+ child.should_not == nil
96
+ dialog.child?(child.handle).should == true
97
+ child.text.should == "Cancel"
98
+ end
99
+ end
100
+ end
101
+
3
102
  describe WinGui::Window do
4
103
  before(:each) { @win = launch_test_app.main_window }
5
104
  after(:each) { close_test_app }
@@ -51,18 +150,17 @@ describe WinGui::Window do
51
150
 
52
151
  it 'has class_name and text/title properties (derived from WinGui function calls)' do
53
152
  @win.class_name.should == WIN_CLASS
54
- # text propery accessed by sending WM_GETTEXT directly to window (convenience method in WinGui)
153
+ # text property accessed by sending WM_GETTEXT directly to window (convenience method in WinGui)
55
154
  @win.text.should == WIN_TITLE
56
- # window_text propery accessed via GetWindowText
155
+ # window_text property accessed via GetWindowText
57
156
  @win.window_text.should == WIN_TITLE
58
157
  # title property is just an alias for window_text
59
158
  @win.title.should == WIN_TITLE
60
159
  end
61
160
 
62
- it 'has thread and process properties derived from get_window_thread_process_id' do
63
- thread = @win.thread
64
- process = @win.process
65
- [thread, process].should == get_window_thread_process_id(@win.handle)
161
+ it 'has thread and process(pid) properties derived from get_window_thread_process_id' do
162
+ [@win.thread, @win.process].should == get_window_thread_process_id(@win.handle)
163
+ @win.pid.should == @win.process
66
164
  end
67
165
 
68
166
  it 'has id property that only makes sense for controls' do
@@ -71,143 +169,79 @@ describe WinGui::Window do
71
169
  end
72
170
 
73
171
  describe '::top_level' do
74
- it 'finds top-level window by title and wraps it in a Window object' do
75
- window = Window.top_level(title: WIN_TITLE, timeout: 1)
76
- window.handle.should == @win.handle
77
- end
78
-
79
- it 'finds top-level window by class and wraps it in a Window object' do
80
- window = Window.top_level(class: WIN_CLASS, timeout: 1)
81
- window.handle.should == @win.handle
82
- end
83
-
84
172
  it 'finds ANY top-level window without args and wraps it in a Window object' do
85
173
  use { @window = Window.top_level() }
86
174
  @window.should be_a Window
87
175
  end
88
176
 
89
- it 'returns nil immediately if top-level window with given title not found' do
90
- start = Time.now
91
- Window.top_level(title: IMPOSSIBLE).should == nil
92
- (Time.now - start).should be_within(0.03).of 0
93
- end
177
+ context 'with String arguments' do
178
+ let(:title) { WIN_TITLE }
179
+ let(:class_name) { WIN_CLASS }
180
+ let(:impossible) { IMPOSSIBLE }
94
181
 
95
- it 'returns nil after timeout if top-level window with given title not found' do
96
- start = Time.now
97
- Window.top_level(title: IMPOSSIBLE, timeout: 0.3).should == nil
98
- (Time.now - start).should be_within(0.03).of 0.3
182
+ it_should_behave_like 'normal window finder'
99
183
  end
100
184
 
101
- it 'raises exception if asked to' do
102
- expect { Window.top_level(title: IMPOSSIBLE, raise: "Horror!") }.to raise_error "Horror!"
103
- end
185
+ context 'with Regexp arguments' do
186
+ let(:title) { Regexp.new WIN_TITLE[-6..-1] }
187
+ let(:class_name) { Regexp.new WIN_CLASS[-6..-1] }
188
+ let(:impossible) { Regexp.new IMPOSSIBLE }
104
189
 
105
- it 'uses .find as alias for .top_level' do
106
- use { @window = Window.find() }
107
- @window.should be_a Window
190
+ it_should_behave_like 'normal window finder'
108
191
  end
109
- end # describe .top_level
110
192
 
111
- describe '#child' do
112
- spec { use { @child = @win.child(title: "Title", class: "Class", id: 0) } }
193
+ context 'with mixed arguments' do
194
+ let(:title) { Regexp.new WIN_TITLE[-6..-1] }
195
+ let(:class_name) { WIN_CLASS }
196
+ let(:impossible) { IMPOSSIBLE }
113
197
 
114
- it 'returns nil immediately if specific child not found' do
115
- start = Time.now
116
- @win.child(title: IMPOSSIBLE).should == nil
117
- (Time.now - start).should be_within(0.03).of 0
118
- end
119
-
120
- it 'returns nil after timeout if specific child not found' do
121
- start = Time.now
122
- @win.child(title: IMPOSSIBLE, timeout: 0.5).should == nil
123
- (Time.now - start).should be_within(0.03).of 0.5
198
+ it_should_behave_like 'normal window finder'
124
199
  end
125
200
 
126
- it 'finds ANY child window without args' do
127
- use { @child = @win.child() }
128
- @child.should_not == nil
129
- @win.child?(@child.handle).should == true
130
- end
201
+ end # describe .top_level
131
202
 
132
- it 'finds child window by class and returns it as a Window object (no timeout)' do
133
- child = @win.child(class: TEXTAREA_CLASS)
134
- child.should_not == nil
135
- @win.child?(child.handle).should == true
136
- end
203
+ describe '#child' do
204
+ spec { use { @child = @win.child(title: "Title", class: "Class", id: 0) } }
137
205
 
138
- it 'finds child window by class and returns it as a Window object (with timeout)' do
139
- child = @win.child(class: TEXTAREA_CLASS, timeout: 0.5)
140
- child.should_not == nil
206
+ context 'direct children only' do
207
+ let(:opts) { {} }
141
208
 
142
- @win.child?(child.handle).should == true
143
- child = @win.child(class: STATUSBAR_CLASS, timeout: 0.5)
144
- child.should_not == nil
145
- @win.child?(child.handle).should == true
146
- end
209
+ context 'with String arguments' do
210
+ let(:cancel) { 'Cancel' }
211
+ let(:class_name) { TEXTAREA_CLASS }
212
+ let(:impossible) { IMPOSSIBLE }
147
213
 
148
- it 'finds child with specific text and returns it as a Window object' do
149
- with_dialog(:save) do |dialog|
150
- child = dialog.child(title: "Cancel")
151
- child.should_not == nil
152
- dialog.child?(child.handle).should == true
153
- child.get_dlg_ctrl_id.should == IDCANCEL
154
-
155
- child = dialog.child(title: "&Save")
156
- child.should_not == nil
157
- dialog.child?(child.handle).should == true
158
- child.get_dlg_ctrl_id.should == IDOK
214
+ it_should_behave_like 'child window finder'
159
215
  end
160
- end
161
216
 
162
- it 'finds child control with a given ID and returns it as a Window object' do
163
- with_dialog(:save) do |dialog|
164
- child = dialog.child(id: IDCANCEL)
165
- child.should_not == nil
166
- dialog.child?(child.handle).should == true
167
- child.text.should == "Cancel"
217
+ context 'with Regexp arguments' do
218
+ let(:cancel) { /Cancel/ }
219
+ let(:class_name) { Regexp.new TEXTAREA_CLASS[-6..-1] }
220
+ let(:impossible) { Regexp.new IMPOSSIBLE }
221
+
222
+ it_should_behave_like 'child window finder'
168
223
  end
169
224
  end
170
225
 
171
- context 'indirect child' do
172
- it 'returns nil if specified child not found' do
173
- @win.child(title: IMPOSSIBLE, indirect: true).should == nil
174
- end
226
+ context 'indirect children' do
227
+ let(:opts) { {:indirect => true} }
175
228
 
176
- it 'finds ANY child window without other args' do
177
- use { @child = @win.child(indirect: true) }
178
- @child.should_not == nil
179
- @win.child?(@child.handle).should == true
180
- end
229
+ context 'with String arguments' do
230
+ let(:cancel) { 'Cancel' }
231
+ let(:class_name) { TEXTAREA_CLASS }
232
+ let(:impossible) { IMPOSSIBLE }
181
233
 
182
- it 'finds child window by class' do
183
- child = @win.child(class: TEXTAREA_CLASS, indirect: true)
184
- child.should_not == nil
185
- @win.child?(child.handle).should == true
234
+ it_should_behave_like 'child window finder'
186
235
  end
187
236
 
188
- it 'finds child with specific text' do
189
- with_dialog(:save) do |dialog|
190
- child = dialog.child(title: "Cancel", indirect: true)
191
- child.should_not == nil
192
- dialog.child?(child.handle).should == true
193
- child.id.should == IDCANCEL
194
-
195
- child = dialog.child(title: "&Save", indirect: true)
196
- child.should_not == nil
197
- dialog.child?(child.handle).should == true
198
- child.id.should == IDOK
199
- end
200
- end
237
+ context 'with Regexp arguments' do
238
+ let(:cancel) { /Cancel/ }
239
+ let(:class_name) { Regexp.new TEXTAREA_CLASS[-6..-1] }
240
+ let(:impossible) { Regexp.new IMPOSSIBLE }
201
241
 
202
- it 'finds child control with a given ID ' do
203
- with_dialog(:save) do |dialog|
204
- child = dialog.child(id: IDCANCEL, indirect: true)
205
- child.should_not == nil
206
- dialog.child?(child.handle).should == true
207
- child.text.should == "Cancel"
208
- end
242
+ it_should_behave_like 'child window finder'
209
243
  end
210
- end # context indirect
244
+ end
211
245
  end # describe child
212
246
 
213
247
  describe '#children' do
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: win_gui
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.17
5
+ version: 0.2.18
6
6
  platform: ruby
7
7
  authors:
8
8
  - arvicco
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-02-16 00:00:00 +03:00
13
+ date: 2011-02-17 00:00:00 +03:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -46,7 +46,7 @@ dependencies:
46
46
  version: 0.3.1
47
47
  type: :runtime
48
48
  version_requirements: *id003
49
- description: Abstractions/wrappers around GUI-related Win32 API functions
49
+ description: Work with Windows GUI in an object-oriented way. Abstractions/wrappers around GUI-related Win32 API functions
50
50
  email: arvitallian@gmail.com
51
51
  executables: []
52
52
 
@@ -115,7 +115,7 @@ rubyforge_project:
115
115
  rubygems_version: 1.5.0
116
116
  signing_key:
117
117
  specification_version: 3
118
- summary: Abstractions/wrappers around GUI-related Win32 API functions
118
+ summary: Work with Windows GUI in an object-oriented way
119
119
  test_files:
120
120
  - spec/extension_spec.rb
121
121
  - spec/spec_helper.rb