918 lines
30 KiB
Plaintext
918 lines
30 KiB
Plaintext
/*----------------------------------------------------------------------------*/
|
|
/* */
|
|
/* Copyright (c) 2008-2014 Rexx Language Association. All rights reserved. */
|
|
/* */
|
|
/* This program and the accompanying materials are made available under */
|
|
/* the terms of the Common Public License v1.0 which accompanies this */
|
|
/* distribution. A copy is also available at the following address: */
|
|
/* https://www.oorexx.org/license.html */
|
|
/* */
|
|
/* Redistribution and use in source and binary forms, with or */
|
|
/* without modification, are permitted provided that the following */
|
|
/* conditions are met: */
|
|
/* */
|
|
/* Redistributions of source code must retain the above copyright */
|
|
/* notice, this list of conditions and the following disclaimer. */
|
|
/* Redistributions in binary form must reproduce the above copyright */
|
|
/* notice, this list of conditions and the following disclaimer in */
|
|
/* the documentation and/or other materials provided with the distribution. */
|
|
/* */
|
|
/* Neither the name of Rexx Language Association nor the names */
|
|
/* of its contributors may be used to endorse or promote products */
|
|
/* derived from this software without specific prior written permission. */
|
|
/* */
|
|
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */
|
|
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */
|
|
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */
|
|
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */
|
|
/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */
|
|
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */
|
|
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */
|
|
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY */
|
|
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */
|
|
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS */
|
|
/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
|
|
/* */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Name: windowsSystem.frm
|
|
* Type: Framework
|
|
*
|
|
* Description: A collection of public routines and classes to help work with
|
|
* the winsystm.cls package.
|
|
*
|
|
* This is an example of how to extract common function into a
|
|
* package, and the use the package to help in writting similar
|
|
* programs. A number of the sample programs that use
|
|
* winsystm.cls make use of this framework.
|
|
*/
|
|
|
|
::requires 'winsystm.cls'
|
|
::requires "ooDialog.cls"
|
|
|
|
/** findTheWindow()
|
|
* Uses the WindowsManager to create a WindowObject representing a top-level
|
|
* window currently running on the system.
|
|
*
|
|
* When an application is started up, it takes some finite amount of time before
|
|
* the window for the application is created by the operating system. The
|
|
* WindowsManager can not 'find' the aplication window until it has been
|
|
* created. This function loops a number of times trying to find the window,
|
|
* then eventually gives up
|
|
*
|
|
* How long to wait for to find the window before giving up can be adjusted by
|
|
* the caller.
|
|
*
|
|
* @param title The title of the window being sought.
|
|
* @param loops Optional. The number of loops to perform looking for the
|
|
* window. The default is 20.
|
|
* @param pause Optional. The time to sleep during each loop. The default
|
|
* is .2 of a second.
|
|
*
|
|
* @return A WindowObject object representing the desired window on
|
|
* success. If the function fails, .nil is returned.
|
|
*
|
|
* @note To find a window, you must use its exact title.
|
|
*/
|
|
::routine findTheWindow public
|
|
use strict arg title, loops = 20, pause = .2
|
|
|
|
windowMgr = .WindowsManager~new
|
|
|
|
do loops
|
|
j = SysSleep(pause)
|
|
wnd = windowMgr~find(title)
|
|
if wnd~class == .WindowObject then leave
|
|
end
|
|
|
|
return wnd
|
|
|
|
/** sendTextWithPause()
|
|
* Sends text to a window and pauses slightly before returning.
|
|
*
|
|
* @param wnd A window object that represents the window to send the text to.
|
|
* @param text The text to send.
|
|
*
|
|
* @return The return from SysSleep()
|
|
*
|
|
* @note Not to go into too much detail about the underlying implmentation of
|
|
* winsystm.cls, but a brief explantion. When sending text or a key
|
|
* press to a window, a Windows API function is used that returns
|
|
* immediately, before the text or key press actually makes it to
|
|
* the OS window. In a fast dual-core / multi-core / multi-processor
|
|
* system this can result in the window receiving the key presses or text
|
|
* in a different order than they are sent by ooRexx.
|
|
*
|
|
* This function and the sendKeyWithPause() function prevent that out of
|
|
* ordering by pausing very slightly after doing the send. This actually
|
|
* mimics more closely a user entering the data on a keyboard. Anyone
|
|
* familiar with Expect, probably knows that Expect provides a similiar
|
|
* function.
|
|
*/
|
|
::routine sendTextWithPause public
|
|
use strict arg wnd, text
|
|
wnd~sendText(text)
|
|
return SysSleep(.01)
|
|
|
|
/** sendKeyWithPause()
|
|
* Sends a key press to a window and pauses slightly before returning.
|
|
*
|
|
* @param wnd A window object that represents the window to send the key
|
|
* press to.
|
|
* @param key The key press to send.
|
|
*
|
|
* @return The return from SysSleep()
|
|
*
|
|
* @see sendTextWithPause()
|
|
*/
|
|
::routine sendKeyWithPause public
|
|
use strict arg wnd, key
|
|
wnd~sendKey(key)
|
|
return SysSleep(.01)
|
|
|
|
/** printChildren()
|
|
* Given a window object, enumerates all descendent windows of that window and
|
|
* prints the results to the screen.
|
|
*
|
|
* @param wnd The parent window object.
|
|
* @param indent When printing to the console, the amount of indentation.
|
|
*
|
|
* @return True if this function recursed, otherwise false.
|
|
*/
|
|
::routine printChildren public
|
|
use strict arg wnd, indent
|
|
|
|
line = indent || wnd~hwnd ":" wnd~id ":" wnd~wclass ":" wnd~title~left(15) ":" wnd~coordinates
|
|
|
|
childs. = wnd~enumerateChildren
|
|
|
|
select
|
|
when childs.0 == 0 then do
|
|
line = line "no children"
|
|
say line
|
|
return .false
|
|
end
|
|
|
|
when childs.0 == 1 then do
|
|
line = line "1 child"
|
|
end
|
|
|
|
otherwise do
|
|
line = line childs.0 "children"
|
|
end
|
|
end
|
|
-- End select
|
|
|
|
say line
|
|
|
|
indent = indent || " "
|
|
|
|
do i = 1 to childs.0
|
|
childWnd = .WindowObject~new(childs.i.!Handle)
|
|
ret = printChildren(childWnd, indent)
|
|
end
|
|
|
|
return .true
|
|
|
|
/** findDescendent()
|
|
* Finds a descendent window with the window title specified, if any.
|
|
*
|
|
* @param wnd The parent window whose descendents are searched.
|
|
* @param title The title (label) of the window being searched for.
|
|
*
|
|
* @return A .WindowObject representing the window if found. If not found then
|
|
* .nil is returned.
|
|
*
|
|
* @note This function is very similar to .WindowObject~findChild(), except
|
|
* that this function will search recursively through the window
|
|
* hierarchy for any descendent window with the specified label. The
|
|
* WindowObject's findChild() on the other hand only looks at the
|
|
* immediate children.
|
|
*/
|
|
::routine findDescendent public
|
|
use strict arg wnd, title
|
|
|
|
descendent = wnd~findChild(title)
|
|
if descendent \== .nil then return descendent
|
|
|
|
children. = wnd~enumerateChildren
|
|
|
|
do i = 1 to children.0
|
|
childWnd = .WindowObject~new(children.i.!Handle)
|
|
|
|
descendent = findDescendent(childWnd, title)
|
|
if descendent \== .nil then return descendent
|
|
end
|
|
|
|
return .nil
|
|
|
|
/** isVistaOrLater()
|
|
* Simple convenience function to determine if the operating system is at least
|
|
* Windows Vista.
|
|
*
|
|
* @return True if the current OS is Vista or a later version of Windows,
|
|
* otherwise false.
|
|
*/
|
|
::routine isVistaOrLater public
|
|
parse value SysVersion() with name ver
|
|
return (ver >= 6)
|
|
|
|
|
|
/** getPathToSystemExe()
|
|
* Gets the complete path to an executable that is a standard application
|
|
* shipped with a Windows system. In a Windows distributions, these
|
|
* applications are in the system directory. Getting the complete path ensures
|
|
* that the correct application is started.
|
|
*
|
|
* For instance, there are a surprisingly large number of applications named
|
|
* 'calc.' If a user has set up her system with a 'calc' program in a directory
|
|
* ahead of the system directory in the path, then that calc program can end up
|
|
* being started rather than the expected Windows calculator application.
|
|
*
|
|
* @param prgName The program whose path is being sought. The .exe is
|
|
* expected.
|
|
*
|
|
* @return The fully qualified path name of the program, if it exists in the
|
|
* system directory. If not found, then .nil is returned.
|
|
*/
|
|
::routine getPathToSystemExe public
|
|
use strict arg prgName
|
|
|
|
shell = .oleObject~new('Shell.Application')
|
|
|
|
-- 0x25 is the CSIDL_SYSTEM constant. The CSIDL_XXX constants are used to
|
|
-- identify well known directories on Windows. Like the My Documents
|
|
-- directory, the All Users Start Menu, etc. We use this constant to get the
|
|
-- Windows System folder object, and from that the path to the directory.
|
|
csidl_system = '25'
|
|
|
|
folderObj = shell~nameSpace(csidl_system~x2d)
|
|
sysFolderPath = folderObj~self~path
|
|
if sysFolderPath~right(1) \== '\' then sysFolderPath = sysFolderPath'\'
|
|
|
|
prgPathName = sysFolderPath || prgName
|
|
if \ SysIsFile(prgPathName) then prgPathName = .nil
|
|
|
|
return prgPathName
|
|
|
|
/** getWindowTree()
|
|
* Constructs a data structure that represents the entire window hierarchy of
|
|
* a window.
|
|
*
|
|
* Each node in the structure represents a window. The node is a .directory
|
|
* object whose items contain the important attributes of the window. This
|
|
* directory object has an item: children, that is an array of nodes
|
|
* representing the children windows of the node.
|
|
*
|
|
* Each node has these indexes:
|
|
*
|
|
* node~handle
|
|
* node~title
|
|
* node~windowClass
|
|
* node~state
|
|
* node~id
|
|
* node~coordinates
|
|
* node~children
|
|
* node~hasChildren
|
|
* node~childrenCount
|
|
*
|
|
* @param wnd The window object to construct a node for.
|
|
*
|
|
* @return A tree of window nodes representing the window specified and all its
|
|
* children.
|
|
*
|
|
* @note This is a recursive function.
|
|
*/
|
|
::routine getWindowTree public
|
|
use strict arg wnd
|
|
|
|
tree = buildWindowDetails(wnd)
|
|
child = wnd~firstChild
|
|
if child == .nil then return tree
|
|
|
|
tree~children = .array~new
|
|
tree~hasChildren = .true
|
|
do while child \== .nil
|
|
tree~children~append(getWindowTree(child))
|
|
tree~childrenCount += 1
|
|
child = child~next
|
|
end
|
|
|
|
return tree
|
|
|
|
/** buildWindowDetails()
|
|
* Private helper function for getWindowTree().
|
|
*
|
|
* @param wnd The .WindowObject whose details are desired.
|
|
*
|
|
* @return A directory object whose items reflect a number of attributes of
|
|
* a window.
|
|
*/
|
|
::routine buildWindowDetails
|
|
use strict arg wnd
|
|
|
|
d = .directory~new
|
|
d~handle = wnd~handle
|
|
d~text = wnd~title
|
|
d~windowClass = wnd~wClass
|
|
d~state = wnd~state
|
|
d~id = wnd~id
|
|
d~coordinates = wnd~coordinates
|
|
d~children = .nil
|
|
d~hasChildren = .false
|
|
d~childrenCount = 0
|
|
|
|
return d
|
|
|
|
/** printWindowTree
|
|
* Given a tree of window nodes, prints out the tree to the console.
|
|
*
|
|
* @param tree The tree of window nodes to print.
|
|
* @param indent The current indentation. Note that this is a recursive
|
|
* function and the indent is increased with each recursion.
|
|
*
|
|
* @return True if the function has recursed, othewise false.
|
|
*
|
|
* @see getWindowTree()
|
|
* @see printChildren()
|
|
*
|
|
* @note The function produces the exact same output as printChildren(). Its
|
|
* main purpose was to debug getWindowTree(). However, it could serve
|
|
* as a useful template if one wanted to change how the information or
|
|
* what information is printed to the console.
|
|
*/
|
|
::routine printWindowTree public
|
|
use strict arg tree, indent
|
|
|
|
-- A little defensive programming.
|
|
if \ tree~isA(.directory) then return .false
|
|
|
|
line = indent || tree~handle ":" tree~id ":" tree~windowClass ":" tree~text~left(15) ":" tree~coordinates
|
|
|
|
select
|
|
when tree~childrenCount == 0 then do
|
|
line = line "no children"
|
|
say line
|
|
return .false
|
|
end
|
|
|
|
when tree~childrenCount == 1 then do
|
|
line = line "1 child"
|
|
end
|
|
|
|
otherwise do
|
|
line = line tree~childrenCount "children"
|
|
end
|
|
end
|
|
-- End select
|
|
|
|
say line
|
|
|
|
indent = indent || " "
|
|
|
|
do child over tree~children
|
|
ret = printWindowTree(child, indent)
|
|
end
|
|
|
|
return .true
|
|
|
|
/** showWindowTree()
|
|
* Puts up an ooDialog dialog that displays the window hierarchy of a window in
|
|
* a tree view control.
|
|
*
|
|
* @param tree A tree node data structure produced by the getWindowTree()
|
|
* function.
|
|
*
|
|
* @return True if the dialog was shown, false if it was not.
|
|
*
|
|
* @see getWindowTree()
|
|
*
|
|
* @note We use the locate() function to get the directory this source code is
|
|
* located in. And then, use that value to create a complete path name
|
|
* to our resource files. This ensures the .rc and .h files will always
|
|
* be found
|
|
*/
|
|
::routine showWindowTree public
|
|
use strict arg tree
|
|
|
|
sd = locate()
|
|
dlg = .WindowTreeDlg~new(sd"winSystemDlgs.rc", IDD_WINDOW_TREE, , sd"winSystemDlgs.h")
|
|
if dlg~initCode == 0 then do
|
|
dlg~useTree(tree)
|
|
dlg~execute("SHOWTOP")
|
|
return .true
|
|
end
|
|
|
|
return .false
|
|
|
|
/** class: WindowTreeDlg
|
|
*
|
|
* A simple ooDialog dialog class to display the window hierarchy, parent and
|
|
* descendent windows, of any window.
|
|
*
|
|
* The only caveat is that the class requires a window tree structure created by
|
|
* the getWindowTree() public routine in this framework.
|
|
*
|
|
* This is a subclass of RcDialog, meaning the dialog template is defined in a
|
|
* resource script file. The dialog template was created by a GUI dialog
|
|
* editor. The dialog template is stored in the winSystemDlgs.rc file and has
|
|
* a symbolic resource ID of IDD_WINDOW_TREE. The symbolic resource IDs are
|
|
* defined in the winSystemDlgs.h file.
|
|
*
|
|
* @note For an example of how to use this class see the showWindowTree()
|
|
* public routine in this framework.
|
|
*/
|
|
::class 'WindowTreeDlg' public subclass RcDialog
|
|
|
|
/** useTree()
|
|
* Sets the window tree structure for this dialog. The structure must be set
|
|
* prior to executing the dialog, otherwise the dialog will display an empty
|
|
* tree view control.
|
|
*
|
|
* @param windowTree A tree node structure in the same format as that produced
|
|
* by the getWindowTree() function.
|
|
@
|
|
@ @see getWindowTree()
|
|
*/
|
|
::method useTree
|
|
expose windowTree
|
|
use arg windowTree
|
|
|
|
/** initDialog()
|
|
* Initializes the dialog controls for this dialog. The only control is the
|
|
* Tree control.
|
|
*
|
|
* The ooDialog framework automatically inovkes this method for every dialog
|
|
* immediately after the underlying Windows dialog has been created. Since most
|
|
* of the initialization of dialog controls requires that the underlying control
|
|
* has been created, this makes initDialog() the proper place to do all the
|
|
* control initialization.
|
|
*/
|
|
::method initDialog
|
|
expose windowTree
|
|
|
|
if windowTree~isA(.directory), windowTree~hasIndex("WINDOWCLASS") then self~doInit(windowTree)
|
|
|
|
/** doInit()
|
|
* A private method that does the actuall work of initializing the tree control.
|
|
*
|
|
* @param windowTree The window tree node structure for this dialog.
|
|
*/
|
|
::method doInit private
|
|
use strict arg windowTree
|
|
|
|
-- Get the tree-view control object and then invoke the addNode() recursive
|
|
-- method to add all the items to the control.
|
|
tree = self~newTreeView(IDC_TREE_WINDOWS)
|
|
rootNode = self~addNode(tree, "Root", windowTree)
|
|
|
|
-- Set the title of this dialog, which will contain the window handle for the
|
|
-- window we represent.
|
|
title = "The" windowTree~text "(" || windowTree~handle || ") Window Hierarchy"
|
|
self~setTitle(title)
|
|
|
|
-- Expand the root item in the tree-view control.
|
|
tree~expand(rootNode)
|
|
|
|
/** addNode()
|
|
* A private recursive function that adds each node to the tree control. Each
|
|
* node represents a single window in the hiearchy.
|
|
*
|
|
* @param tree The tree-view control object.
|
|
*
|
|
* @param root A reference to the current item in the tree-view control we
|
|
* are working with. Note that this can also be the keyword: root
|
|
* which will signal that this is to be the initial item in the
|
|
* tree-view control. Otherwise, it is a reference to an already
|
|
* created item in the tree-view control.
|
|
*
|
|
* @param node The current node in the window tree structure we are working
|
|
* with.
|
|
*/
|
|
::method addNode private
|
|
use strict arg tree, root, node
|
|
|
|
-- The text displayed for this item.
|
|
text = node~handle || ":" node~text
|
|
|
|
-- Insert (create) a new item in the tree-view control. Insert it after the
|
|
-- 'LAST' sub-item under the item referenced by root in the tree-view control.
|
|
-- This item is the window item.
|
|
newRoot = tree~insert(root, "LAST", text, , , , node~childrenCount)
|
|
|
|
-- Insert sub-items. Each of these sub-items is an attribute of the window
|
|
tree~insert(newRoot, , "Text:" node~text)
|
|
tree~insert(newRoot, , "Handle:" node~handle)
|
|
tree~insert(newRoot, , "Class:" node~windowClass)
|
|
tree~insert(newRoot, , "ID:" node~id)
|
|
tree~insert(newRoot, , "Coordinates:" node~coordinates)
|
|
tree~insert(newRoot, , "State:" node~state)
|
|
|
|
-- Now, if the window has children windows, recursively add them to the
|
|
-- tree-view control.
|
|
if node~hasChildren then do n over node~children
|
|
self~addNode(tree, newRoot, n)
|
|
end
|
|
|
|
return newRoot
|
|
|
|
/** class: MenuDetailer
|
|
*
|
|
* A class to parse and display the details of a menu bar.
|
|
*
|
|
* In Windows, a menu bar is similar to a top level window in that they both
|
|
* have a hierarchy of contained objects. A top-level window can contain other
|
|
* windows, which themselves can contain other windows. A menu bar can contain
|
|
* submenus, which can contain submenus.
|
|
*
|
|
* This hierarchy is easily displayed in a tree-like structure, for both windows
|
|
* and menus. The function of displaying a menu hierachy is therefore very
|
|
* similar to the set of functions used to display a window hierarchy provided
|
|
* in this framework.
|
|
*
|
|
* However, the MenuDetailer is a more object-orientated approach than that used
|
|
* for displaying a window hierarchy. The data and the means to manipulate the
|
|
* data are all contained within a single object, the MenuDetailer object.
|
|
*
|
|
* Note that this is a subclass of ooDialog's RcDialog, but the class can be
|
|
* used / useful without ever creating an underlying Windows dialog. The
|
|
* ooDialog part is only used in the display() method which produces a graphical
|
|
* dislpay of the menu tree.
|
|
*/
|
|
::class 'MenuDetailer' public subclass RcDialog
|
|
|
|
::method init
|
|
use strict arg wnd
|
|
self~newWindow(wnd)
|
|
|
|
|
|
/** newWindow()
|
|
* This method is called to create a menu tree node structure that represents
|
|
* the menu of the specified window. It is used when a new MenuDetailer object
|
|
* is created and when / if the user of the class sets a new window.
|
|
*
|
|
* @param wnd A window object whose menu is to be 'detailed.;
|
|
*/
|
|
::method newWindow private
|
|
expose menubar maxTextLength textFormat mainWindow
|
|
use strict arg wnd
|
|
|
|
-- Ensure wnd is a WindowObject.
|
|
if \ wnd~isA(.WindowObject) then raise syntax 93.948 array ("1 'wnd'", "WindowObject")
|
|
|
|
-- Set / reset some state variables.
|
|
maxTextLength = 0
|
|
textFormat = .nil
|
|
menubar = .nil
|
|
mainWindow = wnd
|
|
|
|
-- Create the menu tree node structure.
|
|
self~createMenubar
|
|
|
|
/** createMenubar()
|
|
* Creates a menu tree node structure that represents the menu of the main
|
|
* window.
|
|
*
|
|
* Conceptually, a menu consists of a container with some number of menu items,
|
|
* where each menu item can, possibly, be a submenu. Typically in Windows, the
|
|
* top-level container is called a menubar. A tree is a natural data
|
|
* structure to represent this.
|
|
*
|
|
* The menu tree node structure consists of a directory that represents the
|
|
* menubar and has this structure:
|
|
*
|
|
* menubar~ownerHwnd
|
|
* menubar~ownerTitle
|
|
* menubar~itemCount
|
|
* menubar~menuItems an array of menu items where each menu item is a
|
|
* directory.
|
|
*
|
|
* menuitem~pos
|
|
* menuitem~id
|
|
* menuitem~text
|
|
* menuitem~isSubmenu
|
|
* menuitem~isSeparator
|
|
* menuitem~isTextItem
|
|
* menuitem~isChecked
|
|
* menuitem~itemCount
|
|
* menuitem~menuItems an array of menu items
|
|
*
|
|
*/
|
|
::method createMenubar private
|
|
expose menubar mainWindow
|
|
|
|
menubar = .directory~new
|
|
menubar~ownerTitle = mainWindow~title
|
|
menubar~ownerHwnd = mainWindow~hwnd
|
|
|
|
menu = mainWindow~menu
|
|
if menu == .nil then do
|
|
menubar~itemCount = -1
|
|
menubar~menuItems = .nil
|
|
end
|
|
else do
|
|
menubar~itemCount = menu~items
|
|
menubar~menuItems = self~populate(menu)
|
|
end
|
|
|
|
/** populate()
|
|
* A recursive function to create and return an array of menu items.
|
|
*
|
|
* @param A MenuObject object whose menuitems will be used to create and
|
|
* populate an array.
|
|
*
|
|
* @return The populated array.
|
|
*
|
|
* @see createMenubar()
|
|
*/
|
|
::method populate private
|
|
expose maxTextLength
|
|
use strict arg menu
|
|
|
|
maxTextLength = 0
|
|
count = menu~items
|
|
|
|
a = .array~new(count)
|
|
do i = 0 to count - 1
|
|
d = .directory~new
|
|
d~pos = i
|
|
d~id = menu~idOf(i)
|
|
d~text = menu~textOf(i)
|
|
d~isSubmenu = menu~isSubmenu(i)
|
|
d~isSeparator = menu~isSeparator(i)
|
|
d~isTextItem = \(d~isSeparator | d~isSubmenu)
|
|
d~isChecked = menu~isChecked(i)
|
|
|
|
if d~text~length > maxTextLength then maxTextLength = d~text~length
|
|
|
|
if d~isSubmenu then do
|
|
submenu = menu~submenu(i)
|
|
d~itemCount = submenu~items
|
|
d~menuItems = self~populate(submenu)
|
|
end
|
|
else do
|
|
d~itemCount = 0
|
|
d~menuItems = .nil
|
|
end
|
|
|
|
a[i + 1] = d
|
|
end
|
|
|
|
return a
|
|
|
|
/** print()
|
|
* Outputs a text representation of the menu to the console.
|
|
*/
|
|
::method print
|
|
expose textFormat
|
|
|
|
-- If not already created, construct an array of text lines that represents
|
|
-- the menu. Then output the lines to the console.
|
|
if textFormat == .nil then self~toArray
|
|
do l over textFormat
|
|
say l
|
|
end
|
|
|
|
/** getOutline()
|
|
* Return the array of text lines representing the menu to the caller. The
|
|
* caller can then use the array as they see fit.
|
|
*/
|
|
::method getOutline
|
|
expose textFormat
|
|
|
|
-- Return a copy of the array to the caller so that an outsider can not
|
|
-- unintentionally or intentionally change our internal data.
|
|
if textFormat == .nil then self~toArray
|
|
return textFormat~copy
|
|
|
|
/** getMenubar()
|
|
* Return the menu tree node structure to the caller. The caller can then
|
|
* format and / or use the data as desired.
|
|
*/
|
|
::method getMenubar
|
|
expose menubar
|
|
|
|
-- Recreate the menubar if needed.
|
|
if menubar == .nil then self~createMenubar
|
|
|
|
-- Since ooRexx does not have a deep copy, we save a reference to the menuBar
|
|
-- object, set our internal reference to the menubar to .nil, and return the
|
|
-- saved reference. This allows the caller to change the menubar without
|
|
-- changing our internal representation. If we need the menubar structure
|
|
-- again, we will re-create it.
|
|
populatedMenubar = menubar
|
|
menubar = .nil
|
|
|
|
return populatedMenubar
|
|
|
|
/** setWindow()
|
|
* Changes the main window to a new one.
|
|
*/
|
|
::method setWindow
|
|
use strict arg wnd
|
|
self~newWindow(wnd)
|
|
|
|
/** toArray()
|
|
* Transforms the menu tree node structure that represents our menu into an
|
|
* array of text lines that represents the menu.
|
|
*/
|
|
::method toArray private
|
|
expose menubar textFormat
|
|
|
|
-- Check if we need to recreated the menubar.
|
|
if menubar == .nil then self~createMenubar
|
|
|
|
textFormat = .array~new
|
|
|
|
-- The first line will show data about the main window. It is possible that
|
|
-- the window will were initialized with does not have a menu. In addition,
|
|
-- many applications now create their menus dynamically. Those types of menus
|
|
-- may not contain any menu items until the user actually selects a menu item.
|
|
-- For these two cases we return immediately.
|
|
|
|
line = menubar~ownerTitle "(" || menubar~ownerHwnd || ")"
|
|
if menubar~itemCount == -1 then do
|
|
textFormat[1] = line "does not have a menu"
|
|
return textFormat
|
|
end
|
|
else if menubar~itemCount == 0 then do
|
|
textFormat[1] = line "menu is not populated"
|
|
return textFormat
|
|
end
|
|
|
|
-- Add the first line, then recursively add a line for every menu item.
|
|
textFormat[1] = line
|
|
do item over menubar~menuItems
|
|
self~addArrayItem(item, " ")
|
|
end
|
|
|
|
/** addArrayItem()
|
|
* A recursive method that adds a line of text for every menu item to an array
|
|
* of lines.
|
|
*
|
|
* @param item A node in the menu tree node structure representing a menu
|
|
* item.
|
|
*
|
|
* @param indent A string of spaces, the length of which shows the depth of
|
|
* the menu item within the overall menu.
|
|
*/
|
|
::method addArrayItem private
|
|
expose maxTextLength textFormat
|
|
use strict arg item, indent
|
|
|
|
-- Build up a line of text for the menu item
|
|
line = indent
|
|
sep = '-'~copies(maxTextLength)
|
|
pos = item~pos~right(2) || ':'
|
|
id = '[id:' item~id~right(3) || ']'
|
|
|
|
select
|
|
when item~isSubmenu then do
|
|
line = line || pos id 'popup menu' item~text~left(maxTextLength)
|
|
end
|
|
when item~isSeparator then do
|
|
line = line || pos id 'separator ' sep
|
|
end
|
|
otherwise do
|
|
line = line || pos id 'menu item ' item~text~left(maxTextLength)
|
|
if item~isChecked then line = line '[checked]'
|
|
end
|
|
end
|
|
-- End select
|
|
|
|
-- Add the line to our array of lines.
|
|
textFormat~append(line)
|
|
|
|
-- If item is itself a menu, recursively add its menu items to the array.
|
|
if item~isSubmenu then do
|
|
-- Increase our indent by 2 spaces.
|
|
indent = indent || " "
|
|
do menuItem over item~menuItems
|
|
self~addArrayItem(menuItem, indent)
|
|
end
|
|
end
|
|
|
|
/** display()
|
|
* Displays our menu in a graphical format using a tree control in a dialog.
|
|
*/
|
|
::method display
|
|
|
|
-- Initialize our ooDialog superclass. The dialog template is stored in the
|
|
-- resource script file: winSystemDlgs.rec with a symbolic ID of IDD_MENU_TREE
|
|
-- and the symbolic IDs for the dialog are defined in winSystemDlgs.h
|
|
--
|
|
-- Note: We use the locate() function to get the directory this source code
|
|
-- is located in. And then, use that value to create a complete path name to
|
|
-- our resource files. This ensures the .rc and .h files will always be
|
|
-- found.
|
|
sd = locate()
|
|
self~init:super(sd"winSystemDlgs.rc", IDD_MENU_TREE, , sd"winSystemDlgs.h")
|
|
self~execute("SHOWTOP")
|
|
|
|
/** initDialog()
|
|
* Initializes the dialog controls for this dialog. The only control is the
|
|
* Tree control.
|
|
*
|
|
* The ooDialog framework automatically inovkes this method for every dialog
|
|
* immediately after the underlying Windows dialog has been created. Since most
|
|
* of the initialization of dialog controls requires that the underlying control
|
|
* has been created, this makes initDialog() the proper place to do all the
|
|
* control initialization.
|
|
*
|
|
* @see WindowTreeDlg::initDialog()
|
|
*/
|
|
::method initDialog
|
|
expose menubar
|
|
|
|
-- Check if we need to recreate the menubar.
|
|
if menubar == .nil then self~createMenubar
|
|
|
|
-- Set the dialog title to a subset of what will be the text for the first
|
|
-- item in the tree-view control.
|
|
rootText = menubar~ownerTitle "(" || menubar~ownerHwnd || ")"
|
|
self~setTitle(rootText "Menu Hierarchy")
|
|
|
|
-- The tree-view control is populated in a similar manner as was the tree-view
|
|
-- control in the WindowTreeDlg class in this framework. See that class for
|
|
-- comment if needed.
|
|
|
|
-- Get the tree-view object, set the first item, expand it.
|
|
tree = self~newTreeView(IDC_TREE_MENUS)
|
|
|
|
if menubar~itemCount == -1 then do
|
|
rootText = rootText "[window does not have a menu]"
|
|
end
|
|
else if menubar~itemCount == 0 then do
|
|
rootText = rootText "[menu is not populated]"
|
|
end
|
|
|
|
rootNode = tree~add(rootText, , , "EXPANDED")
|
|
|
|
-- If the menubar has menu items, recursively add then to the tree-view
|
|
-- control.
|
|
if menubar~itemCount > 0 then do item over menubar~menuItems
|
|
self~addNode(tree, rootNode, item)
|
|
end
|
|
|
|
/** addNode()
|
|
* Recursively add tree-view items for each menu item in our menu.
|
|
*
|
|
* @param tree The tree-view control object.
|
|
*
|
|
* @param root A reference to the current item in the tree-view control we
|
|
* are working with.
|
|
*
|
|
* @param menuItem The current node in the menu tree structure we are working
|
|
* with.
|
|
*
|
|
* @see WindowTreeDlg::initDialog()
|
|
*/
|
|
::method addNode private
|
|
expose maxTextLength
|
|
use strict arg tree, root, menuItem
|
|
|
|
text = menuItem~pos~right(2) || ':'
|
|
|
|
if menuItem~isSeparator then text = text '-'~copies(maxTextLength)
|
|
else text = text menuItem~text
|
|
|
|
if menuItem~isSubmenu then count = menuItem~itemCount
|
|
else count = 0
|
|
|
|
newRoot = tree~insert(root, "LAST", text, , , , count)
|
|
|
|
tree~insert(newRoot, , "Text:" menuItem~text)
|
|
tree~insert(newRoot, , "ID:" menuItem~id)
|
|
tree~insert(newRoot, , "Submenu?" self~logicalToString(menuItem~isSubmenu))
|
|
tree~insert(newRoot, , "Text Item?" self~logicalToString(menuItem~isTextItem))
|
|
tree~insert(newRoot, , "Separator?" self~logicalToString(menuItem~isSeparator))
|
|
tree~insert(newRoot, , "Checked?" self~logicalToString(menuItem~isChecked))
|
|
|
|
if menuItem~isSubmenu then do mi over menuItem~menuItems
|
|
self~addNode(tree, newRoot, mi)
|
|
end
|
|
|
|
return newRoot
|
|
|
|
/** logicalToString()
|
|
* Simple helper method to convert an ooRexx logical value to a string
|
|
* representation.
|
|
*
|
|
* @param logical The value to convert.
|
|
*
|
|
* @return The string "true" if logical is strictly true, otherwise the string
|
|
* "false"
|
|
*/
|
|
::method logicalToString private
|
|
use strict arg logical
|
|
|
|
-- Work with anything sent to us. This uses the short-cut AND operator. Once
|
|
-- a test fails, the rest of the tests are not evaluated. If logical is a
|
|
-- string object, and the data type of the string object is lOgical, and
|
|
-- logical is true then return "true" Othewise return "false"
|
|
|
|
if logical~isA(.String), logical~datatype('O'), logical then return 'true'
|
|
else return 'false'
|
|
|