win_gui 0.1.0
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +43 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/book_code/early_success/bundle.rb +34 -0
- data/book_code/early_success/english.txt +1 -0
- data/book_code/early_success/jruby_basics.rb +47 -0
- data/book_code/early_success/windows_basics.rb +97 -0
- data/book_code/guessing/locknote.rb +379 -0
- data/book_code/guessing/monkeyshines.rb +14 -0
- data/book_code/guessing/note.rb +120 -0
- data/book_code/guessing/note_spec.rb +175 -0
- data/book_code/guessing/replay.rb +21 -0
- data/book_code/guessing/seed.rb +9 -0
- data/book_code/guessing/spec_helper.rb +69 -0
- data/book_code/guessing/windows_gui.rb +247 -0
- data/book_code/home_stretch/junquenote.rb +151 -0
- data/book_code/home_stretch/locknote.rb +180 -0
- data/book_code/home_stretch/note.rb +144 -0
- data/book_code/home_stretch/note_spec.rb +191 -0
- data/book_code/home_stretch/spec_helper.rb +55 -0
- data/book_code/home_stretch/swing_gui.rb +50 -0
- data/book_code/home_stretch/windows_gui.rb +232 -0
- data/book_code/junquenote/exports.sh +10 -0
- data/book_code/junquenote/jruby_mac.sh +10 -0
- data/book_code/junquenote/junquenote_app.rb +262 -0
- data/book_code/novite/Rakefile +10 -0
- data/book_code/novite/app/controllers/application.rb +18 -0
- data/book_code/novite/app/controllers/guests_controller.rb +28 -0
- data/book_code/novite/app/controllers/parties_controller.rb +77 -0
- data/book_code/novite/app/helpers/application_helper.rb +11 -0
- data/book_code/novite/app/helpers/guests_helper.rb +10 -0
- data/book_code/novite/app/helpers/parties_helper.rb +10 -0
- data/book_code/novite/app/models/guest.rb +11 -0
- data/book_code/novite/app/models/party.rb +32 -0
- data/book_code/novite/app/models/party_mailer.rb +19 -0
- data/book_code/novite/app/views/layouts/application.rhtml +44 -0
- data/book_code/novite/app/views/parties/new.html.erb +42 -0
- data/book_code/novite/app/views/parties/show.html.erb +43 -0
- data/book_code/novite/app/views/party_mailer/invite.erb +17 -0
- data/book_code/novite/config/boot.rb +117 -0
- data/book_code/novite/config/database.yml +19 -0
- data/book_code/novite/config/environment.rb +67 -0
- data/book_code/novite/config/environments/development.rb +29 -0
- data/book_code/novite/config/environments/production.rb +27 -0
- data/book_code/novite/config/environments/test.rb +30 -0
- data/book_code/novite/config/initializers/inflections.rb +18 -0
- data/book_code/novite/config/initializers/mime_types.rb +13 -0
- data/book_code/novite/config/routes.rb +47 -0
- data/book_code/novite/db/migrate/001_create_parties.rb +26 -0
- data/book_code/novite/db/migrate/002_create_guests.rb +23 -0
- data/book_code/novite/db/schema.rb +41 -0
- data/book_code/novite/log/empty.txt +0 -0
- data/book_code/novite/public/.htaccess +40 -0
- data/book_code/novite/public/404.html +38 -0
- data/book_code/novite/public/422.html +38 -0
- data/book_code/novite/public/500.html +38 -0
- data/book_code/novite/public/dispatch.cgi +10 -0
- data/book_code/novite/public/dispatch.fcgi +24 -0
- data/book_code/novite/public/dispatch.rb +18 -0
- data/book_code/novite/public/favicon.ico +0 -0
- data/book_code/novite/public/images/rails.png +0 -0
- data/book_code/novite/public/index.html +285 -0
- data/book_code/novite/public/javascripts/application.js +10 -0
- data/book_code/novite/public/javascripts/controls.js +971 -0
- data/book_code/novite/public/javascripts/dragdrop.js +980 -0
- data/book_code/novite/public/javascripts/effects.js +1128 -0
- data/book_code/novite/public/javascripts/prototype.js +4233 -0
- data/book_code/novite/public/robots.txt +5 -0
- data/book_code/novite/script/about +3 -0
- data/book_code/novite/script/console +3 -0
- data/book_code/novite/script/destroy +3 -0
- data/book_code/novite/script/generate +3 -0
- data/book_code/novite/script/performance/benchmarker +3 -0
- data/book_code/novite/script/performance/profiler +3 -0
- data/book_code/novite/script/performance/request +3 -0
- data/book_code/novite/script/plugin +3 -0
- data/book_code/novite/script/process/inspector +3 -0
- data/book_code/novite/script/process/reaper +3 -0
- data/book_code/novite/script/process/spawner +3 -0
- data/book_code/novite/script/runner +3 -0
- data/book_code/novite/script/server +3 -0
- data/book_code/novite/test/test_helper.rb +46 -0
- data/book_code/one_more_thing/applescript.rb +68 -0
- data/book_code/one_more_thing/note_spec.rb +50 -0
- data/book_code/one_more_thing/spec_helper.rb +17 -0
- data/book_code/one_more_thing/textedit-pure.rb +28 -0
- data/book_code/one_more_thing/textedit.applescript +26 -0
- data/book_code/one_more_thing/textedit.rb +32 -0
- data/book_code/one_more_thing/textnote.rb +87 -0
- data/book_code/simplify/junquenote.rb +48 -0
- data/book_code/simplify/locknote.rb +46 -0
- data/book_code/simplify/note.rb +35 -0
- data/book_code/simplify/note_spec.rb +28 -0
- data/book_code/simplify/swing_gui.rb +45 -0
- data/book_code/simplify/windows_gui.rb +232 -0
- data/book_code/simplify/windows_gui_spec.rb +35 -0
- data/book_code/story/invite.story +19 -0
- data/book_code/story/journal.txt +29 -0
- data/book_code/story/novite_stories.rb +156 -0
- data/book_code/story/party.rb +149 -0
- data/book_code/story/password.rb +61 -0
- data/book_code/story/password.story +26 -0
- data/book_code/story/rsvp.story +29 -0
- data/book_code/tables/TestTime.html +93 -0
- data/book_code/tables/TestTimeSample.html +63 -0
- data/book_code/tables/calculate_time.rb +39 -0
- data/book_code/tables/calculator.rb +108 -0
- data/book_code/tables/calculator_actions.rb +27 -0
- data/book_code/tables/calculator_spec.rb +47 -0
- data/book_code/tables/fit.rb +32 -0
- data/book_code/tables/matrix.rb +109 -0
- data/book_code/tables/pseudocode.rb +17 -0
- data/book_code/tubes/book_selenium.rb +67 -0
- data/book_code/tubes/book_watir.rb +60 -0
- data/book_code/tubes/dragdrop.html +81 -0
- data/book_code/tubes/html_capture.rb +33 -0
- data/book_code/tubes/joke_list.rb +67 -0
- data/book_code/tubes/list_spec.rb +41 -0
- data/book_code/tubes/search_spec.rb +32 -0
- data/book_code/tubes/selenium_example.rb +66 -0
- data/book_code/tubes/selenium_link.rb +23 -0
- data/book_code/tubes/web_server.rb +14 -0
- data/book_code/windows/wgui.rb +29 -0
- data/book_code/windows/wobj.rb +25 -0
- data/book_code/windows/wsh.rb +25 -0
- data/book_code/with_rspec/empty_spec.rb +13 -0
- data/book_code/with_rspec/junquenote.rb +60 -0
- data/book_code/with_rspec/locknote.rb +129 -0
- data/book_code/with_rspec/note_spec.rb +32 -0
- data/book_code/with_rspec/should_examples.rb +18 -0
- data/exp/exp.rb +6 -0
- data/exp/exp_encodings.rb +40 -0
- data/exp/exp_enum_windows.rb +60 -0
- data/exp/exp_quik.rb +38 -0
- data/exp/exp_wsh.rb +115 -0
- data/exp/old/windows_basics.rb +80 -0
- data/exp/old/wnote.rb +80 -0
- data/exp/old/wnote_spec.rb +20 -0
- data/features/step_definitions/win_gui_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/features/win_gui.feature +9 -0
- data/lib/note/java/jemmy.jar +0 -0
- data/lib/note/java/jnote.rb +48 -0
- data/lib/note/java/jruby_basics.rb +37 -0
- data/lib/note/java/junquenote_app.rb +262 -0
- data/lib/note/java/note_spec.rb +20 -0
- data/lib/note/win/locknote.rb +19 -0
- data/lib/note.rb +15 -0
- data/lib/win_gui/constants.rb +66 -0
- data/lib/win_gui/string_extensions.rb +24 -0
- data/lib/win_gui/win_gui.rb +274 -0
- data/lib/win_gui/window.rb +70 -0
- data/lib/win_gui.rb +3 -0
- data/spec/note/win/locknote_spec.rb +7 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/test_apps/locknote/LockNote.exe +0 -0
- data/spec/win_gui/string_extensions_spec.rb +61 -0
- data/spec/win_gui/win_gui_spec.rb +733 -0
- data/spec/win_gui/window_spec.rb +124 -0
- metadata +251 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
require 'hpricot'
|
|
12
|
+
require 'open-uri'
|
|
13
|
+
|
|
14
|
+
module RandomHelper
|
|
15
|
+
def random_paragraph
|
|
16
|
+
doc = Hpricot open('http://www.lipsum.com/feed/html?amount=1')
|
|
17
|
+
(doc/"div#lipsum p").inner_html.strip #(1)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe 'a new document', :shared => true do
|
|
23
|
+
before do
|
|
24
|
+
@note = Note.open
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
after do
|
|
28
|
+
@note.exit! if @note.running?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe 'a saved document', :shared => true do
|
|
33
|
+
before do
|
|
34
|
+
Note.fixture 'SavedNote'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe 'a reopened document', :shared => true do
|
|
39
|
+
before do
|
|
40
|
+
@note = Note.open 'SavedNote'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
after do
|
|
44
|
+
@note.exit! if @note.running?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
describe 'a searchable document', :shared => true do
|
|
50
|
+
include RandomHelper #(2)
|
|
51
|
+
|
|
52
|
+
before do
|
|
53
|
+
@example = random_paragraph #(3)
|
|
54
|
+
|
|
55
|
+
words = @example.split /[^A-Za-z]+/
|
|
56
|
+
last_cap = words.select {|w| w =~ /^[A-Z]/}.last
|
|
57
|
+
@term = last_cap[0..1] #(4)
|
|
58
|
+
|
|
59
|
+
@first_match = @example.index(/#{@term}/i)
|
|
60
|
+
@second_match = @first_match ?
|
|
61
|
+
@example.index(/#{@term}/i, @first_match + 1) :
|
|
62
|
+
nil
|
|
63
|
+
@reverse_match = @example.rindex(/#{@term}/i)
|
|
64
|
+
@word_match = @example.index(/#{@term}\b/i)
|
|
65
|
+
@case_match = @example.index(/#{@term}/)
|
|
66
|
+
|
|
67
|
+
@note.text = @example
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
require 'Win32API'
|
|
10
|
+
require 'jcode'
|
|
11
|
+
$KCODE = 'U'
|
|
12
|
+
|
|
13
|
+
class String
|
|
14
|
+
def snake_case
|
|
15
|
+
gsub(/([a-z])([A-Z0-9])/, '\1_\2').downcase
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_keys
|
|
19
|
+
unless size == 1
|
|
20
|
+
raise "conversion is for single characters only"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
code = unpack('U')[0]
|
|
24
|
+
|
|
25
|
+
case self
|
|
26
|
+
when '0'..'9'
|
|
27
|
+
[code - ?0 + 0x30]
|
|
28
|
+
when 'A'..'Z'
|
|
29
|
+
[WindowsGui.const_get(:VK_SHIFT), code]
|
|
30
|
+
when 'a'..'z'
|
|
31
|
+
[code - ?a + ?A]
|
|
32
|
+
when ' '
|
|
33
|
+
[code]
|
|
34
|
+
when ','
|
|
35
|
+
[WindowsGui.const_get(:VK_OEM_COMMA)]
|
|
36
|
+
when '.'
|
|
37
|
+
[WindowsGui.const_get(:VK_OEM_PERIOD)]
|
|
38
|
+
when ':'
|
|
39
|
+
[:VK_SHIFT, :VK_OEM_1].map {|s| WindowsGui.const_get s}
|
|
40
|
+
when ';'
|
|
41
|
+
[WindowsGui.const_get(:VK_OEM_1)]
|
|
42
|
+
when "\\"
|
|
43
|
+
[WindowsGui.const_get(:VK_OEM_102)]
|
|
44
|
+
when "\n"
|
|
45
|
+
[WindowsGui.const_get(:VK_RETURN)]
|
|
46
|
+
else
|
|
47
|
+
raise "Can't convert unknown character #{self}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module WindowsGui
|
|
53
|
+
def self.def_api(function, parameters, return_value, rename = nil)
|
|
54
|
+
api = Win32API.new 'user32', function, parameters, return_value
|
|
55
|
+
define_method(rename || function.snake_case) do |*args|
|
|
56
|
+
api.call *args
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def self.load_symbols(header)
|
|
62
|
+
File.open(header) do |f|
|
|
63
|
+
f.grep(/#define\s+(ID\w+)\s+(\w+)/) do
|
|
64
|
+
name = $1
|
|
65
|
+
value = (0 == $2.to_i) ? $2.hex : $2.to_i #(1)
|
|
66
|
+
WindowsGui.const_set name, value #(2)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def_api 'FindWindow', ['P', 'P'], 'L'
|
|
73
|
+
def_api 'FindWindowEx', ['L', 'L', 'P', 'P'], 'L'
|
|
74
|
+
def_api 'SendMessage', ['L', 'L', 'L', 'P'], 'L', :send_with_buffer
|
|
75
|
+
def_api 'SendMessage', ['L', 'L', 'L', 'L'], 'L'
|
|
76
|
+
def_api 'PostMessage', ['L', 'L', 'L', 'L'], 'L'
|
|
77
|
+
def_api 'keybd_event', ['I', 'I', 'L', 'L'], 'V'
|
|
78
|
+
def_api 'GetDlgItem', ['L', 'L'], 'L'
|
|
79
|
+
def_api 'GetWindowRect', ['L', 'P'], 'I'
|
|
80
|
+
def_api 'SetCursorPos', ['L', 'L'], 'I'
|
|
81
|
+
def_api 'mouse_event', ['L', 'L', 'L', 'L', 'L'], 'V'
|
|
82
|
+
def_api 'IsWindow', ['L'], 'L'
|
|
83
|
+
def_api 'IsWindowVisible', ['L'], 'L'
|
|
84
|
+
def_api 'SetForegroundWindow', ['L'], 'L'
|
|
85
|
+
|
|
86
|
+
WM_GETTEXT = 0x000D
|
|
87
|
+
EM_GETSEL = 0x00B0
|
|
88
|
+
EM_SETSEL = 0x00B1
|
|
89
|
+
|
|
90
|
+
WM_COMMAND = 0x0111
|
|
91
|
+
WM_SYSCOMMAND = 0x0112
|
|
92
|
+
SC_CLOSE = 0xF060
|
|
93
|
+
|
|
94
|
+
IDOK = 1
|
|
95
|
+
IDCANCEL = 2
|
|
96
|
+
IDYES = 6
|
|
97
|
+
IDNO = 7
|
|
98
|
+
|
|
99
|
+
MOUSEEVENTF_LEFTDOWN = 0x0002
|
|
100
|
+
MOUSEEVENTF_LEFTUP = 0x0004
|
|
101
|
+
MOUSEEVENTF_RIGHTDOWN = 0x0008
|
|
102
|
+
MOUSEEVENTF_RIGHTUP = 0x0010
|
|
103
|
+
|
|
104
|
+
KEYEVENTF_KEYDOWN = 0
|
|
105
|
+
KEYEVENTF_KEYUP = 2
|
|
106
|
+
|
|
107
|
+
VK_SHIFT = 0x10
|
|
108
|
+
VK_CONTROL = 0x11
|
|
109
|
+
VK_MENU = 0x12
|
|
110
|
+
|
|
111
|
+
VK_BACK = 0x08
|
|
112
|
+
VK_TAB = 0x09
|
|
113
|
+
VK_RETURN = 0x0D
|
|
114
|
+
VK_ESCAPE = 0x1B
|
|
115
|
+
VK_OEM_1 = 0xBA
|
|
116
|
+
VK_OEM_102 = 0xE2
|
|
117
|
+
VK_OEM_PERIOD = 0xBE
|
|
118
|
+
VK_HOME = 0x24
|
|
119
|
+
VK_END = 0x23
|
|
120
|
+
VK_OEM_COMMA = 0xBC
|
|
121
|
+
VK_DELETE = 0x2E
|
|
122
|
+
VK_F3 = 0x72
|
|
123
|
+
|
|
124
|
+
def keystroke(*keys)
|
|
125
|
+
return if keys.empty?
|
|
126
|
+
|
|
127
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0
|
|
128
|
+
sleep 0.05
|
|
129
|
+
keystroke *keys[1..-1]
|
|
130
|
+
sleep 0.05
|
|
131
|
+
keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def type_in(message)
|
|
135
|
+
message.each_char do |char|
|
|
136
|
+
keystroke(*char.to_keys)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def wait_for_window(title, seconds = 5)
|
|
141
|
+
timeout(seconds) do
|
|
142
|
+
sleep 0.2 while
|
|
143
|
+
(h = find_window nil, title) <= 0 ||
|
|
144
|
+
window_text(h) != title
|
|
145
|
+
h
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class Window
|
|
150
|
+
include WindowsGui
|
|
151
|
+
extend WindowsGui
|
|
152
|
+
|
|
153
|
+
attr_reader :handle
|
|
154
|
+
|
|
155
|
+
def initialize(handle)
|
|
156
|
+
@handle = handle
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def close
|
|
160
|
+
post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def wait_for_close
|
|
164
|
+
timeout(5) do
|
|
165
|
+
sleep 0.2 until 0 == is_window_visible(@handle)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def text(max_length = 2048)
|
|
170
|
+
buffer = '\0' * max_length
|
|
171
|
+
length = send_with_buffer @handle, WM_GETTEXT, buffer.length, buffer
|
|
172
|
+
length == 0 ? '' : buffer[0..length - 1]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def child(id)
|
|
176
|
+
result = case id
|
|
177
|
+
when String
|
|
178
|
+
by_title = find_window_ex @handle, 0, nil, id.gsub('_', '&') #(3)
|
|
179
|
+
by_class = find_window_ex @handle, 0, id, nil
|
|
180
|
+
by_title > 0 ? by_title : by_class
|
|
181
|
+
when Fixnum
|
|
182
|
+
get_dlg_item @handle, id
|
|
183
|
+
else
|
|
184
|
+
0
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
raise "Control '#{id}' not found" if result == 0
|
|
188
|
+
Window.new result
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def click(id, which = :left, where = :center)
|
|
192
|
+
h = child(id).handle
|
|
193
|
+
|
|
194
|
+
rectangle = [0, 0, 0, 0].pack 'LLLL'
|
|
195
|
+
get_window_rect h, rectangle
|
|
196
|
+
left, top, right, bottom = rectangle.unpack 'LLLL'
|
|
197
|
+
|
|
198
|
+
point = case where
|
|
199
|
+
when Array
|
|
200
|
+
where
|
|
201
|
+
when :random
|
|
202
|
+
[left + rand(right - left), top + rand(bottom - top)]
|
|
203
|
+
else
|
|
204
|
+
point = [(left + right) / 2, (top + bottom) / 2]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
set_cursor_pos *point
|
|
208
|
+
|
|
209
|
+
down, up = (:left == which) ?
|
|
210
|
+
[MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP] :
|
|
211
|
+
[MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP]
|
|
212
|
+
|
|
213
|
+
mouse_event down, 0, 0, 0, 0
|
|
214
|
+
mouse_event up, 0, 0, 0, 0
|
|
215
|
+
|
|
216
|
+
return point
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.top_level(title, seconds=10, wnd_class = nil)
|
|
220
|
+
@handle = timeout(seconds) do
|
|
221
|
+
loop do
|
|
222
|
+
h = find_window wnd_class, title
|
|
223
|
+
break h if h > 0
|
|
224
|
+
sleep 0.3
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
Window.new @handle
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
DialogWndClass = '#32770'
|
|
233
|
+
def dialog(title, seconds=5)
|
|
234
|
+
close, dlg = begin
|
|
235
|
+
sleep 0.25
|
|
236
|
+
w = Window.top_level(title, seconds, DialogWndClass)
|
|
237
|
+
Window.set_foreground_window w.handle
|
|
238
|
+
sleep 0.25
|
|
239
|
+
|
|
240
|
+
[yield(w), w]
|
|
241
|
+
rescue TimeoutError
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
dlg.wait_for_close if dlg && close
|
|
245
|
+
return dlg
|
|
246
|
+
end
|
|
247
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
require 'swing_gui'
|
|
10
|
+
require 'junquenote_app'
|
|
11
|
+
require 'note'
|
|
12
|
+
|
|
13
|
+
class JunqueNote < Note
|
|
14
|
+
@@app = JunqueNote
|
|
15
|
+
@@titles =
|
|
16
|
+
{
|
|
17
|
+
:file => "Input",
|
|
18
|
+
:exit => "Quittin' time",
|
|
19
|
+
:about => "About JunqueNote",
|
|
20
|
+
:about_menu => "About JunqueNote..."
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
include SwingGui
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def initialize(name = nil, with_options = {})
|
|
27
|
+
options = DefaultOptions.merge(with_options)
|
|
28
|
+
|
|
29
|
+
@prompted = {}
|
|
30
|
+
@path = JunqueNote.path_to(name) if name
|
|
31
|
+
|
|
32
|
+
@program = JunqueNoteApp.new
|
|
33
|
+
@main_window = JFrameOperator.new 'JunqueNote'
|
|
34
|
+
@edit_window = JTextAreaOperator.new @main_window
|
|
35
|
+
@menu_bar = JMenuBarOperator.new @main_window
|
|
36
|
+
|
|
37
|
+
if name
|
|
38
|
+
menu 'File', 'Open...'
|
|
39
|
+
enter_filename @path, '_Open'
|
|
40
|
+
unlock_password options
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if @prompted[:with_error] || options[:cancel_password]
|
|
44
|
+
@program = nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def text
|
|
50
|
+
@edit_window.text
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def text=(message)
|
|
55
|
+
@edit_window.select_all
|
|
56
|
+
@edit_window.type_text message
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def selection
|
|
62
|
+
first = @edit_window.get_selection_start
|
|
63
|
+
last = @edit_window.get_selection_end
|
|
64
|
+
Range.new(first, last - 1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def go_to(where)
|
|
70
|
+
case where
|
|
71
|
+
when :beginning
|
|
72
|
+
@edit_window.set_caret_position 0
|
|
73
|
+
when :end
|
|
74
|
+
@edit_window.set_caret_position text.length
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def find(term, with_options={})
|
|
81
|
+
command = 'Find...'
|
|
82
|
+
command = 'Find Exact Case...' if with_options[:exact_case]
|
|
83
|
+
command = 'Reverse ' + command if :back == with_options[:direction]
|
|
84
|
+
|
|
85
|
+
menu 'Edit', command
|
|
86
|
+
|
|
87
|
+
dialog('Input') do |d|
|
|
88
|
+
d.type_in term
|
|
89
|
+
d.click 'OK'
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def running?
|
|
96
|
+
@main_window && @main_window.visible
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def menu(name, item, wait = false)
|
|
102
|
+
action = wait ? :push_menu : :push_menu_no_block
|
|
103
|
+
@menu_bar.send action, name + '|' + item, '|'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def select_all
|
|
108
|
+
@edit_window.select_all
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.path_to(name)
|
|
112
|
+
name + '.junque'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def enter_password(with_options = {})
|
|
119
|
+
options = DefaultOptions.merge with_options
|
|
120
|
+
|
|
121
|
+
@prompted[:for_password] = single_password_entry \
|
|
122
|
+
options[:password], options[:cancel_password]
|
|
123
|
+
|
|
124
|
+
confirmation =
|
|
125
|
+
options[:confirmation] == true ?
|
|
126
|
+
options[:password] :
|
|
127
|
+
options[:confirmation]
|
|
128
|
+
|
|
129
|
+
if @prompted[:for_password] && confirmation
|
|
130
|
+
single_password_entry confirmation, false
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def single_password_entry(password, cancel)
|
|
135
|
+
dialog('Input') do |d|
|
|
136
|
+
d.type_in password
|
|
137
|
+
d.click(cancel ? 'Cancel' : 'OK')
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def watch_for_error
|
|
144
|
+
if @prompted[:for_password]
|
|
145
|
+
@prompted[:with_error] = dialog('Oops') do |d|
|
|
146
|
+
d.click 'OK'
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#---
|
|
2
|
+
# Excerpted from "Scripted GUI Testing With Ruby",
|
|
3
|
+
# published by The Pragmatic Bookshelf.
|
|
4
|
+
# Copyrights apply to this code. It may not be used to create training material,
|
|
5
|
+
# courses, books, articles, and the like. Contact us if you are in doubt.
|
|
6
|
+
# We make no guarantees that this code is fit for any purpose.
|
|
7
|
+
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
|
|
8
|
+
#---
|
|
9
|
+
require 'windows_gui'
|
|
10
|
+
require 'note'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LockNote < Note
|
|
14
|
+
include WindowsGui
|
|
15
|
+
|
|
16
|
+
@@app = LockNote
|
|
17
|
+
@@titles =
|
|
18
|
+
{
|
|
19
|
+
:file => 'Save As',
|
|
20
|
+
:exit => 'Steganos LockNote',
|
|
21
|
+
:about => 'About Steganos LockNote...',
|
|
22
|
+
:about_menu => 'About',
|
|
23
|
+
:dialog => 'Steganos LockNote'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
BasePath = "C:\\LockNote"
|
|
28
|
+
WindowsGui.load_symbols "#{BasePath}\\src\\resource.h"
|
|
29
|
+
WindowsGui.load_symbols "#{BasePath}\\src\\atlres.h"
|
|
30
|
+
ID_HELP_ABOUT = ID_APP_ABOUT
|
|
31
|
+
ID_FILE_EXIT = ID_APP_EXIT
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def initialize(name = 'LockNote', with_options = {})
|
|
36
|
+
options = DefaultOptions.merge(with_options)
|
|
37
|
+
|
|
38
|
+
@prompted = {}
|
|
39
|
+
@path = LockNote.path_to(name)
|
|
40
|
+
|
|
41
|
+
system 'start "" "' + @path + '"'
|
|
42
|
+
unlock_password options
|
|
43
|
+
|
|
44
|
+
if @prompted[:with_error] || options[:cancel_password]
|
|
45
|
+
@main_window = Window.new 0
|
|
46
|
+
sleep 1.0
|
|
47
|
+
else
|
|
48
|
+
@main_window = Window.top_level "#{name} - Steganos LockNote"
|
|
49
|
+
@edit_window = @main_window.child "ATL:00434310"
|
|
50
|
+
|
|
51
|
+
set_foreground_window @main_window.handle
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def select_all
|
|
57
|
+
keystroke VK_CONTROL, ?A
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def text
|
|
61
|
+
@edit_window.text
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def text=(new_text)
|
|
66
|
+
select_all
|
|
67
|
+
keystroke VK_BACK
|
|
68
|
+
type_in new_text
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def selection
|
|
74
|
+
result = send_message @edit_window.handle, EM_GETSEL, 0, 0
|
|
75
|
+
bounds = [result].pack('L').unpack('SS')
|
|
76
|
+
bounds[0]...bounds[1] #(1)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def go_to(where)
|
|
82
|
+
case where
|
|
83
|
+
when :beginning
|
|
84
|
+
keystroke VK_CONTROL, VK_HOME
|
|
85
|
+
when :end
|
|
86
|
+
keystroke VK_CONTROL, VK_END
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
WholeWord = 0x0410
|
|
93
|
+
ExactCase = 0x0411
|
|
94
|
+
SearchUp = 0x0420
|
|
95
|
+
|
|
96
|
+
def find(term, with_options={})
|
|
97
|
+
menu 'Edit', 'Find...'
|
|
98
|
+
|
|
99
|
+
appeared = dialog('Find') do |d|
|
|
100
|
+
type_in term
|
|
101
|
+
|
|
102
|
+
d.click WholeWord if with_options[:whole_word]
|
|
103
|
+
d.click ExactCase if with_options[:exact_case]
|
|
104
|
+
d.click SearchUp if :back == with_options[:direction]
|
|
105
|
+
|
|
106
|
+
d.click IDOK
|
|
107
|
+
d.click IDCANCEL
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
raise 'Find dialog did not appear' unless appeared
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def running?
|
|
116
|
+
@main_window.handle != 0 && is_window(@main_window.handle) != 0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def menu(name, item, wait = false)
|
|
122
|
+
multiple_words = /[.]/
|
|
123
|
+
single_word = /[ .]/
|
|
124
|
+
|
|
125
|
+
[multiple_words, single_word].each do |pattern|
|
|
126
|
+
words = item.gsub(pattern, '').split
|
|
127
|
+
const_name = ['ID', name, *words].join('_').upcase
|
|
128
|
+
msg = WM_COMMAND
|
|
129
|
+
|
|
130
|
+
begin
|
|
131
|
+
id = LockNote.const_get const_name
|
|
132
|
+
action = wait ? :send_message : :post_message
|
|
133
|
+
|
|
134
|
+
return send(action, @main_window.handle, msg, id, 0)
|
|
135
|
+
rescue NameError
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def self.path_to(name)
|
|
142
|
+
"#{BasePath}\\#{name}.exe"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def enter_password(with_options = {})
|
|
149
|
+
options = DefaultOptions.merge with_options
|
|
150
|
+
|
|
151
|
+
@prompted[:for_password] = dialog(@@titles[:dialog]) do |d|
|
|
152
|
+
type_in options[:password]
|
|
153
|
+
|
|
154
|
+
confirmation =
|
|
155
|
+
options[:confirmation] == true ?
|
|
156
|
+
options[:password] :
|
|
157
|
+
options[:confirmation]
|
|
158
|
+
|
|
159
|
+
if confirmation
|
|
160
|
+
keystroke VK_TAB
|
|
161
|
+
type_in confirmation
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
d.click options[:cancel_password] ? IDCANCEL : IDOK
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
ErrorIcon = 0x0014
|
|
171
|
+
|
|
172
|
+
def watch_for_error
|
|
173
|
+
if @prompted[:for_password]
|
|
174
|
+
@prompted[:with_error] = dialog(@@titles[:dialog]) do |d|
|
|
175
|
+
d.click IDCANCEL if get_dlg_item(d.handle, ErrorIcon) > 0 #(2)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
end
|