pyWinAuto: c:\.projects\py_pywinauto\pywinauto\tests\truncation.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# pylint: disable-msg=W0611
0022
0023"""Truncation Test
0024
0025**What is checked**
0026Checks for controls where the text does not fit in the space provided by the
0027control.
0028
0029**How is it checked**
0030There is a function in windows (DrawText) that allows us to find the size that
0031certain text will need. We use this function with correct fonts and other
0032relevant information for the control to be as accurate as possible.
0033
0034**When is a bug reported**
0035When the calculated required size for the text is greater than the size of the
0036space available for displaying the text.
0037
0038**Bug Extra Information**
0039The bug contains the following extra information
0040Name    Description
0041Strings         The list of the truncated strings as explained above
0042StringIndices           The list of indices (0 based) that are truncated. This
0043will often just be 0 but if there are many strings in the control untranslated
0044it will report ALL the strings e.g. 0,2,5,19,23
0045
0046
0047**Is Reference dialog needed**
0048The reference dialog does not need to be available. If it is available then
0049for each bug discovered it is checked to see if it is a problem in the
0050reference dialog.
0051
0052**False positive bug reports**
0053Certain controls do not display the text that is the title of the control, if
0054this is not handled in a standard manner by the software then DLGCheck will
0055report that the string is truncated.
0056
0057**Test Identifier**
0058The identifier for this test/bug is "Truncation"
0059"""
0060
0061__revision__ = "$Revision: 286 $"
0062
0063testname = "Truncation"
0064
0065import ctypes
0066
0067from pywinauto import win32defines
0068from pywinauto import win32functions
0069from pywinauto.win32structures import RECT
0070
0071
0072#==============================================================================
0073def TruncationTest(windows):
0074    "Actually do the test"
0075
0076    truncations = []
0077
0078    # for each of the windows in the dialog
0079    for win in windows:
0080
0081        truncIdxs, truncStrings = _FindTruncations(win)
0082
0083        isInRef = -1
0084
0085        # if there were any truncations for this control
0086        if truncIdxs:
0087
0088            # now that we know there was at least one truncation
0089            # check if the reference control has truncations
0090            if win.ref:
0091                isInRef = 0
0092                refTruncIdxs, refTruncStrings = _FindTruncations(win.ref)
0093
0094                if refTruncIdxs:
0095                    isInRef = 1
0096
0097            truncIdxs = ",".join([unicode(index) for index in truncIdxs])
0098            truncStrings = '"%s"' % ",".join(
0099                [unicode(string) for string in truncStrings])
0100            truncations.append((
0101                [win,],
0102                {
0103                    "StringIndices": truncIdxs,
0104                    "Strings": truncStrings,
0105                },
0106                testname,
0107                isInRef)
0108            )
0109
0110    # return all the truncations
0111    return truncations
0112
0113#==============================================================================
0114def _FindTruncations(ctrl):
0115    "Return the index of the texts that are truncated for this control"
0116    truncIdxs = []
0117    truncStrings = []
0118
0119    # for each of the titles this dialog
0120    for idx, (text, rect, font, flags) in enumerate(_GetTruncationInfo(ctrl)):
0121
0122        # skip if there is no text
0123        if not text:
0124            continue
0125
0126        # get the minimum rectangle
0127        minRect = _GetMinimumRect(text, font, rect, flags)
0128
0129        # if the min rectangle is bigger than the rectangle of the
0130        # object
0131        if minRect.right > rect.right or               minRect.bottom > rect.bottom:
0133
0134            # append the index and the rectangle to list of bug items
0135            truncIdxs.append(idx)
0136            truncStrings.append(text)
0137
0138    return truncIdxs, truncStrings
0139
0140
0141
0142#==============================================================================
0143def _GetMinimumRect(text, font, usableRect, drawFlags):
0144    """Return the minimum rectangle that the text will fit into
0145
0146    Uses font, usableRect and drawFlags information to find how
0147    how to do it accurately
0148    """
0149
0150    # try to create the font
0151    # create a Display DC (compatible to the screen)
0152    txtDC = win32functions.CreateDC(u"DISPLAY", None, None, None )
0153
0154    hFontGUI = win32functions.CreateFontIndirect(ctypes.byref(font))
0155
0156    # Maybe we could not get the font or we got the system font
0157    if not hFontGUI:
0158
0159        # So just get the default system font
0160        hFontGUI = win32functions.GetStockObject(win32defines.DEFAULT_GUI_FONT)
0161
0162        # if we still don't have a font!
0163        # ----- ie, we're on an antiquated OS, like NT 3.51
0164        if not hFontGUI:
0165
0166            # ----- On Asian platforms, ANSI font won't show.
0167            if win32functions.GetSystemMetrics(win32defines.SM_DBCSENABLED):
0168                # ----- was...(SYSTEM_FONT)
0169                hFontGUI = win32functions.GetStockObject(
0170                    win32defines.SYSTEM_FONT)
0171            else:
0172                # ----- was...(SYSTEM_FONT)
0173                hFontGUI = win32functions.GetStockObject(
0174                    win32defines.ANSI_VAR_FONT)
0175
0176
0177    # put our font into the Device Context
0178    win32functions.SelectObject (txtDC, hFontGUI)
0179
0180
0181    modifiedRect = RECT(usableRect)
0182    # Now write the text to our DC with our font to get the
0183    # rectangle that the text needs to fit in
0184    win32functions.DrawText (txtDC, # The DC
0185        unicode(text),          # The Title of the control
0186        -1,                     # -1 because sTitle is NULL terminated
0187        ctypes.byref(modifiedRect),     # The Rectangle to be calculated to
0188        #truncCtrlData.drawTextFormat |
0189        win32defines.DT_CALCRECT | drawFlags)
0190
0191    #elif modifiedRect.right == usableRect.right and \
0192    #   modifiedRect.bottom == usableRect.bottom:
0193    #   print "Oh so you thought you were perfect!!!"
0194
0195
0196    # Delete the font we created
0197    win32functions.DeleteObject(hFontGUI)
0198
0199    # delete the Display context that we created
0200    win32functions.DeleteDC(txtDC)
0201
0202    return modifiedRect
0203
0204
0205
0206
0207#==============================================================================
0208def _ButtonTruncInfo(win):
0209    "Return truncation information specific to Button controls"
0210    lineFormat = win32defines.DT_SINGLELINE
0211
0212    widthAdj = 0
0213    heightAdj = 0
0214
0215    # get the last byte of the style
0216    buttonStyle = win.Style() & 0xF
0217
0218    if win.HasStyle(win32defines.BS_MULTILINE):
0219        lineFormat = win32defines.DT_WORDBREAK
0220
0221    if buttonStyle == win32defines.BS_PUSHBUTTON:
0222        heightAdj = 4
0223        widthAdj = 5
0224
0225    elif win.HasStyle(win32defines.BS_PUSHLIKE):
0226        widthAdj = 3
0227        heightAdj = 3 # 3
0228        if win.HasStyle(win32defines.BS_MULTILINE):
0229            widthAdj = 9
0230            heightAdj = 2 # 3
0231
0232    elif buttonStyle == win32defines.BS_CHECKBOX or           buttonStyle == win32defines.BS_AUTOCHECKBOX:
0234        widthAdj = 18
0235
0236    elif buttonStyle == win32defines.BS_RADIOBUTTON or           buttonStyle == win32defines.BS_AUTORADIOBUTTON:
0238        widthAdj = 19
0239
0240    elif buttonStyle == win32defines.BS_GROUPBOX:
0241        heightAdj = 4
0242        widthAdj = 9
0243        lineFormat = win32defines.DT_SINGLELINE
0244
0245    # don't check image controls for truncation!
0246    # we do this by specifying huge adjustments
0247    # maybe a better/more pythonic way of doing this would be
0248    # to set some special lineFormat (None or something?)
0249    if win.HasStyle(win32defines.BS_BITMAP) or           win.HasStyle(win32defines.BS_ICON):
0251        heightAdj = -9000
0252        widthAdj = -9000
0253        lineFormat = win32defines.DT_WORDBREAK
0254
0255    newRect = win.ClientRects()[0]
0256    newRect.right -=  widthAdj
0257    newRect.bottom -=  heightAdj
0258
0259    return [(win.WindowText(), newRect, win.Font(), lineFormat), ]
0260
0261#==============================================================================
0262def _ComboBoxTruncInfo(win):
0263    "Return truncation information specific to ComboBox controls"
0264    # canot wrap and never had a hotkey
0265    lineFormat = win32defines.DT_SINGLELINE | win32defines.DT_NOPREFIX
0266
0267    if win.HasStyle(win32defines.CBS_DROPDOWN) or           win.HasStyle(win32defines.CBS_DROPDOWNLIST):
0269        widthAdj = 2#5
0270    else:
0271        widthAdj = 3
0272
0273    truncData = []
0274    for title in win.Texts():
0275        newRect = win.ClientRects()[0]
0276        newRect.right -= widthAdj
0277        truncData.append((title, newRect, win.Font(), lineFormat))
0278
0279    return truncData
0280
0281#==============================================================================
0282def _ComboLBoxTruncInfo(win):
0283    "Return truncation information specific to ComboLBox controls"
0284    # canot wrap and never had a hotkey
0285    lineFormat = win32defines.DT_SINGLELINE | win32defines.DT_NOPREFIX
0286
0287    truncData = []
0288    for title in win.Texts():
0289        newRect = win.ClientRects()[0]
0290        newRect.right -= 5
0291        truncData.append((title, newRect, win.Font(), lineFormat))
0292
0293    return truncData
0294
0295
0296#==============================================================================
0297def _ListBoxTruncInfo(win):
0298    "Return truncation information specific to ListBox controls"
0299    # canot wrap and never had a hotkey
0300    lineFormat = win32defines.DT_SINGLELINE | win32defines.DT_NOPREFIX
0301
0302    truncData = []
0303    for title in win.Texts():
0304        newRect = win.ClientRects()[0]
0305        newRect.right -= 2
0306        newRect.bottom -= 1
0307        truncData.append((title, newRect, win.Font(), lineFormat))
0308
0309    return truncData
0310
0311
0312#==============================================================================
0313def _StaticTruncInfo(win):
0314    "Return truncation information specific to Static controls"
0315    lineFormat = win32defines.DT_WORDBREAK
0316
0317    if win.HasStyle(win32defines.SS_CENTERIMAGE) or           win.HasStyle(win32defines.SS_SIMPLE) or           win.HasStyle(win32defines.SS_LEFTNOWORDWRAP):
0320
0321        lineFormat = win32defines.DT_SINGLELINE
0322
0323    if win.HasStyle(win32defines.SS_NOPREFIX):
0324        lineFormat |= win32defines.DT_NOPREFIX
0325
0326    return [(win.WindowText(), win.ClientRects()[0], win.Font(), lineFormat), ]
0327
0328#==============================================================================
0329def _EditTruncInfo(win):
0330    "Return truncation information specific to Edit controls"
0331    lineFormat = win32defines.DT_WORDBREAK | win32defines.DT_NOPREFIX
0332
0333    if not win.HasStyle(win32defines.ES_MULTILINE):
0334        lineFormat |= win32defines.DT_SINGLELINE
0335
0336    return [(win.WindowText(), win.ClientRects()[0], win.Font(), lineFormat), ]
0337
0338
0339#==============================================================================
0340def _DialogTruncInfo(win):
0341    "Return truncation information specific to Header controls"
0342    # move it down more into range
0343
0344    newRect = win.ClientRects()[0]
0345
0346    newRect.top += 5
0347    newRect.left += 5
0348    newRect.right -= 5
0349
0350
0351    if win.HasStyle(win32defines.WS_THICKFRAME):
0352        newRect.top += 1
0353        newRect.left += 1
0354        newRect.right -= 1
0355
0356    # if it has the system menu but is a small caption
0357    # then the only button it can have is the close button
0358    if win.HasStyle(win32defines.WS_SYSMENU) and           (win.HasExStyle(win32defines.WS_EX_PALETTEWINDOW) or
0360        win.HasExStyle(win32defines.WS_EX_TOOLWINDOW)):
0361        newRect.right -= 15
0362
0363
0364    # all the rest only need to be considered if there is a system menu.
0365    elif win.HasStyle(win32defines.WS_SYSMENU):
0366        buttons = []
0367        # account for the close button
0368        newRect.right -= 18
0369        buttons.append('close')
0370
0371        # account for Icon if it is not disabled
0372        if not win.HasExStyle(win32defines.WS_EX_DLGMODALFRAME):
0373            newRect.left += 19 # icon
0374
0375
0376        # account for context sensitive help if set
0377        if win.HasExStyle(win32defines.WS_EX_CONTEXTHELP) and not (               win.HasStyle(win32defines.WS_MAXIMIZEBOX) and               win.HasStyle(win32defines.WS_MINIMIZEBOX)):
0380
0381            newRect.right -= 17
0382
0383            # there is a bigger gap if the minimize box is there
0384            if win.HasStyle(win32defines.WS_MINIMIZEBOX) or                   win.HasStyle(win32defines.WS_MAXIMIZEBOX) or                   win.HasStyle(win32defines.WS_GROUP):
0387                newRect.right -= 3
0388
0389            buttons.append('help')
0390
0391
0392        # account for Maximize button (but skip if WS_GROUP is set
0393        if win.HasStyle(win32defines.WS_MINIMIZEBOX) or               win.HasStyle(win32defines.WS_MAXIMIZEBOX) or               win.HasStyle(win32defines.WS_GROUP):
0396
0397            newRect.right -= 32
0398            buttons.append('min')
0399            buttons.append('max')
0400
0401        if buttons:
0402            # space between first button and dialog edge
0403            diff = 5
0404
0405            # space for each button
0406            diff += len(buttons) * 16
0407
0408            # space between close and next button
0409            if len(buttons) > 1:
0410                diff += 2
0411
0412            # extra space between help and min buttons
0413            if 'min' in buttons and 'help' in buttons:
0414                diff += 4
0415
0416    return [(win.WindowText(), newRect, win.Font(), win32defines.DT_SINGLELINE), ]
0417
0418
0419#==============================================================================
0420def _StatusBarTruncInfo(win):
0421    "Return truncation information specific to StatusBar controls"
0422    truncInfo = _WindowTruncInfo(win)
0423    for i, (title, rect, font, flag) in enumerate(truncInfo):
0424
0425        rect.bottom -= win.VertBorderWidth
0426        if i == 0:
0427            rect.right -= win.HorizBorderWidth
0428        else:
0429            rect.right -= win.InterBorderWidth
0430
0431    return truncInfo
0432
0433#==============================================================================
0434def _HeaderTruncInfo(win):
0435    "Return truncation information specific to Header controls"
0436    truncInfo = _WindowTruncInfo(win)
0437
0438    for i, (title, rect, font, flag) in enumerate(truncInfo):
0439        # only modify the header rectangle
0440        rect.right -= 12
0441
0442    return truncInfo
0443
0444
0445
0446
0447
0448#==============================================================================
0449def _WindowTruncInfo(win):
0450    "Return Default truncation information"
0451    matchedItems = []
0452
0453    for i, title in enumerate(win.Texts()):
0454
0455        # Use the client rects for rectangles
0456        if i < len(win.ClientRects()):
0457            rect = win.ClientRects()[i]
0458        else:
0459            # until we run out then just use the first 'main' client rectangle
0460            rect = win.ClientRects()[0]
0461
0462        # if we have fewer fonts than titles
0463        if len(win.Fonts())-1 < i:
0464            font = win.Font()
0465        else:
0466            font = win.Fonts()[i]
0467
0468        # add the item
0469        matchedItems.append(
0470            (title, rect, font, win32defines.DT_SINGLELINE))
0471
0472    return matchedItems
0473
0474
0475
0476#==============================================================================
0477_TruncInfo = {
0478    "#32770" : _DialogTruncInfo,
0479    "ComboBox" : _ComboBoxTruncInfo,
0480    "ComboLBox" : _ComboLBoxTruncInfo,
0481    "ListBox" : _ListBoxTruncInfo,
0482    "Button" : _ButtonTruncInfo,
0483    "Edit": _EditTruncInfo,
0484    "Static" : _StaticTruncInfo,
0485
0486#       "msctls_statusbar32" : StatusBarTruncInfo,
0487#       "HSStatusBar" : StatusBarTruncInfo,
0488#       "SysHeader32" : HeaderTruncInfo,
0489
0490#       "SysListView32" :  ListViewTruncInfo,
0491    #"SysTreeView32" :
0492}
0493
0494#==============================================================================
0495def _GetTruncationInfo(win):
0496    "helper function to hide non special windows"
0497    if win.Class() in _TruncInfo:
0498        return _TruncInfo[win.Class()](win)
0499    else:
0500
0501        return _WindowTruncInfo(win)