win 0.3.17 → 0.3.24
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 +28 -0
- data/README.rdoc +5 -3
- data/VERSION +1 -1
- data/lib/win/dde.rb +138 -138
- data/lib/win/error.rb +26 -26
- data/lib/win/gui.rb +2 -0
- data/lib/win/gui/dialog.rb +9 -9
- data/lib/win/gui/input.rb +8 -8
- data/lib/win/gui/menu.rb +740 -0
- data/lib/win/gui/message.rb +44 -44
- data/lib/win/gui/window.rb +8 -8
- data/lib/win/library.rb +6 -6
- data/spec/spec_helper.rb +25 -3
- data/spec/win/gui/dialog_spec.rb +0 -16
- data/spec/win/gui/input_spec.rb +6 -10
- data/spec/win/gui/menu_spec.rb +291 -0
- data/spec/win/gui/window_spec.rb +8 -8
- data/spec/win/library_spec.rb +7 -14
- metadata +6 -5
- data/spec/test_apps/locknote/LockNote.exe +0 -0
data/lib/win/gui/message.rb
CHANGED
@@ -7,7 +7,7 @@ module Win
|
|
7
7
|
# Below is a table of system-defined message prefixes:
|
8
8
|
#
|
9
9
|
# *Prefix*:: *Message* *category*
|
10
|
-
# ABM::
|
10
|
+
# ABM:: App desktop toolbar
|
11
11
|
# BM:: Button control
|
12
12
|
# CB:: Combo box control
|
13
13
|
# CBEM:: Extended combo box control
|
@@ -157,7 +157,7 @@ module Win
|
|
157
157
|
WM_IME_KEYLAST = 0x010F
|
158
158
|
WM_INITDIALOG = 0x0110
|
159
159
|
WM_COMMAND = 0x0111
|
160
|
-
# Windows Message
|
160
|
+
# Windows Message System Command (emitted by Window/System menu)
|
161
161
|
WM_SYSCOMMAND = 0x0112
|
162
162
|
WM_TIMER = 0x0113
|
163
163
|
WM_HSCROLL = 0x0114
|
@@ -337,22 +337,22 @@ module Win
|
|
337
337
|
|
338
338
|
# The MSG structure contains message information from a thread's message queue.
|
339
339
|
#
|
340
|
-
#
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
344
|
-
#
|
345
|
-
#
|
346
|
-
#
|
347
|
-
#
|
348
|
-
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
355
|
-
#
|
340
|
+
# typedef struct {
|
341
|
+
# HWND hwnd;
|
342
|
+
# UINT message;
|
343
|
+
# WPARAM wParam;
|
344
|
+
# LPARAM lParam;
|
345
|
+
# DWORD time;
|
346
|
+
# POINT pt;
|
347
|
+
# } MSG, *PMSG;
|
348
|
+
#
|
349
|
+
# hwnd:: Handle to the window whose window procedure receives the message. NULL when the message is a thread message.
|
350
|
+
# message:: Message identifier. Apps can only use the low word; the high word is reserved by the system.
|
351
|
+
# wParam:: Additional info about the message. Exact meaning depends on the value of the message member.
|
352
|
+
# lParam:: Additional info about the message. Exact meaning depends on the value of the message member.
|
353
|
+
# time:: Specifies the time at which the message was posted.
|
354
|
+
# pt:: POINT structure - the cursor position, in screen coordinates, when the message was posted.
|
355
|
+
# (in my definition, it is changed to two longs: x, y - has the same effect, just avoid nested structs)
|
356
356
|
class Msg < FFI::Struct
|
357
357
|
layout :hwnd, :ulong,
|
358
358
|
:message, :uint,
|
@@ -364,9 +364,9 @@ module Win
|
|
364
364
|
end
|
365
365
|
|
366
366
|
##
|
367
|
-
# The SendAsyncProc function is an
|
367
|
+
# The SendAsyncProc function is an App-defined callback function used with the SendMessageCallback
|
368
368
|
# function. The system passes the message to the callback function after passing the message to the
|
369
|
-
# destination window procedure. SendAsyncProc is a placeholder for the
|
369
|
+
# destination window procedure. SendAsyncProc is a placeholder for the App-defined function name.
|
370
370
|
#
|
371
371
|
# [*Syntax*] VOID SendAsyncProc( HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult );
|
372
372
|
#
|
@@ -374,7 +374,7 @@ module Win
|
|
374
374
|
# function was called with its hwnd parameter set to HWND_BROADCAST, the system calls the
|
375
375
|
# SendAsyncProc function once for each top-level window.
|
376
376
|
# uMsg:: <in> Specifies the message.
|
377
|
-
# dwData:: <in> Specifies an
|
377
|
+
# dwData:: <in> Specifies an App-defined value sent from the SendMessageCallback function.
|
378
378
|
# lResult:: <in> Specifies the result of the message processing. This value depends on the message.
|
379
379
|
#
|
380
380
|
# :call-seq:
|
@@ -386,7 +386,7 @@ module Win
|
|
386
386
|
# The SendMessageCallback function sends the specified message to a window or windows. It calls the window
|
387
387
|
# procedure for the specified window and returns immediately. After the window procedure processes the message,
|
388
388
|
# the system calls the specified callback function, passing the result of the message processing and an
|
389
|
-
#
|
389
|
+
# App-defined value to the callback function.
|
390
390
|
#
|
391
391
|
# [*Syntax*] BOOL SendMessageCallback( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam,
|
392
392
|
# SENDASYNCPROC lpCallBack, ULONG_PTR dwData);
|
@@ -400,7 +400,7 @@ module Win
|
|
400
400
|
# lpCallBack:: <in> Pointer to a callback function that the system calls after the window procedure processes
|
401
401
|
# the message. For more information, see SendAsyncProc. If hWnd is HWND_BROADCAST, the system calls
|
402
402
|
# SendAsyncProc callback function once for each top-level window.
|
403
|
-
# dwData:: <in> Specifies an
|
403
|
+
# dwData:: <in> Specifies an App-defined value to be sent to the callback function pointed to by
|
404
404
|
# the lpCallBack parameter.
|
405
405
|
# *Returns*:: Nonzero if the function succeeds, zero if it fails. For extended error info, call GetLastError.
|
406
406
|
# ---
|
@@ -409,8 +409,8 @@ module Win
|
|
409
409
|
# SendNotifyMessage, and SendMessageCallback), its message parameters cannot include pointers. Otherwise,
|
410
410
|
# the operation will fail. The functions will return before the receiving thread has had a chance
|
411
411
|
# to process the message and the sender will free the memory before it is used.
|
412
|
-
# -
|
413
|
-
# to obtain a unique message for inter-
|
412
|
+
# - Apps that need to communicate using HWND_BROADCAST should use the RegisterWindowMessage function
|
413
|
+
# to obtain a unique message for inter-App communication.
|
414
414
|
# - The system only does marshalling for system messages (those in the range 0 to (WM_USER-1)). To send
|
415
415
|
# messages (>= WM_USER) to another process, you must do custom marshalling.
|
416
416
|
# - The callback function is called only when the thread that called SendMessageCallback also calls GetMessage,
|
@@ -448,8 +448,8 @@ module Win
|
|
448
448
|
# - Microsoft Windows Vista and later. When a message is blocked by UIPI the last error, retrieved with
|
449
449
|
# GetLastError, is set to 5 (access denied). Messages in a message queue are retrieved by calls to the
|
450
450
|
# GetMessage or PeekMessage function.
|
451
|
-
# -
|
452
|
-
# to obtain a unique message for inter-
|
451
|
+
# - Apps that need to communicate using HWND_BROADCAST should use the RegisterWindowMessage function
|
452
|
+
# to obtain a unique message for inter-App communication.
|
453
453
|
# - The system only does marshalling for system messages (those in the range 0 to (WM_USER-1)). To send other
|
454
454
|
# messages (those >= WM_USER) to another process, you must do custom marshalling.
|
455
455
|
# - If you send a message in the range below WM_USER to the asynchronous message functions (PostMessage,
|
@@ -492,8 +492,8 @@ module Win
|
|
492
492
|
# *Remarks*:
|
493
493
|
# - Microsoft Windows Vista and later. When a message is blocked by UIPI the last error, retrieved with
|
494
494
|
# GetLastError, is set to 5 (access denied).
|
495
|
-
# -
|
496
|
-
# to obtain a unique message for inter-
|
495
|
+
# - Apps that need to communicate using HWND_BROADCAST should use the RegisterWindowMessage function
|
496
|
+
# to obtain a unique message for inter-App communication.
|
497
497
|
# - The system only does marshalling for system messages (those in the range 0 to (WM_USER-1)). To send other
|
498
498
|
# messages (those >= WM_USER) to another process, you must do custom marshalling.
|
499
499
|
# - If the specified window was created by the calling thread, the window procedure is called immediately as
|
@@ -549,7 +549,7 @@ module Win
|
|
549
549
|
# *Warning*
|
550
550
|
# Because the return value can be nonzero, zero, or -1, avoid code like this:
|
551
551
|
# while (GetMessage( lpMsg, hWnd, 0, 0)) ...
|
552
|
-
# The possibility of a -1 return value means that such code can lead to fatal
|
552
|
+
# The possibility of a -1 return value means that such code can lead to fatal App errors.
|
553
553
|
# Instead, use code like this:
|
554
554
|
# while( (bRet = GetMessage( msg, hWnd, 0, 0 )) != 0)
|
555
555
|
# if (bRet == -1)
|
@@ -561,12 +561,12 @@ module Win
|
|
561
561
|
# end
|
562
562
|
# ---
|
563
563
|
# *Remarks*:
|
564
|
-
# An
|
564
|
+
# An App typically uses the return value to determine whether to end the main message loop and
|
565
565
|
# exit the program.
|
566
566
|
#
|
567
567
|
# The GetMessage function retrieves messages associated with the window identified by the hWnd parameter
|
568
568
|
# or any of its children, as specified by the IsChild function, and within the range of message values
|
569
|
-
# given by the wMsgFilterMin and wMsgFilterMax parameters. Note that an
|
569
|
+
# given by the wMsgFilterMin and wMsgFilterMax parameters. Note that an App can only use the low
|
570
570
|
# word in the wMsgFilterMin and wMsgFilterMax parameters; the high word is reserved for the system.
|
571
571
|
# Note that GetMessage always retrieves WM_QUIT messages, no matter which values you specify for
|
572
572
|
# wMsgFilterMin and wMsgFilterMax.
|
@@ -590,7 +590,7 @@ module Win
|
|
590
590
|
# Windows XP: If a top-level window stops responding to messages for more than several seconds, the
|
591
591
|
# system considers the window to be not responding and replaces it with a ghost window that has the same
|
592
592
|
# z-order, location, size, and visual attributes. This allows the user to move it, resize it, or even
|
593
|
-
# close the
|
593
|
+
# close the App. However, these are the only actions available because the App is
|
594
594
|
# actually not responding. When in the debugger mode, the system does not generate a ghost window.
|
595
595
|
# ---
|
596
596
|
# <b>Enhanced (snake_case) API: makes all args optional, returns: *false* if WM_QUIT was posted,
|
@@ -650,7 +650,7 @@ module Win
|
|
650
650
|
# *Remarks*:
|
651
651
|
# PeekMessage retrieves messages associated with the window identified by the hWnd parameter or any of
|
652
652
|
# its children as specified by the IsChild function, and within the range of message values given by the
|
653
|
-
# wMsgFilterMin and wMsgFilterMax parameters. Note that an
|
653
|
+
# wMsgFilterMin and wMsgFilterMax parameters. Note that an App can only use the low word in the
|
654
654
|
# wMsgFilterMin and wMsgFilterMax parameters; the high word is reserved for the system.
|
655
655
|
#
|
656
656
|
# Note that PeekMessage always retrieves WM_QUIT messages, no matter which values you specify for
|
@@ -677,8 +677,8 @@ module Win
|
|
677
677
|
# Windows XP: If a top-level window stops responding to messages for more than several seconds, the
|
678
678
|
# system considers the window to be not responding and replaces it with a ghost window that has the same
|
679
679
|
# z-order, location, size, and visual attributes. This allows the user to move it, resize it, or even
|
680
|
-
# close the
|
681
|
-
# actually not responding. When an
|
680
|
+
# close the App. However, these are the only actions available because the App is
|
681
|
+
# actually not responding. When an App is being debugged, the system does not generate a ghost
|
682
682
|
# window.
|
683
683
|
# ---
|
684
684
|
# <b>Enhanced (snake_case) API: makes all args optional, returns *nil* if no message in queue,
|
@@ -718,12 +718,12 @@ module Win
|
|
718
718
|
# TranslateMessage produces WM_CHAR messages only for keys that are mapped to ASCII characters by the
|
719
719
|
# keyboard driver.
|
720
720
|
#
|
721
|
-
# If
|
722
|
-
# TranslateMessage. For instance, an
|
723
|
-
# TranslateAccelerator function returns a nonzero value. Note that the
|
724
|
-
# retrieving and dispatching input messages to the dialog box. Most
|
721
|
+
# If Apps process virtual-key messages for some other purpose, they should not call
|
722
|
+
# TranslateMessage. For instance, an App should not call TranslateMessage if the
|
723
|
+
# TranslateAccelerator function returns a nonzero value. Note that the App is responsible for
|
724
|
+
# retrieving and dispatching input messages to the dialog box. Most Apps use the main message
|
725
725
|
# loop for this. However, to permit the user to move to and to select controls by using the keyboard,
|
726
|
-
# the
|
726
|
+
# the App must call IsDialogMessage. For more information, see Dialog Box Keyboard Interface.
|
727
727
|
# ---
|
728
728
|
# <b>Enhanced (snake_case) API: returns true/false instead of nonzero/zero</b>
|
729
729
|
#
|
@@ -748,9 +748,9 @@ module Win
|
|
748
748
|
# message and the lParam parameter of the WM_TIMER message is not NULL, lParam points to a function that
|
749
749
|
# is called instead of the window procedure.
|
750
750
|
#
|
751
|
-
# Note that the
|
752
|
-
# box. Most
|
753
|
-
# to select controls by using the keyboard, the
|
751
|
+
# Note that the App is responsible for retrieving and dispatching input messages to the dialog
|
752
|
+
# box. Most Apps use the main message loop for this. However, to permit the user to move to and
|
753
|
+
# to select controls by using the keyboard, the App must call IsDialogMessage. For more
|
754
754
|
# information, see Dialog Box Keyboard Interface.
|
755
755
|
# ---
|
756
756
|
# <b>Enhanced (snake_case) API: </b>
|
data/lib/win/gui/window.rb
CHANGED
@@ -229,7 +229,7 @@ module Win
|
|
229
229
|
# :call-seq:
|
230
230
|
# win_handle = find_window( class_name, win_name )
|
231
231
|
#
|
232
|
-
function :FindWindow, [:pointer, :pointer], :HWND,
|
232
|
+
function :FindWindow, [:pointer, :pointer], :HWND, fails: 0
|
233
233
|
|
234
234
|
##
|
235
235
|
# Unicode version of FindWindow (strings must be encoded as utf-16LE AND terminate with "\x00\x00")
|
@@ -237,7 +237,7 @@ module Win
|
|
237
237
|
# :call-seq:
|
238
238
|
# win_handle = find_window_w( class_name, win_name )
|
239
239
|
#
|
240
|
-
function :FindWindowW, [:pointer, :pointer], :HWND,
|
240
|
+
function :FindWindowW, [:pointer, :pointer], :HWND, fails: 0
|
241
241
|
|
242
242
|
##
|
243
243
|
# The FindWindowEx function retrieves a handle to a window whose class name and window name match the specified
|
@@ -284,7 +284,7 @@ module Win
|
|
284
284
|
# :call-seq:
|
285
285
|
# win_handle = find_window_ex( win_handle, after_child, class_name, win_name )
|
286
286
|
#
|
287
|
-
function :FindWindowEx, [:HWND, :HWND, :pointer, :pointer], :HWND,
|
287
|
+
function :FindWindowEx, [:HWND, :HWND, :pointer, :pointer], :HWND, fails: 0
|
288
288
|
|
289
289
|
##
|
290
290
|
# GetWindowText returns the text of the specified window's title bar (if it has one).
|
@@ -632,7 +632,7 @@ module Win
|
|
632
632
|
#:call-seq:
|
633
633
|
# win_handle = [get_]foreground_window()
|
634
634
|
#
|
635
|
-
function :GetForegroundWindow, [], :HWND,
|
635
|
+
function :GetForegroundWindow, [], :HWND, fails: 0
|
636
636
|
|
637
637
|
##
|
638
638
|
# SetForegroundWindow function puts the thread that created the specified window into the foreground
|
@@ -705,7 +705,7 @@ module Win
|
|
705
705
|
#:call-seq:
|
706
706
|
# win_handle = [get_]active_window()
|
707
707
|
#
|
708
|
-
function :GetActiveWindow, [], :HWND,
|
708
|
+
function :GetActiveWindow, [], :HWND, fails: 0
|
709
709
|
|
710
710
|
##
|
711
711
|
# The GetWindow function retrieves a handle to a window that has the specified relationship (Z-Order or
|
@@ -762,7 +762,7 @@ module Win
|
|
762
762
|
# :call-seq:
|
763
763
|
# window_handle = get_window(h_wnd, u_cmd)
|
764
764
|
#
|
765
|
-
function :GetWindow, [:HWND, :UINT], :HWND,
|
765
|
+
function :GetWindow, [:HWND, :UINT], :HWND, fails: 0
|
766
766
|
|
767
767
|
##
|
768
768
|
# The GetParent function retrieves a handle to the specified window's parent OR OWNER.
|
@@ -788,7 +788,7 @@ module Win
|
|
788
788
|
# :call-seq:
|
789
789
|
# parent = get_parent(h_wnd)
|
790
790
|
#
|
791
|
-
function :GetParent, [:HWND], :HWND,
|
791
|
+
function :GetParent, [:HWND], :HWND, fails: 0
|
792
792
|
|
793
793
|
##
|
794
794
|
# The GetAncestor function retrieves the handle to the ancestor of the specified window.
|
@@ -810,7 +810,7 @@ module Win
|
|
810
810
|
# :call-seq:
|
811
811
|
# ancestor = get_ancestor(hwnd, ga_flags)
|
812
812
|
#
|
813
|
-
function :GetAncestor, [:HWND, :UINT], :HWND,
|
813
|
+
function :GetAncestor, [:HWND, :UINT], :HWND, fails: 0
|
814
814
|
|
815
815
|
|
816
816
|
# Convenience wrapper methods:
|
data/lib/win/library.rb
CHANGED
@@ -281,7 +281,7 @@ module Win
|
|
281
281
|
# :camel_only:: If true, no snake_case method is defined
|
282
282
|
# :alias(es):: Provides additional alias(es) for defined method
|
283
283
|
# :boolean:: Forces method to return true/false instead of nonzero/zero
|
284
|
-
# :
|
284
|
+
# :fails:: Forces method to return nil if function result is equal to following error code
|
285
285
|
#
|
286
286
|
def function(name, params, returns, options={}, &def_block)
|
287
287
|
snake_name, camel_name, effective_names, aliases = generate_names(name, options)
|
@@ -394,20 +394,20 @@ module Win
|
|
394
394
|
end
|
395
395
|
|
396
396
|
# Generates body for snake_case method according to directives contained in options
|
397
|
-
# options (:boolean, :
|
397
|
+
# options (:boolean, :fails) currently supported
|
398
398
|
#
|
399
399
|
def generate_snake_method_body(api, options, &def_block)
|
400
400
|
if def_block
|
401
|
-
if options[:
|
402
|
-
->(*args, &block){ (res = def_block.(api, *args, &block))
|
401
|
+
if options[:fails]
|
402
|
+
->(*args, &block){ (res = def_block.(api, *args, &block)) == options[:fails] ? nil: res }
|
403
403
|
elsif options[:boolean]
|
404
404
|
->(*args, &block){ def_block.(api, *args, &block) != 0 }
|
405
405
|
else
|
406
406
|
->(*args, &block){ def_block.(api, *args, &block) }
|
407
407
|
end
|
408
408
|
else
|
409
|
-
if options[:
|
410
|
-
->(*args, &block){ (res = block ? block[api.call(*args)] : api.call(*args))
|
409
|
+
if options[:fails]
|
410
|
+
->(*args, &block){ (res = block ? block[api.call(*args)] : api.call(*args)) == options[:fails] ? nil : res }
|
411
411
|
elsif options[:boolean]
|
412
412
|
->(*args, &block){ block ? block[api.call(*args)] : api.call(*args) != 0 }
|
413
413
|
else
|
data/spec/spec_helper.rb
CHANGED
@@ -83,7 +83,7 @@ module WinTest
|
|
83
83
|
IMPOSSIBLE = 'Impossible'
|
84
84
|
CONVERSION_ERROR = /Can.t convert/
|
85
85
|
SLEEP_DELAY = 0.02
|
86
|
-
APP_PATH = File.join(File.dirname(__FILE__), "
|
86
|
+
APP_PATH = File.join(File.dirname(__FILE__), "../misc/locknote/LockNote.exe" )
|
87
87
|
APP_START = cygwin? ? "cmd /c start `cygpath -w #{APP_PATH}`" : 'start "" "' + APP_PATH + '"'
|
88
88
|
WIN_TITLE = 'LockNote - Steganos LockNote'
|
89
89
|
WIN_RECT = [710, 400, 1210, 800]
|
@@ -123,7 +123,9 @@ module WinTestApp
|
|
123
123
|
textarea = find_window_ex(handle, 0, TEXTAREA_CLASS, nil)
|
124
124
|
app = "Locknote" # App identifier
|
125
125
|
|
126
|
-
eigen_class = class << app;
|
126
|
+
eigen_class = class << app;
|
127
|
+
self;
|
128
|
+
end # Extracting app's eigenclass
|
127
129
|
eigen_class.class_eval do # Defining singleton methods on app
|
128
130
|
define_method(:handle) {handle}
|
129
131
|
define_method(:textarea) {textarea}
|
@@ -136,7 +138,10 @@ module WinTestApp
|
|
136
138
|
while @launched_test_app && find_window(nil, WIN_TITLE)
|
137
139
|
shut_window @launched_test_app.handle
|
138
140
|
sleep SLEEP_DELAY
|
139
|
-
|
141
|
+
if dialog = find_window(nil, "Steganos Locknote") # Dealing with closing modal dialog
|
142
|
+
set_foreground_window(dialog)
|
143
|
+
keystroke('N')
|
144
|
+
end
|
140
145
|
end
|
141
146
|
@launched_test_app = nil
|
142
147
|
end
|
@@ -148,6 +153,23 @@ module WinTestApp
|
|
148
153
|
close_test_app
|
149
154
|
end
|
150
155
|
|
156
|
+
def test_app_with_dialog(type=:close)
|
157
|
+
test_app do |app|
|
158
|
+
case type
|
159
|
+
when :close
|
160
|
+
keystroke('A')
|
161
|
+
shut_window app.handle
|
162
|
+
sleep 0.01 until dialog = find_window(nil, "Steganos Locknote")
|
163
|
+
when :save
|
164
|
+
keystroke(VK_ALT, 'F', 'A')
|
165
|
+
sleep 0.01 until dialog = find_window(nil, "Save As")
|
166
|
+
end
|
167
|
+
yield app, dialog
|
168
|
+
set_foreground_window(dialog)
|
169
|
+
keystroke(VK_ESCAPE)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
151
173
|
# Emulates combinations of (any amount of) keys pressed one after another (Ctrl+Alt+P) and then released
|
152
174
|
# *keys should be a sequence of a virtual-key codes. These codes must be a value in the range 1 to 254.
|
153
175
|
# For a complete list, see msdn:Virtual Key Codes.
|
data/spec/win/gui/dialog_spec.rb
CHANGED
@@ -6,22 +6,6 @@ module WinGuiDialogTest
|
|
6
6
|
include WinTestApp
|
7
7
|
include Win::Gui::Dialog
|
8
8
|
|
9
|
-
def test_app_with_dialog(type=:close)
|
10
|
-
test_app do |app|
|
11
|
-
case type
|
12
|
-
when :close
|
13
|
-
keystroke('A')
|
14
|
-
shut_window app.handle
|
15
|
-
sleep 0.01 until dialog = find_window(nil, "Steganos Locknote")
|
16
|
-
when :save
|
17
|
-
keystroke(VK_ALT, 'F', 'A')
|
18
|
-
sleep 0.01 until dialog = find_window(nil, "Save As")
|
19
|
-
end
|
20
|
-
yield app, dialog
|
21
|
-
keystroke(VK_ESCAPE)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
9
|
describe Win::Gui::Dialog do
|
26
10
|
|
27
11
|
describe "#message_box" do
|
data/spec/win/gui/input_spec.rb
CHANGED
@@ -6,22 +6,18 @@ module WinGuiInputTest
|
|
6
6
|
include WinTestApp
|
7
7
|
include Win::Gui::Input
|
8
8
|
|
9
|
+
# rolling back changes with Ctrl-Z to allow window closing without dialog!
|
10
|
+
def rollback_changes(num_changes)
|
11
|
+
num_changes.times {keystroke(VK_CONTROL, 'Z'.ord)}
|
12
|
+
end
|
13
|
+
|
9
14
|
describe Win::Gui::Input, ' defines a set of API functions related to user input' do
|
10
15
|
|
11
16
|
describe '#keydb_event' do
|
12
17
|
spec{ use{ keybd_event(vkey = 0, bscan = 0, flags = 0, extra_info = 0) }}
|
13
18
|
before(:each){ (@app=launch_test_app)}
|
14
19
|
after(:each) do
|
15
|
-
3
|
16
|
-
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYDOWN, 0)
|
17
|
-
sleep KEY_DELAY
|
18
|
-
keybd_event('Z'.ord, 0, KEYEVENTF_KEYDOWN, 0)
|
19
|
-
sleep KEY_DELAY
|
20
|
-
keybd_event('Z'.ord, 0, KEYEVENTF_KEYUP, 0)
|
21
|
-
sleep KEY_DELAY
|
22
|
-
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0)
|
23
|
-
sleep KEY_DELAY
|
24
|
-
end
|
20
|
+
rollback_changes(3)
|
25
21
|
close_test_app
|
26
22
|
end
|
27
23
|
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
require 'win/gui/menu'
|
3
|
+
|
4
|
+
module WinGuiWindowTest
|
5
|
+
|
6
|
+
include WinTestApp
|
7
|
+
include Win::Gui::Window
|
8
|
+
|
9
|
+
describe Win::Gui::Menu, ' defines a set of API functions related to menus' do
|
10
|
+
context 'non-destructive methods' do
|
11
|
+
before(:all)do
|
12
|
+
@app = launch_test_app
|
13
|
+
@menu = get_menu(@app.handle)
|
14
|
+
@file_menu = get_sub_menu(@menu, 0)
|
15
|
+
end
|
16
|
+
after(:all){ close_test_app if @launched_test_app }
|
17
|
+
|
18
|
+
describe "#get_menu" do
|
19
|
+
|
20
|
+
spec{ use{ menu = GetMenu(@app.handle) }}
|
21
|
+
spec{ use{ menu = get_menu(@app.handle) }}
|
22
|
+
|
23
|
+
it "retrieves a handle to the menu assigned to the specified top-level window" do
|
24
|
+
menu1 = GetMenu(@app.handle)
|
25
|
+
menu2 = get_menu(@app.handle)
|
26
|
+
menu1.should be_an Integer
|
27
|
+
menu1.should == @menu
|
28
|
+
menu1.should == menu2
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns 0/nil if no menu assigned to the specified top-level window" do
|
32
|
+
test_app_with_dialog(:close) do |app, dialog|
|
33
|
+
GetMenu(dialog).should == 0
|
34
|
+
get_menu(dialog).should == nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end # describe get_menu
|
38
|
+
|
39
|
+
describe "#get_system_menu" do
|
40
|
+
spec{ use{ system_menu = GetSystemMenu(any_handle, reset=0) }}
|
41
|
+
spec{ use{ system_menu = get_system_menu(any_handle, reset=false) }}
|
42
|
+
|
43
|
+
it "with reset=0/false(default) allows the application to access the window menu (AKA system menu)" do
|
44
|
+
menu1 = GetSystemMenu(@app.handle, reset=0)
|
45
|
+
menu2 = get_system_menu(@app.handle, reset=0)
|
46
|
+
menu3 = get_system_menu(@app.handle)
|
47
|
+
menu1.should be_an Integer
|
48
|
+
menu1.should == menu2
|
49
|
+
menu1.should == menu3
|
50
|
+
end
|
51
|
+
|
52
|
+
it "with reset=1/true allows the application to reset its window menu to default, returns 0/nil" do
|
53
|
+
GetSystemMenu(@app.handle, reset=1).should == 0
|
54
|
+
get_system_menu(@app.handle, reset=true).should == nil
|
55
|
+
end
|
56
|
+
end # describe get_system_menu
|
57
|
+
|
58
|
+
describe "#get_menu_item_count" do
|
59
|
+
|
60
|
+
spec{ use{ num_items = GetMenuItemCount(@menu) }}
|
61
|
+
spec{ use{ num_items = get_menu_item_count(@menu) }}
|
62
|
+
|
63
|
+
it "determines the number of items in the specified menu. " do
|
64
|
+
GetMenuItemCount(@menu).should == 3
|
65
|
+
get_menu_item_count(@menu).should == 3
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns -1/nil if function fails " do
|
69
|
+
GetMenuItemCount(not_a_handle).should == -1
|
70
|
+
get_menu_item_count(not_a_handle).should == nil
|
71
|
+
end
|
72
|
+
end # describe get_menu_item_count
|
73
|
+
|
74
|
+
describe "#get_menu_item_id" do
|
75
|
+
spec{ use{ item_id = GetMenuItemID(@menu, pos=0) }}
|
76
|
+
spec{ use{ item_id = get_menu_item_id(@menu, pos=0) }}
|
77
|
+
|
78
|
+
it "retrieves the menu item identifier of a menu item located at the specified position" do
|
79
|
+
GetMenuItemID(@file_menu, pos=0).should == ID_FILE_SAVE_AS
|
80
|
+
get_menu_item_id(@file_menu, pos=0).should == ID_FILE_SAVE_AS
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns -1/nil if no menu item at given position" do
|
84
|
+
GetMenuItemID(@menu, pos=4).should == -1
|
85
|
+
get_menu_item_id(@menu, pos=4).should == nil
|
86
|
+
end
|
87
|
+
|
88
|
+
it "returns -1/nil if given menu item is in fact a sub-menu" do
|
89
|
+
GetMenuItemID(@menu, pos=0).should == -1
|
90
|
+
get_menu_item_id(@menu, pos=1).should == nil
|
91
|
+
end
|
92
|
+
end # describe get_menu_item_id
|
93
|
+
|
94
|
+
describe "#get_sub_menu" do
|
95
|
+
spec{ use{ sub_menu = GetSubMenu(@menu, pos=0) }}
|
96
|
+
spec{ use{ sub_menu = get_sub_menu(@menu, pos=0) }}
|
97
|
+
|
98
|
+
it "retrieves a handle to the drop-down menu or submenu activated by the specified menu item" do
|
99
|
+
sub_menu1 = GetSubMenu(@menu, pos=0)
|
100
|
+
sub_menu2 = get_sub_menu(@menu, pos=0)
|
101
|
+
sub_menu1.should be_an Integer
|
102
|
+
sub_menu1.should == @file_menu
|
103
|
+
sub_menu1.should == sub_menu2
|
104
|
+
end
|
105
|
+
|
106
|
+
it "returns 0/nil if unable to find submenu activated by the specified menu item" do
|
107
|
+
GetSubMenu(@file_menu, pos=0).should == 0
|
108
|
+
get_sub_menu(@file_menu, pos=0).should == nil
|
109
|
+
end
|
110
|
+
end # describe get_sub_menu
|
111
|
+
|
112
|
+
describe "#is_menu" do
|
113
|
+
before(:all){ @menu = get_menu(@app.handle) }
|
114
|
+
|
115
|
+
spec{ use{ success = IsMenu(@menu) }}
|
116
|
+
spec{ use{ success = menu?(@menu) }}
|
117
|
+
|
118
|
+
it "determines whether a given handle is a menu handle " do
|
119
|
+
IsMenu(@menu).should == 1
|
120
|
+
is_menu(@menu).should == true
|
121
|
+
menu?(@menu).should == true
|
122
|
+
menu?(@file_menu).should == true
|
123
|
+
IsMenu(not_a_handle).should == 0
|
124
|
+
is_menu(not_a_handle).should == false
|
125
|
+
menu?(not_a_handle).should == false
|
126
|
+
end
|
127
|
+
end # describe is_menu
|
128
|
+
|
129
|
+
describe "#set_menu" do
|
130
|
+
spec{ use{ success = SetMenu(window_handle=0, menu_handle=0) }}
|
131
|
+
spec{ use{ success = set_menu(window_handle=0, menu_handle=0) }}
|
132
|
+
|
133
|
+
it "assigns/removes menu of the specified top-level window" do
|
134
|
+
SetMenu(@app.handle, menu_handle=0)
|
135
|
+
get_menu(@app.handle).should == nil
|
136
|
+
SetMenu(@app.handle, @menu)
|
137
|
+
menu(@app.handle).should == @menu
|
138
|
+
set_menu(@app.handle)
|
139
|
+
menu(@app.handle).should == nil
|
140
|
+
set_menu(@app.handle, @menu)
|
141
|
+
menu(@app.handle).should == @menu
|
142
|
+
end
|
143
|
+
|
144
|
+
it "snake_case api with nil, zero or omitted menu_handle removes menu" do
|
145
|
+
[[@app.handle, 0], [@app.handle, nil], [@app.handle]].each do |args|
|
146
|
+
set_menu(*args)
|
147
|
+
menu(@app.handle).should == nil
|
148
|
+
set_menu(@app.handle, @menu)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end # describe set_menu
|
152
|
+
|
153
|
+
describe "#create_menu" do
|
154
|
+
after(:each){ destroy_menu(@new_menu) }
|
155
|
+
|
156
|
+
spec{ use{ @new_menu = CreateMenu() }}
|
157
|
+
spec{ use{ @new_menu = create_menu() }}
|
158
|
+
|
159
|
+
it "original api creates a menu. The menu is initially empty, but it can be filled with menu items" do
|
160
|
+
@new_menu = CreateMenu()
|
161
|
+
menu?(@new_menu).should == true
|
162
|
+
end
|
163
|
+
|
164
|
+
it "snake_case api creates a menu. The menu is initially empty." do
|
165
|
+
@new_menu = create_menu()
|
166
|
+
menu?(@new_menu).should == true
|
167
|
+
end
|
168
|
+
end # describe create_menu
|
169
|
+
|
170
|
+
context 'functions related to menu item manipulation' do
|
171
|
+
before(:each)do
|
172
|
+
@new_menu = create_menu()
|
173
|
+
@sub_menu = create_menu()
|
174
|
+
@text = FFI::MemoryPointer.from_string("Appended Item Text")
|
175
|
+
end
|
176
|
+
after(:each){ destroy_menu(@new_menu) }
|
177
|
+
|
178
|
+
describe "#append_menu" do
|
179
|
+
spec{ use{ success = AppendMenu(menu_handle=0, flags=0, id_new_item=0, lp_new_item=nil) }}
|
180
|
+
spec{ use{ success = append_menu(menu_handle=0, flags=0, id_new_item=0, lp_new_item=nil) }}
|
181
|
+
|
182
|
+
it "appends a new item to the end of the specified menu bar, drop-down or context menu, returns 1/true " do
|
183
|
+
AppendMenu(@new_menu, flags=MF_STRING, 333, @text).should == 1
|
184
|
+
append_menu(@new_menu, flags=MF_STRING, 333, @text).should == true
|
185
|
+
menu_item_count(@new_menu).should == 2
|
186
|
+
menu_item_id(@new_menu, pos=0).should == 333
|
187
|
+
end
|
188
|
+
|
189
|
+
it "appends a submenu to the end of the specified menu bar, drop-down or context menu, returns 1/true " do
|
190
|
+
AppendMenu(@new_menu, MF_STRING | MF_POPUP, @sub_menu, @text).should == 1
|
191
|
+
append_menu(@new_menu, MF_STRING | MF_POPUP, @sub_menu, @text).should == true
|
192
|
+
menu_item_count(@new_menu).should == 2
|
193
|
+
get_sub_menu(@new_menu, pos=0).should == @sub_menu
|
194
|
+
get_sub_menu(@new_menu, pos=1).should == @sub_menu
|
195
|
+
end
|
196
|
+
|
197
|
+
it "returns 0/false if unable to appends a new item to the end of the specified menu" do
|
198
|
+
AppendMenu(0, flags=MF_STRING, 333, @text).should == 0
|
199
|
+
append_menu(0, flags=MF_STRING, 333, @text).should == false
|
200
|
+
end
|
201
|
+
end # describe append_menu
|
202
|
+
|
203
|
+
describe "#insert_menu" do
|
204
|
+
before(:each)do
|
205
|
+
append_menu(@new_menu, MF_STRING, ID_FILE_SAVE_AS, FFI::MemoryPointer.from_string("Appended Item Text"))
|
206
|
+
end
|
207
|
+
|
208
|
+
spec{ use{ success = InsertMenu(menu_handle=0, position=0, flags=0, id_new_item=0, lp_new_item=nil) }}
|
209
|
+
spec{ use{ success = insert_menu(menu_handle=0, position=0, flags=0, id_new_item=0, lp_new_item=nil) }}
|
210
|
+
|
211
|
+
it "inserts a new menu item into a menu, moving other items down the menu, returns 0/true" do
|
212
|
+
InsertMenu(@new_menu, 0, MF_STRING | MF_BYPOSITION, 1, @text).should == 1
|
213
|
+
insert_menu(@new_menu, 0, MF_STRING | MF_BYPOSITION, 0, @text).should == true
|
214
|
+
menu_item_count(@new_menu).should == 3
|
215
|
+
menu_item_id(@new_menu, pos=0).should == 0
|
216
|
+
menu_item_id(@new_menu, pos=1).should == 1
|
217
|
+
end
|
218
|
+
|
219
|
+
it "returns 0/false if unable to appends a new item to the end of the specified menu" do
|
220
|
+
InsertMenu(0, 0, flags=MF_STRING, 333, @text).should == 0
|
221
|
+
insert_menu(0, 0, flags=MF_STRING, 333, @text).should == false
|
222
|
+
end
|
223
|
+
end # describe insert_menu
|
224
|
+
|
225
|
+
describe "#delete_menu" do
|
226
|
+
before(:each)do
|
227
|
+
append_menu(@new_menu, MF_STRING, 0, FFI::MemoryPointer.from_string("Item 0"))
|
228
|
+
append_menu(@new_menu, MF_STRING, 1, FFI::MemoryPointer.from_string("Item 1"))
|
229
|
+
append_menu(@new_menu, MF_POPUP | MF_STRING, @sub_menu, FFI::MemoryPointer.from_string("Sub 1"))
|
230
|
+
end
|
231
|
+
|
232
|
+
spec{ use{ success = DeleteMenu(menu_handle=0, position=0, flags=0) }}
|
233
|
+
spec{ use{ success = delete_menu(menu_handle=0, position=0, flags=0) }}
|
234
|
+
|
235
|
+
it "deletes an item from the specified menu, returns 1/true" do
|
236
|
+
DeleteMenu(@new_menu, position=0, flags=MF_BYPOSITION).should == 1
|
237
|
+
menu_item_count(@new_menu).should == 2
|
238
|
+
delete_menu(@new_menu, position=0, flags=MF_BYPOSITION).should == true
|
239
|
+
menu_item_count(@new_menu).should == 1
|
240
|
+
end
|
241
|
+
|
242
|
+
it "returns 0/false if unable to delete an item from the specified menu" do
|
243
|
+
DeleteMenu(@new_menu, position=5, flags=MF_BYPOSITION).should == 0
|
244
|
+
menu_item_count(@new_menu).should == 3
|
245
|
+
delete_menu(0, position=0, flags=MF_BYPOSITION).should == false
|
246
|
+
end
|
247
|
+
|
248
|
+
it "destroys the handle to submenu and frees the memory if given menu item opens a submenu" do
|
249
|
+
delete_menu(@new_menu, position=2, flags=MF_BYPOSITION).should == true
|
250
|
+
menu_item_count(@new_menu).should == 2
|
251
|
+
menu?(@sub_menu).should == false
|
252
|
+
end
|
253
|
+
end # describe delete_menu
|
254
|
+
end # functions related to menu item manipulation
|
255
|
+
end # context 'non-destructive methods'
|
256
|
+
|
257
|
+
context 'destructive methods' do
|
258
|
+
before(:each)do
|
259
|
+
@app = launch_test_app
|
260
|
+
@menu = get_menu(@app.handle)
|
261
|
+
@file_menu = get_sub_menu(@menu, 0)
|
262
|
+
end
|
263
|
+
after(:each){ close_test_app if @launched_test_app }
|
264
|
+
|
265
|
+
describe "#destroy_menu" do
|
266
|
+
spec{ use{ success = DestroyMenu(menu_handle=0) }}
|
267
|
+
spec{ use{ success = destroy_menu(menu_handle=0) }}
|
268
|
+
|
269
|
+
it "original api destroys the specified menu and frees any memory that the menu occupies, returns 1" do
|
270
|
+
DestroyMenu(@menu).should == 1
|
271
|
+
menu?(@menu).should == false
|
272
|
+
end
|
273
|
+
|
274
|
+
it "snake_case api destroys the specified menu and frees any memory that the menu occupies, returns true" do
|
275
|
+
destroy_menu(@menu).should == true
|
276
|
+
menu?(@menu).should == false
|
277
|
+
end
|
278
|
+
|
279
|
+
it "returns 0/false if function was not successful " do
|
280
|
+
destroy_menu(h_menu=0).should == false
|
281
|
+
DestroyMenu(0).should == 0
|
282
|
+
end
|
283
|
+
end # describe destroy_menu
|
284
|
+
|
285
|
+
end # context 'destructive methods' do
|
286
|
+
|
287
|
+
end # describe Win::Gui::Menu, ' defines a set of API functions related to menus'
|
288
|
+
|
289
|
+
# describe Win::Gui::Menu, ' defines convenience/service methods on top of Windows API' do
|
290
|
+
# end # Win::Gui::Menu, ' defines convenience/service methods on top of Windows API'
|
291
|
+
end
|