pyWinAuto: c:\.projects\py_pywinauto\pywinauto\controls\menuwrapper.py

0001# GUI Application automation and testing library
0002# Copyright (C) 2006 Mark Mc Mahon
0003#
0004# This library is free software; you can redistribute it and/or
0005# modify it under the terms of the GNU Lesser General Public License
0006# as published by the Free Software Foundation; either version 2.1
0007# of the License, or (at your option) any later version.
0008#
0009# This library is distributed in the hope that it will be useful,
0010# but WITHOUT ANY WARRANTY; without even the implied warranty of
0011# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0012# See the GNU Lesser General Public License for more details.
0013#
0014# You should have received a copy of the GNU Lesser General Public
0015# License along with this library; if not, write to the
0016#    Free Software Foundation, Inc.,
0017#    59 Temple Place,
0018#    Suite 330,
0019#    Boston, MA 02111-1307 USA
0020
0021"""Wrapper around Menu's and Menu items
0022
0023These wrappers allow you to work easily with menu items.
0024You can select or click on items and check if they are
0025checked or unchecked.
0026"""
0027
0028__revision__ = "$Revision: 330 $"
0029
0030import ctypes
0031import time
0032
0033from pywinauto import win32structures
0034from pywinauto import win32functions
0035from pywinauto import win32defines
0036from pywinauto import findbestmatch
0037from pywinauto.timings import Timings
0038
0039
0040class MenuItemNotEnabled(RuntimeError):
0041    "Raised when a menuitem is not enabled"
0042    pass
0043
0044
0045class MenuItem(object):
0046    """Wrap a menu item"""
0047
0048    def __init__(self, ctrl, menu, index, on_main_menu = False):
0049        """Initalize the menu item
0050
0051        * **ctrl**      The dialog or control that owns this menu
0052        * **menu**      The menu that this item is on
0053        * **index**     The Index of this menuitem on the menu
0054        * **on_main_menu**      True if the item is on the main menu
0055
0056        """
0057        self.index = index
0058        self.menu = menu
0059        self.ctrl = ctrl
0060        self.on_main_menu = on_main_menu
0061
0062
0063    def _read_item(self):
0064        """Read the menu item info
0065
0066        See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/resources/menus/menureference/menufunctions/getmenuiteminfo.asp
0067        for more information."""
0068        menu_info  = win32structures.MENUITEMINFOW()
0069        menu_info.cbSize = ctypes.sizeof (menu_info)
0070        menu_info.fMask =               win32defines.MIIM_CHECKMARKS |               win32defines.MIIM_ID |               win32defines.MIIM_STATE |               win32defines.MIIM_SUBMENU |               win32defines.MIIM_TYPE #| \
0076            #MIIM_FTYPE #| \
0077            #MIIM_STRING
0078            #MIIM_DATA | \
0079
0080        ret = win32functions.GetMenuItemInfo (
0081            self.menu,
0082            self.index,
0083            True,
0084            ctypes.byref(menu_info))
0085
0086        if not ret:
0087            raise ctypes.WinError()
0088
0089        return menu_info
0090
0091
0092    def Rectangle(self):
0093        "Get the rectangle of the menu item"
0094        rect = win32structures.RECT()
0095
0096        if self.on_main_menu:
0097            ctrl = self.ctrl
0098        else:
0099            ctrl = 0
0100
0101        win32functions.GetMenuItemRect(
0102            ctrl,
0103            self.menu,
0104            self.index,
0105            ctypes.byref(rect))
0106
0107        return rect
0108
0109    def Index(self):
0110        "Return the index of this menu item"
0111        return self.index
0112
0113    def State(self):
0114        "Return the state of this menu item"
0115        return self._read_item().fState
0116
0117    def ID(self):
0118        "Return the ID of this menu item"
0119        return self._read_item().wID
0120
0121    def Type(self):
0122        """Return the Type of this menu item
0123
0124        Main types are MF_STRING, MF_BITMAP, MF_SEPARATOR.
0125
0126        See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/resources/menus/menureference/menustructures/menuiteminfo.asp
0127        for further information."""
0128        return self._read_item().fType
0129
0130    def Text(self):
0131        "Return the state of this menu item"
0132
0133        info = self._read_item()
0134        # if there is text
0135        if info.cch:
0136            # allocate a buffer
0137            buffer_size = info.cch+1
0138            text = ctypes.create_unicode_buffer(buffer_size)
0139
0140            # update the structure and get the text info
0141            info.dwTypeData = ctypes.addressof(text)
0142            info.cch = buffer_size
0143
0144            win32functions.GetMenuItemInfo (
0145                self.menu,
0146                self.index,
0147                True,
0148                ctypes.byref(info))
0149
0150            text = text.value
0151        else:
0152            text = ''
0153
0154        return text
0155
0156    def SubMenu(self):
0157        "Return the SubMenu or None if no submenu"
0158        submenu_handle = self._read_item().hSubMenu
0159
0160        if submenu_handle:
0161            self.ctrl.SendMessageTimeout(
0162                win32defines.WM_INITMENUPOPUP,
0163                submenu_handle,
0164                self.index)
0165
0166            return Menu(self.ctrl, submenu_handle, False, self)
0167
0168        return None
0169
0170    def IsEnabled(self):
0171        "Return True if the item is enabled."
0172        return not (
0173            self.State() & win32defines.MF_DISABLED or
0174            self.State() & win32defines.MF_GRAYED)
0175
0176    def IsChecked(self):
0177        "Return True if the item is checked."
0178        return bool(self.State() & win32defines.MF_CHECKED)
0179
0180
0181    def Click(self):
0182        """Click on the menu item
0183
0184        If the menu is open this it will click with the mouse on the item.
0185        If the menu is not open each of it's parent's will be opened
0186        until the item is visible.
0187
0188        """
0189
0190        self.ctrl.VerifyEnabled()
0191
0192        rect = self.Rectangle()
0193
0194        if not self.IsEnabled():
0195            raise MenuItemNotEnabled(
0196                "MenuItem '%s' is disabled"% self.Text())
0197
0198        # if the item is not visible - work up along it's parents
0199        # until we find an item we CAN click on
0200        if rect == (0, 0, 0, 0):
0201            if self.menu.owner_item:
0202                self.menu.owner_item.Click()
0203
0204        rect = self.Rectangle()
0205
0206        x_pt = (rect.left + rect.right) /2
0207        y_pt = (rect.top + rect.bottom) /2
0208
0209        from HwndWrapper import _perform_click_input #, delay_after_menuselect
0210
0211        _perform_click_input(
0212            None,
0213            coords = (x_pt, y_pt),
0214            absolute = True)
0215
0216        win32functions.WaitGuiThreadIdle(self.ctrl)
0217
0218        #import time
0219        #time.sleep(delay_after_menuselect)
0220
0221
0222    def Select(self):
0223        """Select the menu item
0224
0225        This will send a message to the parent window that the
0226        item was picked
0227        """
0228
0229        if not self.IsEnabled():
0230            raise MenuItemNotEnabled(
0231                "MenuItem '%s' is disabled"% self.Text())
0232
0233        #from HwndWrapper import delay_after_menuselect
0234
0235        #if self.State() & win32defines.MF_BYPOSITION:
0236        #    print self.Text(), "BYPOSITION"
0237        #    self.ctrl.NotifyMenuSelect(self.Index(), True)
0238        #else:
0239
0240        # seems like SetFoucs might be messing with getting the
0241        # id for Popup menu items - so I calling it before SetFocus
0242        command_id = self.ID()
0243
0244        # notify the control that a menu item was selected
0245        self.ctrl.SetFocus()
0246        self.ctrl.SendMessageTimeout(
0247            win32defines.WM_COMMAND,
0248            win32functions.MakeLong(0, command_id))
0249
0250        #self.ctrl.NotifyMenuSelect(self.ID())
0251        win32functions.WaitGuiThreadIdle(self.ctrl)
0252        time.sleep(Timings.after_menu_wait)
0253
0254    def GetProperties(self):
0255        """Return the properties for the item as a dict
0256
0257        If this item opens a sub menu then call Menu.GetProperties()
0258        to return the list of items in the sub menu. This is avialable
0259        under teh 'MenuItems' key
0260        """
0261        props = {}
0262        props['Index'] = self.Index()
0263        props['State'] = self.State()
0264        props['Type'] = self.Type()
0265        props['ID'] = self.ID()
0266        props['Text'] = self.Text()
0267
0268        submenu =  self.SubMenu()
0269        if submenu:
0270            props['MenuItems'] = submenu.GetProperties()
0271
0272        return props
0273
0274    def __repr__(self):
0275        "Return a representation of the object as a string"
0276        return "<MenuItem %s>" % `self.Text()`
0277
0278
0279
0280#    def Check(self):
0281#        item = self._read_item()
0282#        item.fMask = win32defines.MIIM_STATE
0283#        item.fState &= win32defines.MF_CHECKED
0284#
0285##        ret = win32functions.SetMenuItemInfo(
0286##            self.menuhandle,
0287##            self.ID(),
0288##            0, # by position
0289##            ctypes.byref(item))
0290##
0291##        if not ret:
0292##            raise ctypes.WinError()
0293#
0294#        print win32functions.CheckMenuItem(
0295#            self.menuhandle,
0296#            self.index,
0297#            win32defines.MF_BYPOSITION | win32defines.MF_CHECKED)
0298#
0299#        win32functions.DrawMenuBar(self.ctrl)
0300#
0301#    def UnCheck(self):
0302#        item = self._read_item()
0303#        item.fMask = win32defines.MIIM_STATE
0304#        item.fState &= win32defines.MF_UNCHECKED
0305#
0306#        ret = win32functions.SetMenuItemInfo(
0307#            self.menuhandle,
0308#            self.ID(),
0309#            0, # by position
0310#            ctypes.byref(item))
0311#
0312#        if not ret:
0313#            raise ctypes.WinError()
0314#
0315#        win32functions.DrawMenuBar(self.ctrl)
0316#
0317#
0318
0319class Menu(object):
0320    """A simple wrapper around a menu handle
0321
0322    A menu supports methods for querying the menu
0323    and getting it's menu items."""
0324    def __init__(
0325        self,
0326        owner_ctrl,
0327        menuhandle,
0328        is_main_menu = True,
0329        owner_item = None):
0330        """Initialize the class.
0331
0332        * **owner_ctrl** is the Control that owns this menu
0333        * **menuhandle** is the menu handle of the menu
0334        * **is_main_menu** we have to track whether it is the main menu
0335          or a popup menu
0336        * **owner_item** The item that contains this menu - this will be
0337          None for the main menu, it will be a MenuItem instance for a
0338          submenu.
0339
0340        """
0341        self.ctrl = owner_ctrl
0342        self.handle = menuhandle
0343        self.is_main_menu = is_main_menu
0344        self.owner_item = owner_item
0345
0346        self._as_parameter_ = self.handle
0347
0348        if self.is_main_menu:
0349            self.ctrl.SendMessageTimeout(win32defines.WM_INITMENU, self.handle)
0350
0351    def ItemCount(self):
0352        "Return the count of items in this menu"
0353        return win32functions.GetMenuItemCount(self.handle)
0354
0355    def Item(self, index):
0356        """Return a specific menu item
0357
0358        * **index** is the 0 based index of the menu item you want
0359        """
0360        return MenuItem(self.ctrl, self, index, self.is_main_menu)
0361
0362    def Items(self):
0363        "Return a list of all the items in this menu"
0364        items = []
0365        for i in range(0, self.ItemCount()):
0366            items.append(self.Item(i))
0367
0368        return items
0369
0370    def GetProperties(self):
0371        """Return the properties for the menu as a list of dictionaries
0372
0373        This method is actually recursive. It calls GetProperties() for each
0374        of the items. If the item has a sub menu it will call this
0375        GetProperties to get the sub menu items.
0376        """
0377        item_props = []
0378
0379        for item in self.Items():
0380            item_props.append(item.GetProperties())
0381
0382        return {'MenuItems': item_props}
0383
0384
0385    def GetMenuPath(self, path, path_items = None, appdata = None):
0386        "Walk the items in this menu to find the item specified by path"
0387
0388        if path_items is None:
0389            path_items = []
0390
0391        # get the first part (and remainder)
0392        parts = path.split("->", 1)
0393        current_part = parts[0]
0394
0395        if current_part.startswith("#"):
0396            index = int(current_part[1:])
0397            best_item = self.Item(index)
0398        else:
0399            # get the text names from the menu items
0400            if appdata is None:
0401                item_texts = [item.Text() for item in self.Items()]
0402
0403            else:
0404                item_texts = [item['Text'] for item in appdata]
0405
0406            # find the item that best matches the current part
0407            best_item = findbestmatch.find_best_match(
0408                current_part,
0409                item_texts,
0410                self.Items())
0411
0412        path_items.append(best_item)
0413
0414
0415        # if there are more parts - then get the next level
0416        if parts[1:]:
0417            if appdata:
0418                appdata = appdata[best_item.Index()]['MenuItems']
0419            if best_item.SubMenu() is not None:
0420                best_item.SubMenu().GetMenuPath(
0421                    "->".join(parts[1:]),
0422                    path_items,
0423                    appdata)
0424
0425        return path_items
0426
0427
0428    def __repr__(self):
0429        "Return a simple representation of the menu"
0430        return "<Menu %d>" % self.handle
0431
0432
0433#    def GetProperties(self):
0434#
0435#        for i in range(0, self.ItemCount()):
0436#            menu_info = self.Item(self, i)[0]
0437#
0438#            item_prop['Index'] = i
0439#            item_prop['State'] = menu_info.fState
0440#            item_prop['Type'] = menu_info.fType
0441#            item_prop['ID'] = menu_info.wID
0442#
0443#            else:
0444#                item_prop['Text'] = ""
0445#
0446#            if self.IsSubMenu(i):
0447#                item_prop['MenuItems'] = self.SubMenu(i).GetProperties()
0448#
0449#            return item_prop
0450
0451
0452##====================================================================
0453#def _GetMenuItems(menu_handle, ctrl):
0454#    "Get the menu items as a list of dictionaries"
0455#    # If it doesn't have a real menu just return
0456#    if not win32functions.IsMenu(menu_handle):
0457#        return []
0458#
0459#
0460#    menu = Menu(ctrl, menu_handle)
0461#
0462#    props = []
0463#    for item in menu.Items():
0464#        item_props = {}
0465#
0466#        item_prop['Index'] = item.Index()
0467#        item_prop['State'] = item.State()
0468#        item_prop['Type'] = item.Type()
0469#        item_prop['ID'] = item.ID()
0470#        item_prop['Text'] = item.Text()
0471#
0472#        props.append(item_props)
0473#
0474#
0475#
0476#
0477#
0478#
0479#
0480#    items = []
0481#
0482#
0483#    # for each menu item
0484#    for i in range(0, item_count):
0485#
0486#        item_prop = {}
0487#
0488#        # get the information on the menu Item
0489#        menu_info  = win32structures.MENUITEMINFOW()
0490#        menu_info.cbSize = ctypes.sizeof (menu_info)
0491#        menu_info.fMask = \
0492#            win32defines.MIIM_CHECKMARKS | \
0493#            win32defines.MIIM_ID | \
0494#            win32defines.MIIM_STATE | \
0495#            win32defines.MIIM_SUBMENU | \
0496#            win32defines.MIIM_TYPE #| \
0497#            #MIIM_FTYPE #| \
0498#            #MIIM_STRING
0499#            #MIIM_DATA | \
0500#
0501#
0502#        ret = win32functions.GetMenuItemInfo (
0503#            menu_handle, i, True, ctypes.byref(menu_info))
0504#        if not ret:
0505#            raise ctypes.WinError()
0506#
0507#        # if there is text
0508#        if menu_info.cch:
0509#            # allocate a buffer
0510#            buffer_size = menu_info.cch+1
0511#            text = ctypes.create_unicode_buffer(buffer_size)
0512#
0513#            # update the structure and get the text info
0514#            menu_info.dwTypeData = ctypes.addressof(text)
0515#            menu_info.cch = buffer_size
0516#            win32functions.GetMenuItemInfo (
0517#                menu_handle, i, True, ctypes.byref(menu_info))
0518#            item_prop['Text'] = text.value
0519#        else:
0520#            item_prop['Text'] = ""
0521#
0522#        # if it's a sub menu then get it's items
0523#        if menu_info.hSubMenu:
0524#            # make sure that the app updates the menu if it need to
0525#            ctrl.SendMessage(
0526#                win32defines.WM_INITMENUPOPUP, menu_info.hSubMenu, i)
0527#
0528#            #ctrl.SendMessage(
0529#            #    win32defines.WM_INITMENU, menu_info.hSubMenu, )
0530#
0531#            # get the sub menu items
0532#            sub_menu_items = _GetMenuItems(menu_info.hSubMenu, ctrl)
0533#
0534#            # append them
0535#            item_prop['MenuItems'] = sub_menu_items
0536#
0537#        items.append(item_prop)
0538#
0539#    return items
0540#