/*----------------------------------------------------------------------------*/ /* */ /* 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'