home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / msysjour / vol04 / 01b / macsl / sleuth.c < prev    next >
C/C++ Source or Header  |  1988-10-26  |  27KB  |  838 lines

  1. /*-----------------------------------------------------------------*/
  2. /* Sleuth.c                                                        */
  3. /* Window snooper for Presentation Manager                         */
  4. /*-----------------------------------------------------------------*/
  5.  
  6. #include "MyOs2.h"
  7.  
  8. #undef NULL
  9.  
  10. #include "Machine.h"
  11.  
  12. #include <malloc.h>
  13. #include <stdarg.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17.  
  18. #include "Sleuth.h"
  19.  
  20. /*-----------------------------------------------------------------*/
  21. /*  The display for a window looks like this in collapsed mode:    */
  22. /*                                                                 */
  23. /*  Window HHHH:HHHH [id] {class} (L,B;R,T) "text"                 */
  24. /*                                                                 */
  25. /*  or like this in expanded mode:                                 */
  26. /*                                                                 */
  27. /*      Window handle: HHHH:HHHH   Owner window: HHHH:HHHH         */
  28. /*        Class name: {class name}                                 */
  29. /*        Window text: "text"                                      */
  30. /*        Class style:  HHHHHHHH                                   */
  31. /*        Window style: HHHHHHHH                                   */
  32. /*        Class function: HHHH:HHHH   Window function: HHHH:HHHH   */
  33. /*        Window ID: DDDDD   Process ID: HHHH   Thread ID: HHHH    */
  34. /*        Message queue handle: HHHH   Window lock count: DDDDD    */
  35. /*        Window extra alloc: DDDDD                                */
  36. /*        Window rectangle: Left=D, Bottom=D, Right=D, Top=D       */
  37. /*      {blank line}                                               */
  38. /*                                                                 */
  39. /*  Total number of lines for one window display: 11               */
  40. /*-----------------------------------------------------------------*/
  41.  
  42. #define LINES_PER_WINDOW    11
  43. #define WINDOW_WIDTH        160
  44.  
  45. /*-----------------------------------------------------------------*/
  46. /* Structure of information for each window.                       */
  47. /*-----------------------------------------------------------------*/
  48.  
  49. #define CLASSMAX    40
  50. #define TEXTMAX     40
  51.  
  52. typedef struct {
  53.     HWND    hwnd;                   /* Window handle */
  54.     CLASSINFO class;                /* Class info */
  55.     CHAR    szClass[CLASSMAX];      /* Class name */
  56.     CHAR    szText[TEXTMAX];        /* Window title or contents */
  57.     ULONG   flStyle;                /* WS_ window style */
  58.     HWND    hwndOwner;              /* Owner window handle */
  59.     PFNWP   pfnwp;                  /* Window function */
  60.     USHORT  usWindowID;             /* Window ID */
  61.     PID     ProcessID;              /* Process ID */
  62.     TID     ThreadID;               /* Thread ID */
  63.     HMQ     hmq;                    /* Message queue handle */
  64.     RECTL   rclWindow;              /* Window rect, screen coord. */
  65.     SHORT   sLockCount;             /* Window lock count */
  66.     SHORT   sLevel;                 /* Child window nesting level */
  67.     FLAG    fSelect:1;              /* Is this window selected? */
  68.     FLAG    fHasText:1;             /* Does the window have text? */
  69. } INFO;
  70.  
  71. typedef INFO * PINFO;               /* Pointer to INFO array */
  72.  
  73. /*-----------------------------------------------------------------*/
  74. /* Static variables.                                               */
  75. /*-----------------------------------------------------------------*/
  76.  
  77. PIDINFO     pidi;
  78.  
  79. HAB         hab;
  80. HMQ         hmq;
  81. HWND        hwndDesktop, hwndObject;
  82. HWND        hwndSleuth, hwndFrame, hwndHorzScroll, hwndVertScroll;
  83.  
  84. PINFO       pInfoBase;
  85. SHORT       sWinCount;              /* # of windows in system */
  86. BOOL        fExpand = FALSE;        /* Expanded display mode? */
  87. SHORT       sLinesEach = 1;         /* 1 or LINES_PER_WINDOW */
  88. SHORT       sCharX;                 /* Character width in pixels */
  89. SHORT       sCharY;                 /* Character height in pixels */
  90. SHORT       sDescenderY;            /* Descender height */
  91. SHORT       sExtLeading;            /* Vert. space between chars */
  92. HPS         hpsPaint;               /* PS for SleuthPaint */
  93. POINTL      ptlPaint;               /* Current pos for SleuthPaint */
  94. CHAR        szClass[10];            /* Our window class name */
  95. CHAR        szTitle[40];            /* Our window title */
  96.  
  97. /*-----------------------------------------------------------------*/
  98. /* Function prototypes.                                            */
  99. /*-----------------------------------------------------------------*/
  100.  
  101. VOID                main( VOID );
  102. PSZ                 SleuthClassName( PSZ );
  103. BOOL                SleuthGetAllWindows( VOID );
  104. VOID                SleuthGetWindowTree( HWND hwnd, SHORT sLevel );
  105. BOOL                SleuthInit( VOID );
  106. PSZ                 SleuthMsgName( USHORT );
  107. VOID                SleuthPaint( HWND hwndPaint );
  108. VOID      CDECL     SleuthPaint1( CHAR* szFormat, ... );
  109. VOID                SleuthQueryScrollBar( HWND hwndBar, SHORT* psPos,
  110.                                           SHORT* psMin,
  111.                                           SHORT* psMax );
  112. SHORT               SleuthScroll( HWND hwnd, USHORT msg,
  113.                                   USHORT idBar,
  114.                                   USHORT cmd, SHORT sPos );
  115. VOID                SleuthSetOneBar( HWND hwndBar, SHORT sMax );
  116. VOID                SleuthSetScrollBars( VOID );
  117. VOID                SleuthTerminate( VOID );
  118. MRESULT   EXPENTRY  SleuthWndProc( HWND, USHORT, MPARAM, MPARAM );
  119.  
  120. /*-----------------------------------------------------------------*/
  121. /* Application main program.                                       */
  122. /*-----------------------------------------------------------------*/
  123.  
  124. VOID main()
  125. {
  126.     QMSG        qmsg;
  127.  
  128.     /* Initialize application, quit if any errors */
  129.  
  130.     if( ! SleuthInit() )
  131.       return;
  132.  
  133.     /* Main message processing loop */
  134.  
  135.     while( WinGetMsg( hab, &qmsg, NULL, 0, 0 ) )
  136.       WinDispatchMsg( hab, &qmsg );
  137.  
  138.     /* Application termination */
  139.  
  140.     SleuthTerminate();
  141. }
  142.  
  143. /*-----------------------------------------------------------------*/
  144. /* Convert a class name into its printable form.  Normal class     */
  145. /* names are returned unchanged; the special WC_ "names" are       */
  146. /* converted into text.                                            */
  147. /*-----------------------------------------------------------------*/
  148.  
  149. typedef struct _CLASSNAMES {
  150.     NPSZ        szNum;
  151.     NPSZ        szName;
  152. } CLASSNAMES;
  153.  
  154. typedef CLASSNAMES NEAR * NPCLASSNAMES;
  155.  
  156. CLASSNAMES aClassNames[] = {
  157.     { "#1",  "WC_FRAME"      },
  158.     { "#2",  "WC_DIALOG"     },
  159.     { "#3",  "WC_BUTTON"     },
  160.     { "#4",  "WC_MENU"       },
  161.     { "#5",  "WC_STATIC"     },
  162.     { "#6",  "WC_ENTRYFIELD" },
  163.     { "#7",  "WC_LISTBOX"    },
  164.     { "#8",  "WC_SCROLLBAR"  },
  165.     { "#9",  "WC_TITLEBAR"   },
  166.     { "#10", "WC_SIZEBORDER" },
  167.     { NULL,  NULL            }
  168. };
  169.  
  170. PSZ SleuthClassName( pszClass )
  171.     PSZ         pszClass;
  172. {
  173.     NPCLASSNAMES npNames;
  174.  
  175.     if( pszClass[0] != '#' )
  176.       return pszClass;
  177.  
  178.     for( npNames = &aClassNames[0];  npNames->szNum;  npNames++ )
  179.       if( strcmp( pszClass, npNames->szNum ) == 0 )
  180.         return npNames->szName;
  181.  
  182.     return pszClass;
  183. }
  184.  
  185. /*-----------------------------------------------------------------*/
  186. /* Gather up information on all windows in PM and fill in the      */
  187. /* INFO structure for them.                                        */
  188. /*-----------------------------------------------------------------*/
  189.  
  190. BOOL SleuthGetAllWindows()
  191. {
  192.     sWinCount = 0;
  193.  
  194.     /* Pick up both trees, from hwndDesktop and hwndObject */
  195.  
  196.     SleuthGetWindowTree( hwndDesktop, 0 );
  197.     SleuthGetWindowTree( hwndObject,  0 );
  198.  
  199.     /* Set scroll bars based on new window count */
  200.  
  201.     SleuthSetScrollBars();
  202.  
  203.     /* Force our window to be repainted */
  204.  
  205.     WinInvalidateRect( hwndSleuth, NULL, TRUE );
  206.  
  207.     return TRUE;
  208. }
  209.  
  210. /*-----------------------------------------------------------------*/
  211. /* Gather information on all windows in the tree starting at hwnd, */
  212. /* and add an entry to the INFO array for each one.                */
  213. /*-----------------------------------------------------------------*/
  214.  
  215. VOID SleuthGetWindowTree( hwnd, sLevel )
  216.     HWND        hwnd;
  217.     SHORT       sLevel;
  218. {
  219.     PINFO       pInfo;
  220. /*  HENUM       henum;  */
  221.     HWND        hwndChild;
  222.  
  223.     /* Count the window and allocate an INFO entry */
  224.  
  225.     sWinCount++;
  226.  
  227.     if( ! pInfoBase )
  228.       pInfoBase = malloc( sizeof(INFO) );
  229.     else
  230.       pInfoBase = realloc( pInfoBase, sWinCount*sizeof(INFO) );
  231.  
  232.     if( ! pInfoBase )
  233.       exit( 9 );
  234.  
  235.     pInfo = pInfoBase + sWinCount - 1;  /* -> INFO for this window */
  236.  
  237.     /* Gather up this window's information */
  238.  
  239.     pInfo->hwnd = hwnd;
  240.     pInfo->fSelect = FALSE;
  241.     pInfo->fHasText = FALSE;
  242.     pInfo->class.flClassStyle  = 0L;
  243.     pInfo->class.pfnWindowProc = 0L;
  244.     pInfo->class.cbWindowData  = 0;
  245.  
  246.     pInfo->flStyle = WinQueryWindowULong( hwnd, QWL_STYLE );
  247.     pInfo->hwndOwner = WinQueryWindow( hwnd, QW_OWNER, FALSE );
  248.     pInfo->pfnwp = WinQueryWindowPtr( hwnd, QWP_PFNWP );
  249.     pInfo->usWindowID = WinQueryWindowUShort( hwnd, QWS_ID );
  250.     WinQueryWindowProcess( hwnd, &pInfo->ProcessID,
  251.                                  &pInfo->ThreadID );
  252.     pInfo->hmq = WinQueryWindowPtr( hwnd, QWL_HMQ );
  253.     WinQueryWindowRect( hwnd, &pInfo->rclWindow );
  254.     pInfo->sLockCount = WinQueryWindowLockCount( hwnd );
  255.     pInfo->sLevel = sLevel;
  256.  
  257.     if( hwnd == hwndDesktop )
  258.       strcpy( pInfo->szClass, "WC_DESKTOP" );
  259.     else if( hwnd == hwndObject )
  260.       strcpy( pInfo->szClass, "WC_OBJECT" );
  261.     else
  262.     {
  263.       WinQueryClassName( hwnd, sizeof(pInfo->szClass),
  264.                          pInfo->szClass );
  265.       WinQueryClassInfo( hab, pInfo->szClass, &pInfo->class );
  266.       if( ! WinIsRectEmpty( hab, &pInfo->rclWindow ) )
  267.         WinMapWindowRect( hwnd, WinQueryWindow(hwnd,QW_PARENT,FALSE),
  268.                           &pInfo->rclWindow );
  269.     }
  270.  
  271.     pInfo->szText[0] = '\0';
  272.     if( pInfo->ProcessID == pidi.pid )
  273.       pInfo->fHasText =  /* wrong... */
  274.         !! WinQueryWindowText( hwnd, sizeof(pInfo->szText),
  275.                                pInfo->szText );
  276.  
  277.     /* Recurse through all child windows.  The #if'd out code is the
  278.        "right" way to do this, but it crashed PM in the preliminary
  279.        version I used.  The other code works OK, but doesn't protect
  280.        against windows being destroyed by other threads while we're
  281.        executing! */
  282.  
  283. #if 0
  284.     henum = WinBeginEnumWindows( hwnd );
  285.  
  286.     while( hwndChild = WinGetNextWindow(henum) )
  287.       SleuthGetWindowTree( hwndChild, sLevel+1 );
  288.  
  289.     WinEndEnumWindows( henum );
  290. #else
  291.     for( hwndChild = WinQueryWindow( hwnd, QW_TOP, FALSE );
  292.          hwndChild;
  293.          hwndChild = WinQueryWindow( hwndChild, QW_NEXT, FALSE ) )
  294.       SleuthGetWindowTree( hwndChild, sLevel+1 );
  295. #endif
  296. }
  297.  
  298. /*-----------------------------------------------------------------*/
  299. /* Initialize the application.                                     */
  300. /*-----------------------------------------------------------------*/
  301.  
  302. BOOL SleuthInit()
  303. {
  304.     HDC         hps;
  305.     FONTMETRICS fm;
  306.     SHORT       sScreenX;
  307.     SHORT       sScreenY;
  308.     ULONG       flFrameFlags;
  309.  
  310.     /* Pick up the basic information we need */
  311.  
  312.     DosGetPID( &pidi );
  313.  
  314.     hab = WinInitialize( 0 );
  315.     hmq = WinCreateMsgQueue( hab, 0 );
  316.  
  317.     hwndDesktop = WinQueryDesktopWindow( hab, NULL );
  318.     hwndObject  = WinQueryObjectWindow( hwndDesktop );
  319.  
  320.     sScreenX = (SHORT)WinQuerySysValue( hwndDesktop, SV_CXSCREEN );
  321.     sScreenY = (SHORT)WinQuerySysValue( hwndDesktop, SV_CYSCREEN );
  322.  
  323.     /* Calculate character size for system font */
  324.  
  325.     hps = WinGetPS( hwndDesktop );
  326.  
  327.     GpiQueryFontMetrics( hps, (LONG)sizeof(fm), &fm );
  328.  
  329.     sCharX = (SHORT)fm.lAveCharWidth;
  330.     sCharY = (SHORT)fm.lMaxBaselineExt;
  331.     sDescenderY = (SHORT)fm.lMaxDescender;
  332.  
  333.     WinReleasePS( hps );
  334.  
  335.     /* Load strings from resource file */
  336.  
  337.     WinLoadString( hab, NULL, IDS_CLASS, sizeof(szClass), szClass );
  338.     WinLoadString( hab, NULL, IDS_TITLE, sizeof(szTitle), szTitle );
  339.  
  340.     /* Register our window class and create main window */
  341.  
  342.     WinRegisterClass( hab, szClass, SleuthWndProc, 0L, 0 );
  343.  
  344. /* original ------
  345.  
  346.     flFrameFlags = FCF_STANDARD | FCF_VERTSCROLL | FCF_HORZSCROLL;
  347.     hwndFrame =
  348.       WinCreateStdWindow( hwndDesktop, FS_ICON, &flFrameFlags,
  349.                           szClass, szTitle, 0L, NULL,
  350.                           ID_SLEUTH, &hwndSleuth );
  351.  
  352. ------- original - change made by Charles Petzold */
  353.  
  354.         flFrameFlags = FCF_SIZEBORDER | FCF_TITLEBAR | 
  355.                         FCF_MINMAX | FCF_SYSMENU | FCF_MENU | FCF_ICON | 
  356.                         FCF_VERTSCROLL | FCF_HORZSCROLL;
  357.         hwndFrame =
  358.         WinCreateStdWindow (hwndDesktop, WS_VISIBLE, &flFrameFlags,
  359.                                 szClass, szTitle, 0L, NULL,
  360.                                 ID_SLEUTH, &hwndSleuth) ;
  361.  
  362.     hwndHorzScroll = WinWindowFromID( hwndFrame, FID_HORZSCROLL );
  363.     hwndVertScroll = WinWindowFromID( hwndFrame, FID_VERTSCROLL );
  364.  
  365.     /* Set the window position.  Change the #if 1 to #if 0 for more
  366.        convenient debugging under Lightspeed's debugger.  This will
  367.        put our window in the top part of the screen, up above the
  368.        debugger windows. */
  369.  
  370. #if 1
  371.     WinSetWindowPos( hwndFrame, NULL,
  372.                      sScreenX *  1 / 20,     /* X: 5% from left */
  373.                      sScreenY *  2 / 10,     /* Y  20% from bottom */
  374.                      sScreenX *  9 / 10,     /* nWidth: 90% */
  375.                      sScreenY *  7 / 10,     /* nHeight: 70% */
  376.                      SWP_MOVE | SWP_SIZE );
  377. #else
  378.     WinSetWindowPos( hwndFrame, NULL,
  379.                      sScreenX *  1 / 10,     /* X: 10% from left */
  380.                      sScreenY *  6 / 10,     /* Y  60% from bottom */
  381.                      sScreenX *  8 / 10,     /* nWidth: 80% */
  382.                      sScreenY *  3 / 10,     /* nHeight: 30% */
  383.                      SWP_MOVE | SWP_SIZE );
  384. #endif
  385.  
  386.     /* Make our window visible now, so it's included in the list */
  387.  
  388.     WinShowWindow( hwndFrame, TRUE );
  389.  
  390.     /* Post a message to ourself to trigger the first  display */
  391.  
  392.     WinPostMsg( hwndSleuth, WM_COMMAND, MPFROMSHORT(CMD_LOOK), 0L );
  393.  
  394.     return TRUE;
  395. //  return FALSE;
  396. }
  397.  
  398. /*-----------------------------------------------------------------*/
  399. /* Paint our window.                                               */
  400. /*-----------------------------------------------------------------*/
  401.  
  402. VOID SleuthPaint( hwnd )
  403.     HWND        hwnd;
  404. {
  405.     SHORT       sWin;
  406.     SHORT       X;
  407.     SHORT       sScrollY, sScrollX;
  408.     RECTL       rclPaint, rclClient;
  409.     PINFO       pInfo;
  410.     CHAR        szQuote[2];
  411.  
  412.     /* Get the PS and erase it */
  413.  
  414.     hpsPaint = WinBeginPaint( hwnd, NULL, &rclPaint );
  415.  
  416.     GpiErase( hpsPaint );
  417.  
  418.     /* Find out how big the window is and how it's scrolled */
  419.  
  420.     WinQueryWindowRect( hwnd, &rclClient );
  421.     sScrollX =
  422.       (SHORT)WinSendMsg( hwndHorzScroll, SBM_QUERYPOS, 0, 0 );
  423.     sScrollY =
  424.       (SHORT)WinSendMsg( hwndVertScroll, SBM_QUERYPOS, 0, 0 );
  425.  
  426.     /* Calculate horizontal paint pos from scroll bar pos */
  427.  
  428.     X = /* ( 1 */ - sScrollX /* ) */ * sCharX;
  429.  
  430.     /* Calculate index into INFO array and vertical paint pos,
  431.        from scroll bar pos and top of painting rectangle */
  432.  
  433.     sWin =
  434.       ( ( (SHORT)rclClient.yTop - (SHORT)rclPaint.yTop ) / sCharY
  435.         + sScrollY )
  436.       / sLinesEach;
  437.  
  438.     ptlPaint.y =
  439.       (SHORT)rclClient.yTop + sDescenderY
  440.         - ( sWin * sLinesEach - sScrollY + 1 ) * sCharY;
  441.  
  442.     pInfo = pInfoBase + sWin;
  443.  
  444.     /* Loop through and paint each entry */
  445.  
  446.     while( sWin < sWinCount  &&
  447.            (SHORT)ptlPaint.y + sCharY >= (SHORT)rclPaint.yBottom )
  448.     {
  449.       /* Set X position and indent child windows */
  450.  
  451.       ptlPaint.x = X + pInfo->sLevel * sCharX * (fExpand ? 4 : 2);
  452.  
  453.       szQuote[0] = szQuote[1] = '\0';
  454.       if( pInfo->fHasText )
  455.         szQuote[0] = '"';
  456.  
  457.       if( ! fExpand )
  458.       {
  459.         /* Paint the one-liner */
  460.  
  461.         SleuthPaint1(
  462.           "%08lX [%04X] {%s} (%d,%d;%d,%d) %s%s%s",
  463.           pInfo->hwnd,
  464.           pInfo->usWindowID,
  465.           SleuthClassName( pInfo->szClass ),
  466.           (INT)pInfo->rclWindow.xLeft,
  467.           (INT)pInfo->rclWindow.yBottom,
  468.           (INT)pInfo->rclWindow.xRight,
  469.           (INT)pInfo->rclWindow.yTop,
  470.           szQuote, pInfo->szText, szQuote
  471.         );
  472.       }
  473.       else
  474.       {
  475. #if 0
  476.         /* Paint the expanded form, first the window handle */
  477.  
  478.         Paint(
  479.           "%s handle: %04X",
  480.           pTypeName,
  481.           lpInfo->winHWnd
  482.         );
  483.  
  484.         /* Paint the rest of the info, indented two spaces more */
  485.  
  486.         sPaintX += sCharX * 2;
  487.  
  488.         Paint( "Class name: %Fs", lpInfo->winClass );
  489.         Paint( "Window title: %Fs", lpInfo->winTitle );
  490.         Paint( "Parent window handle: %04X", lpInfo->winHWndParent );
  491.         Paint(
  492.           "Class function, Window function: %p, %p",
  493.           lpInfo->winClassProc,
  494.           lpInfo->winWndProc
  495.         );
  496.         Paint(
  497.           "Class module handle, Window instance handle: %04X, %04X",
  498.           lpInfo->winClassModule,
  499.           lpInfo->winInstance
  500.         );
  501.         Paint(
  502.           "Class extra alloc, Window extra alloc: %d, %d",
  503.           lpInfo->winClsExtra,
  504.           lpInfo->winWndExtra
  505.         );
  506.         Paint(
  507.           "Class style, Window style: %04X, %08lX",
  508.           lpInfo->winClassStyle,
  509.           lpInfo->winStyle
  510.         );
  511.         Paint(
  512.           lpInfo->winStyle & WS_CHILD ?  "Control ID: %d" :
  513.                                          "Menu handle: %04X",
  514.           lpInfo->winControlID
  515.         );
  516.         Paint(
  517.           "Brush, Cursor, Icon handles: %04X, %04X, %04X",
  518.           lpInfo->winBkgdBrush,
  519.           lpInfo->winCursor,
  520.           lpInfo->winIcon
  521.         );
  522.         Paint(
  523.           "Window rect: Left=%4d, Top=%4d, Right=%4d, Bottom=%4d",
  524.           lpInfo->winWindowRect.left,
  525.           lpInfo->winWindowRect.top,
  526.           lpInfo->winWindowRect.right,
  527.           lpInfo->winWindowRect.bottom
  528.         );
  529.         Paint(
  530.           "Client rect: Left=%4d, Top=%4d, Right=%4d, Bottom=%4d",
  531.           lpInfo->winClientRect.left,
  532.           lpInfo->winClientRect.top,
  533.           lpInfo->winClientRect.right,
  534.           lpInfo->winClientRect.bottom
  535.         );
  536.  
  537.         /* Make a blank line */
  538.  
  539.         sPaintY -= sCharY;
  540. #endif
  541.       }
  542.  
  543.       /* Increment to next INFO entry */
  544.       sWin++;
  545.       pInfo++;
  546.     }
  547.  
  548.     WinEndPaint( hpsPaint );
  549. }
  550.  
  551. /*-----------------------------------------------------------------*/
  552. /* Paint one line of text, using the global variables hpsPaint and */
  553. /* ptlPaint.  The #ifdef PM_MACINTOSH is because Lightspeed C      */
  554. /* doesn't like the ... notation, and Microsoft C doesn't like to  */
  555. /* do without it!                                                  */
  556. /*-----------------------------------------------------------------*/
  557.  
  558. #ifdef PM_MACINTOSH
  559. VOID CDECL SleuthPaint1( szFormat )
  560. #else
  561. VOID CDECL SleuthPaint1( szFormat, ... )
  562. #endif
  563.     CHAR *      szFormat;
  564. {
  565.     va_list     pArgs;
  566.     CHAR        szBuf[160];
  567.  
  568.     va_start( pArgs, szFormat );
  569.  
  570.     GpiCharStringAt(
  571.       hpsPaint, &ptlPaint,
  572.       (LONG)vsprintf( szBuf, szFormat, pArgs ),
  573.       szBuf
  574.     );
  575.  
  576.     va_end( pArgs );
  577.  
  578.     ptlPaint.y -= sCharY;
  579. }
  580.  
  581. /*-----------------------------------------------------------------*/
  582. /* Get a scroll bar's range and position.  More convenient than    */
  583. /* sending the messages every time.                                */
  584. /*-----------------------------------------------------------------*/
  585.  
  586. VOID SleuthQueryScrollBar( hwndBar, psPos, psMin, psMax )
  587.     HWND        hwndBar;
  588.     SHORT*      psPos;
  589.     SHORT*      psMin;
  590.     SHORT*      psMax;
  591. {
  592.     MRESULT     mrRange;
  593.  
  594.     *psPos  = (SHORT)WinSendMsg( hwndBar, SBM_QUERYPOS, 0, 0 );
  595.  
  596.     mrRange = WinSendMsg( hwndBar, SBM_QUERYRANGE, 0, 0 );
  597.     *psMin = SHORT1FROMMR(mrRange);
  598.     *psMax = SHORT2FROMMR(mrRange);
  599. }
  600.  
  601. /*-----------------------------------------------------------------*/
  602. /* Scroll hwnd and adjust idBar according to cmd and sPos.         */
  603. /*-----------------------------------------------------------------*/
  604.  
  605. SHORT SleuthScroll( hwnd, msg, idBar, cmd, sPos )
  606.     HWND        hwnd;
  607.     USHORT      msg;
  608.     USHORT      idBar;
  609.     USHORT      cmd;
  610.     SHORT       sPos;
  611. {
  612.     HWND        hwndBar;
  613.     SHORT       sOldPos;
  614.     SHORT       sDiff;
  615.     SHORT       sMin;
  616.     SHORT       sMax;
  617.     SHORT       sPageSize;
  618.     RECTL       rcl;
  619.  
  620.     /* Get old scroll position and scroll range */
  621.  
  622.     hwndBar =
  623.       WinWindowFromID( WinQueryWindow(hwnd,QW_PARENT,FALSE), idBar );
  624.  
  625.     SleuthQueryScrollBar( hwndBar, &sOldPos, &sMin, &sMax );
  626.  
  627.     /* Calculate page size, horizontal or vertical as needed */
  628.  
  629.     WinQueryWindowRect( hwnd, &rcl );
  630.  
  631.     if( msg == WM_HSCROLL )
  632.       sPageSize = ( (SHORT)rcl.xRight - (SHORT)rcl.xLeft) / sCharX;
  633.     else
  634.       sPageSize = ( (SHORT)rcl.yTop - (SHORT)rcl.yBottom) / sCharY;
  635.  
  636.     /* Select the amount to scroll by, based on the scroll message */
  637.  
  638.     switch( cmd )
  639.     {
  640.       case SB_LINEUP:
  641.         sDiff = -1;
  642.         break;
  643.  
  644.       case SB_LINEDOWN:
  645.         sDiff = 1;
  646.         break;
  647.  
  648.       case SB_PAGEUP:
  649.         sDiff = -sPageSize;
  650.         break;
  651.  
  652.       case SB_PAGEDOWN:
  653.         sDiff = sPageSize;
  654.         break;
  655.  
  656.       case SB_SLIDERPOSITION:
  657.         sDiff = sPos - sOldPos;
  658.         break;
  659.  
  660.       case SBX_TOP:
  661.         sDiff = -30000;  /* Kind of a kludge but it works... */
  662.         break;
  663.  
  664.       case SBX_BOTTOM:
  665.         sDiff = 30000;
  666.         break;
  667.  
  668.       default:
  669.         return 0;
  670.     }
  671.  
  672.     /* Limit scroll destination to sMin..sMax */
  673.  
  674.     if( sDiff < sMin - sOldPos )
  675.       sDiff = sMin - sOldPos;
  676.  
  677.     if( sDiff > sMax - sOldPos )
  678.       sDiff = sMax - sOldPos;
  679.  
  680.     /* Return if net effect is nothing */
  681.  
  682.     if( sDiff == 0 )
  683.       return 0;
  684.  
  685.     /* Set the new scroll bar position and scroll the window */
  686.  
  687.     WinSendMsg( hwndBar, SBM_SETPOS, MRFROMSHORT(sOldPos+sDiff), 0 );
  688.  
  689.     WinScrollWindow(
  690.       hwnd,
  691.       msg == WM_HSCROLL ?  -sDiff*sCharX : 0,
  692.       msg == WM_VSCROLL ?   sDiff*sCharY : 0,
  693.       NULL, NULL, NULL, NULL,
  694.       SW_INVALIDATERGN
  695.     );
  696.  
  697.     /* Force an immediate update for cleaner appearance */
  698.  
  699.     WinUpdateWindow( hwnd );
  700.  
  701.     return sDiff;
  702. }
  703.  
  704. /*-----------------------------------------------------------------*/
  705. /* Set one scroll bar's position and range.                        */
  706. /*-----------------------------------------------------------------*/
  707.  
  708. VOID SleuthSetOneBar( hwndBar, sMax )
  709.     HWND        hwndBar;
  710.     SHORT       sMax;
  711. {
  712.     SHORT       sPos, sOldMin, sOldMax;
  713.  
  714.     SleuthQueryScrollBar( hwndBar, &sPos, &sOldMin, &sOldMax );
  715.  
  716.     if( sMax <= 0 )
  717.       sMax = sPos = 0;
  718.  
  719.     if( sMax != sOldMax )
  720.     {
  721.       WinSendMsg( hwndBar, SBM_SETSCROLLBAR,
  722.                   MPFROMSHORT(sPos), MPFROM2SHORT(0,sMax) );
  723.  
  724.       WinEnableWindow( hwndBar, !!(sMax) );
  725.     }
  726. }
  727.  
  728. /*-----------------------------------------------------------------*/
  729. /* Set both scroll bars according to the window size and the       */
  730. /* number of INFO entries.                                         */
  731. /*-----------------------------------------------------------------*/
  732.  
  733. VOID SleuthSetScrollBars()
  734. {
  735.     RECTL       rcl;
  736.  
  737.     WinQueryWindowRect( hwndSleuth, &rcl );
  738.  
  739.     SleuthSetOneBar( hwndHorzScroll,
  740.                      WINDOW_WIDTH - (SHORT)rcl.xRight / sCharX );
  741.  
  742.     SleuthSetOneBar( hwndVertScroll,
  743.                      sWinCount*sLinesEach - (SHORT)rcl.yTop/sCharY );
  744. }
  745.  
  746. /*-----------------------------------------------------------------*/
  747. /* Terminate the application.                                      */
  748. /*-----------------------------------------------------------------*/
  749.  
  750. VOID SleuthTerminate()
  751. {
  752.     WinDestroyWindow( hwndFrame );
  753.     WinDestroyMsgQueue( hmq );
  754.     WinTerminate( hab );
  755.  
  756.     exit( 0 );
  757. }
  758.  
  759. /*-----------------------------------------------------------------*/
  760. /* Window function for Sleuth's main window.                       */
  761. /*-----------------------------------------------------------------*/
  762.  
  763. MRESULT EXPENTRY SleuthWndProc( hwnd, msg, mp1, mp2 )
  764.     HWND          hwnd;
  765.     USHORT        msg;
  766.     MPARAM        mp1;
  767.     MPARAM        mp2;
  768. {
  769.     switch( msg )
  770.     {
  771.       /* Tell PM that we don't need no stinkin' upside down
  772.          coordinates! */
  773.  
  774.       case WM_CALCVALIDRECTS:
  775.         return MRFROMSHORT( CVR_ALIGNLEFT | CVR_ALIGNTOP );
  776.  
  777.       /* Menu command message - process the command */
  778.  
  779.       case WM_COMMAND:
  780.         switch( COMMANDMSG(&msg)->cmd )
  781.         {
  782.           case CMD_ABOUT:
  783.             return 0L;
  784.  
  785.           case CMD_EXIT:
  786.             WinPostMsg( hwnd, WM_QUIT, 0L, 0L );
  787.             return 0L;
  788.  
  789.           case CMD_LOOK:
  790.             SleuthGetAllWindows();
  791.             return 0L;
  792.         }
  793.         return 0L;
  794.  
  795.         /* Scroll messages - scroll the window */
  796.  
  797.         case WM_HSCROLL:
  798.         case WM_VSCROLL:
  799.           SleuthScroll( hwnd, msg, SHORT1FROMMP(mp1),
  800.                         SHORT2FROMMP(mp2), SHORT1FROMMP(mp2) );
  801.           return 0L;
  802.  
  803.         /* Key-down message - handle cursor keys, ignore others */
  804.  
  805.         case WM_CHAR:
  806.           switch( CHARMSG(&msg)->vkey )
  807.           {
  808.             case VK_LEFT:
  809.             case VK_RIGHT:
  810.               return WinSendMsg( hwndHorzScroll, msg, mp1, mp2 );
  811.             case VK_UP:
  812.             case VK_DOWN:
  813.             case VK_PAGEUP:
  814.             case VK_PAGEDOWN:
  815.               return WinSendMsg( hwndVertScroll, msg, mp1, mp2 );
  816.           }
  817.           return 0L;
  818.  
  819.         /* Paint message - repaint all or part of our window */
  820.  
  821.         case WM_PAINT:
  822.           SleuthPaint( hwnd );
  823.           return 0L;
  824.  
  825.         /* Size message - recalculate our scroll bars */
  826.  
  827.         case WM_SIZE:
  828.           SleuthSetScrollBars();
  829.           return 0L;
  830.     }
  831.  
  832.     /* All other messages go to DefWindowProc */
  833.  
  834.     return WinDefWindowProc( hwnd, msg, mp1, mp2 );
  835. }
  836.  
  837. /*-----------------------------------------------------------------*/
  838.