win_gui 0.2.17 → 0.2.18

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