
/*
 *@@sourcefile winh.c:
 *      contains Presentation Manager helper functions that are
 *      independent of a single application, i.e. these can be
 *      used w/out the rest of the XFolder source in any PM
 *      program.
 *
 *      Function prefixes (new with V0.81):
 *      --  winh*   Win (Presentation Manager) helper functions
 *
 *@@include #define INCL_WINWINDOWMGR
 *@@include #define INCL_WINMESSAGEMGR
 *@@include #define INCL_WINDIALOGS
 *@@include #define INCL_WINSTDCNR
 *@@include #define INCL_WININPUT
 *@@include #define INCL_WINSYS
 *@@include #define INCL_WINSHELLDATA
 *@@include #include <os2.h>
 *@@include #include "winh.h"
 */

/*
 *      Copyright (C) 1997-99 Ulrich M”ller.
 *      This file is part of the XFolder source package.
 *      XFolder is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XFolder main distribution.
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 */

#define INCL_DOSDEVIOCTL
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_WIN
#define INCL_GPI

// spooler #include's
#define INCL_BASE
#define INCL_SPL
#define INCL_SPLDOSPRINT
#define INCL_SPLERRORS

#include <os2.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "dosh.h"
#include "winh.h"
#include "prfh.h"
#include "gpih.h"

#include "undoc.h"

// #define _PMPRINTF_
#include "pmprintf.h"

/*
 *@@ winhInsertMenuItem:
 *      this inserts one one menu item into a given menu.
 *      Returns the return value of the MM_INSERTITEM msg:
 *      --  MIT_MEMERROR:    space allocation for menu item failed
 *      --  MIT_ERROR:       other error
 *      --  other:           zero-based index of new item in menu.
 */

SHORT winhInsertMenuItem(HWND hwndMenu,     // in:  menu to insert item into
                         SHORT iPosition,   // in:  zero-based index of where to
                                            //      insert or MIT_END
                         SHORT sItemId,     // in:  ID of new menu item
                         PSZ pszItemTitle,  // in:  title of new menu item
                         SHORT afStyle,     // in:  MIS_* style flags
                         SHORT afAttr)      // in:  MIA_* attribute flags
{
    MENUITEM mi;
    SHORT    src = MIT_ERROR;

    mi.iPosition = iPosition;
    mi.afStyle = afStyle;
    mi.afAttribute = afAttr;
    mi.id = sItemId;
    mi.hwndSubMenu = 0;
    mi.hItem = 0;
    src = SHORT1FROMMR(WinSendMsg(hwndMenu, MM_INSERTITEM, (MPARAM)&mi, (MPARAM)pszItemTitle));
    return (src);
}

/*
 *@@ winhInsertSubmenu:
 *      this inserts a submenu into a given menu and, if
 *      sItemId != 0, inserts one item into this new submenu also.
 *      Returns the HWND of the new submenu.
 */

HWND winhInsertSubmenu(HWND hwndMenu,       // in: menu to add submenu to
                       ULONG iPosition,     // in: index where to add submenu or MIT_END
                       SHORT sMenuId,       // in: menu ID of new submenu
                       PSZ pszSubmenuTitle, // in: title of new submenu
                       USHORT afMenuStyle,  // in: MIS* style flags for submenu;
                                            // MIS_SUBMENU will always be added
                       SHORT sItemId,       // in: ID of first item to add to submenu;
                                            // if 0, no first item is inserted
                       PSZ pszItemTitle,    // in: title of this item
                                            // (if sItemID != 0)
                       USHORT afItemStyle,  // in: style flags for this item, e.g. MIS_TEXT
                                            // (this is ignored if sItemID == 0)
                       USHORT afAttribute)  // in: attributes for this item, e.g. MIA_DISABLED
                                            // (this is ignored if sItemID == 0)
{
    MENUITEM mi;
    SHORT    src = MIT_ERROR;
    HWND     hwndNewMenu;

    // create new, empty menu
    hwndNewMenu = WinCreateMenu(hwndMenu,
                                NULL); // no menu template
    if (hwndNewMenu)
    {
        // add "submenu item" to this empty menu;
        // for some reason, PM always needs submenus
        // to be a menu item
        mi.iPosition = iPosition;
        mi.afStyle = afMenuStyle | MIS_SUBMENU;
        mi.afAttribute = 0;
        mi.id = sMenuId;
        mi.hwndSubMenu = hwndNewMenu;
        mi.hItem = 0;
        src = SHORT1FROMMR(WinSendMsg(hwndMenu, MM_INSERTITEM, (MPARAM)&mi, (MPARAM)pszSubmenuTitle));
        if (    (src != MIT_MEMERROR)
            &&  (src != MIT_ERROR)
           )
        {
            // set the new menu's ID to the same as the
            // submenu item
            WinSetWindowUShort(hwndNewMenu, QWS_ID, sMenuId);

            if (sItemId) {
                // item id given: insert first menu item also
                mi.iPosition = 0;
                mi.afStyle = afItemStyle;
                mi.afAttribute = afAttribute;
                mi.id = sItemId;
                mi.hwndSubMenu = 0;
                mi.hItem = 0;
                WinSendMsg(hwndNewMenu,
                           MM_INSERTITEM,
                           (MPARAM)&mi,
                           (MPARAM)pszItemTitle);
            }
        }
    }
    return (hwndNewMenu);
}

/*
 *@@ winhInsertMenuSeparator:
 *      this inserts a separator into a given menu at
 *      the given position (which may be MIT_END);
 *      returns the position at which the item was
 *      inserted.
 */

SHORT winhInsertMenuSeparator(HWND hMenu,       // in: menu to add separator to
                              SHORT iPosition,  // in: index where to add separator or MIT_END
                              SHORT sId)        // in: separator menu ID (doesn't really matter)
{
    MENUITEM mi;
    mi.iPosition = iPosition;
    mi.afStyle = MIS_SEPARATOR;             // append separator
    mi.afAttribute = 0;
    mi.id = sId;
    mi.hwndSubMenu = 0;
    mi.hItem = 0;

    return (SHORT1FROMMR(WinSendMsg(hMenu,
                    MM_INSERTITEM,
                    (MPARAM)&mi,
                    (MPARAM)"")));
}

/*
 *@@ winhMenuRemoveEllipse:
 *      removes a "..." substring from a menu item
 *      title, if found. This is useful if confirmations
 *      have been turned off for a certain menu item, which
 *      should be reflected in the menu.
 */

VOID winhMenuRemoveEllipse(HWND hwndMenu,
                           USHORT usItemId)    // in:  item to remove "..." from
{
    CHAR szBuf[255];
    CHAR *p;
    WinSendMsg(hwndMenu, MM_QUERYITEMTEXT,
            MPFROM2SHORT(usItemId, sizeof(szBuf)-1),
            (MPARAM)&szBuf);
    if (p = strstr(szBuf, "..."))
        strcpy(p, p+3);
    WinSendMsg(hwndMenu, MM_SETITEMTEXT,
            MPFROMSHORT(usItemId),
            (MPARAM)&szBuf);
}

/*
 * winhSetDlgItemSpinData:
 *      sets a spin button's limits and data within a dialog window.
 *      This only works for decimal spin buttons.
 */

VOID winhSetDlgItemSpinData(HWND hwndDlg,       // in: dlg window
                           ULONG idSpinButton,  // in: item ID of spin button
                           ULONG min,           // in: minimum allowed value
                           ULONG max,           // in: maximum allowed value
                           ULONG current)       // in: new current value
{
    HWND hwndSpinButton = WinWindowFromID(hwndDlg, idSpinButton);
    if (hwndSpinButton) {
        WinSendMsg(hwndSpinButton,
                      SPBM_SETLIMITS,          // Set limits message
                      (MPARAM)max,             // Spin Button maximum setting
                      (MPARAM)min);             // Spin Button minimum setting

        WinSendMsg(hwndSpinButton,
                      SPBM_SETCURRENTVALUE,    // Set current value message
                      (MPARAM)current,
                      (MPARAM)NULL);
    }
}

/*
 *@@ winhAdjustDlgItemSpinData:
 *      this can be called on a spin button control to
 *      have its current data snap to a grid. This only
 *      works for LONG integer values.
 *
 *      For example, if you specify 100 for the grid and call
 *      this func after you have received SPBN_UP/DOWNARROW,
 *      the spin button's value will always in/decrease
 *      in steps of 100.
 *
 *      This returns the "snapped" value to which the spin
 *      button was set.
 */

LONG winhAdjustDlgItemSpinData(HWND hwndDlg,     // in: dlg window
                               USHORT usItemID,  // in: item ID of spin button
                               LONG lGrid,       // in: grid
                               USHORT usNotifyCode) // in: SPBN_UP* or *DOWNARROW of WM_CONTROL message
{
    HWND hwndSpin = WinWindowFromID(hwndDlg, usItemID);
    LONG lBottom, lTop, lValue;
    // get value, which has already increased /
    // decreased by 1
    WinSendMsg(hwndSpin,
          SPBM_QUERYVALUE,
          (MPARAM)&lValue,
          MPFROM2SHORT(0, SPBQ_ALWAYSUPDATE));

    if (    (usNotifyCode == SPBN_UPARROW)
         || (usNotifyCode == SPBN_DOWNARROW)
       )
    {
        // only if the up/down buttons were pressed,
        // snap to the nearest grid; if the user
        // manually enters something (SPBN_CHANGE),
        // we'll accept that value
        lValue = (lValue / lGrid) * lGrid;
        // add /subtract grid
        if (usNotifyCode == SPBN_UPARROW)
            lValue += lGrid;
        // else we'll have -= lGrid already

        // balance with spin button limits
        WinSendMsg(hwndSpin,
              SPBM_QUERYLIMITS,
              (MPARAM)&lTop,
              (MPARAM)&lBottom);
        if (lValue < lBottom)
            lValue = lTop;
        else if (lValue > lTop)
            lValue = lBottom;

        WinSendMsg(hwndSpin,
              SPBM_SETCURRENTVALUE,
              (MPARAM)(lValue),
              MPNULL);
    }
    return (lValue);
}

/*
 *@@ winhCnrScrollToRecord:
 *      scrolls a given container control to make a given
 *      record visible.
 *
 *      Returns:
 *      --  0:       OK, scrolled
 *      --  1:       record rectangle query failed (error)
 *      --  2:       cnr viewport query failed (error)
 *      --  3:       record is already visible (scrolling not necessary)
 *      --  4:       cnrinfo query failed (error)
 *      --  5:       parent record rectangle query failed (error)
 *
 *      Note: All messages are _sent_ to the container, not posted.
 *      Scrolling therefore occurs synchroneously before this
 *      function returns.
 *
 *      This function an improved version of the one (W)(C) Dan Libby, found at
 *      http://zebra.asta.fh-weingarten.de/os2/Snippets/Howt6364.HTML
 *      Improvements (C) 1998 Ulrich M”ller.
 */

ULONG winhCnrScrollToRecord(HWND hwndCnr,       // in: container window
                            PRECORDCORE pRec,   // in: record to scroll to
                            ULONG fsExtent,
                                    // in: this determines what parts of pRec
                                    // should be made visible. OR the following
                                    // flags:
                                    // -- CMA_ICON the icon rectangle
                                    // -- CMA_TEXT the record text
                                    // -- CMA_TREEICON the "+" sign in tree view
                            BOOL KeepParent)
                                    // for tree views only: whether to keep
                                    // the parent record of pRec visible when scrolling.
                                    // If scrolling to pRec would make the parent
                                    // record invisible, we instead scroll so that
                                    // the parent record appears at the top of the
                                    // container workspace (Win95 style).

{
    QUERYRECORDRECT qRect, qRect2;
    RECTL           rclRecord, rclParentRecord, rclCnr, rclCnr2;
    POINTL          ptlRecord, ptlParentRecord;
    CNRINFO         CnrInfo;
    HAB             hab = WinQueryAnchorBlock(hwndCnr);
    BOOL            KeepParent2;
    LONG            lYOfs;

    qRect.cb = sizeof(qRect);
    qRect.pRecord = (PRECORDCORE)pRec;
    qRect.fsExtent = fsExtent;

    // query record location and size of container
    if (!WinSendMsg(hwndCnr, CM_QUERYRECORDRECT, &rclRecord, &qRect))
        return 1;
    if (!WinSendMsg(hwndCnr, CM_QUERYVIEWPORTRECT, &rclCnr, MPFROM2SHORT(CMA_WINDOW, FALSE)) )
        return 2;

    // check if left bottom point of pRec is currently visible in container
    ptlRecord.x = (rclRecord.xLeft);
    ptlRecord.y = (rclRecord.yBottom);
    // ptlRecord.x = (rclRecord.xLeft + rclRecord.xRight) / 2;
    // ptlRecord.y = (rclRecord.yBottom + rclRecord.yTop) / 2;
    if (WinPtInRect(hab, &rclCnr, &ptlRecord))
         return 3;

    if (KeepParent)
        if (!WinSendMsg(hwndCnr, CM_QUERYCNRINFO, (MPARAM)&CnrInfo, (MPARAM)sizeof(CnrInfo)))
            return 4;
        else KeepParent2 = (CnrInfo.flWindowAttr & CV_TREE);
    else KeepParent2 = FALSE;

    // calculate offset to scroll to make pRec visible
    lYOfs = (rclCnr.yBottom - rclRecord.yBottom)    // this would suffice
          + (rclRecord.yTop - rclRecord.yBottom);  // but we make the next rcl visible too

    if (KeepParent2) {
        qRect2.cb = sizeof(qRect2);
        qRect2.pRecord = (PRECORDCORE)WinSendMsg(hwndCnr, CM_QUERYRECORD,
                (MPARAM)pRec, MPFROM2SHORT(CMA_PARENT, CMA_ITEMORDER));
        qRect2.fsExtent = fsExtent;

        // now query PARENT record location and size of container
        if (!WinSendMsg(hwndCnr, CM_QUERYRECORDRECT, &rclParentRecord, &qRect2))
            return 5;

        ptlParentRecord.x = (rclParentRecord.xLeft);
        ptlParentRecord.y = (rclParentRecord.yTop);
        // ptlParentRecord.x = (rclParentRecord.xLeft + rclParentRecord.xRight) / 2;
        // ptlParentRecord.y = (rclParentRecord.yBottom + rclParentRecord.yTop) / 2;
        rclCnr2 = rclCnr;
        WinOffsetRect(hab, &rclCnr2, 0, -lYOfs);
        // if ( (rclParentRecord.yBottom - rclRecord.yBottom) > (rclCnr.yTop - rclCnr.yBottom) )
        if (!(WinPtInRect(hab, &rclCnr2, &ptlParentRecord)))
        {
            lYOfs = (rclCnr.yTop - rclParentRecord.yTop) // this would suffice
                  - (rclRecord.yTop - rclRecord.yBottom);  // but we make the previous rcl visible too
        }
    }

    if (!KeepParent2)
        WinSendMsg(hwndCnr, CM_SCROLLWINDOW, (MPARAM)CMA_HORIZONTAL,
            (MPARAM)(rclRecord.xLeft - rclCnr.xLeft));
    WinSendMsg(hwndCnr, CM_SCROLLWINDOW,
        (MPARAM)CMA_VERTICAL, (MPARAM)lYOfs);

    return 0;
}

/*
 *@@ winhCnrAllocRecord:
 *      this is a shortcut to allocating memory for
 *      a container record core. Returns the new recc.
 */

PRECORDCORE winhCnrAllocRecord(HWND hwndCnr,    // in: cnr to allocate from
                               ULONG cbrecc)
                                    // in: total size of your record core;
                                    // if you're using the default recc's, this
                                    // must be sizeof(RECORDCORE)
{
    PRECORDCORE      precc;
    precc = (PRECORDCORE)WinSendMsg(hwndCnr, CM_ALLOCRECORD,
            (MPARAM)(cbrecc-sizeof(RECORDCORE)),
            (MPARAM)1);
    precc->cb = cbrecc;
    return (precc);
}

/*
 *@@ winhCnrInsertRecord:
 *      shortcut to inserting a record core which has been
 *      allocated using winhCnrAllocRecord into a container.
 *      flRecordAttr should have the record attributes as
 *      specified in the RECORDCORE structure, which are:
 *      --  CRA_SELECTED       record is selected
 *      --  CRA_CURSORED       cursor (keyboard focus) is on the record
 *      --  CRA_SOURCE         record has source emphasis (drag'n'drop)
 *      --  CRA_TARGET         record has target emphasis (drag'n'drop)
 *      --  CRA_INUSE          record has in-use emphasis
 *      --  CRA_FILTERED       record has been filtered
 *      --  CRA_DROPONABLE     record can be dropped something upon
 *      --  CRA_RECORDREADONLY record is read-only (no text edit)
 *      --  CRA_EXPANDED       record is expanded (tree view)
 *      --  CRA_COLLAPSED      record is collapsed (tree view)
 *      --  CRA_PICKED         record picked (Lazy Drag)
 *
 *      plus the following undocumented (haven't tested these):
 *      --  CRA_IGNORE
 *      --  CRA_DISABLED
 *      --  CRA_OWNERFREE
 *      --  CRA_OWNERDRAW
 *
 *      This func returns precc.
 */

PRECORDCORE winhCnrInsertRecord(HWND hwndCnr,   // in: container to insert into
                                PRECORDCORE preccParent,
                                    // in: record core below which precc should
                                    // be inserted (tree view only); if NULL, precc
                                    // is inserted at "root" level
                                PRECORDCORE precc,
                                    // in: record core to insert (allocated using
                                    // winhCnrAllocRecord)
                                PSZ pszText,
                                    // in: text for recc. in all views
                                ULONG flRecordAttr)
                                    // in: CRA_* flags
{
    RECORDINSERT     ri;

    if (precc) {
        // RECORDCORE stuff
        precc->flRecordAttr = flRecordAttr;
        precc->preccNextRecord = NULL;
        precc->pszIcon = pszText;
        precc->pszText = pszText;
        precc->pszName = pszText;
        precc->pszTree = pszText;

        // setup RECORDINSERT struct
        ri.cb = sizeof(RECORDINSERT);
        ri.pRecordOrder = (PRECORDCORE)CMA_END;
        ri.pRecordParent = (PRECORDCORE)preccParent;
        ri.zOrder = CMA_TOP;
        ri.fInvalidateRecord = TRUE;
        ri.cRecordsInsert = 1;

        // printf("  Sending CM_INSERTRECORD\n");
        WinSendMsg(hwndCnr, CM_INSERTRECORD, (MPARAM)precc, (MPARAM)&ri);
    }
    return (precc);
}

/*
 *@@ winhSaveWindowPos:
 *      saves the position of a certain window. As opposed
 *      to the barely documented WinStoreWindowPos API, this
 *      one only saves one regular SWP structure for the given
 *      window. It does _not_ save minimized/maximized positions,
 *      presparams or anything about child windows.
 *      The window should still be visible on the screen
 *      when calling this function. Do not call it in WM_DESTROY,
 *      because then the SWP data is no longer valid.
 *      Returns TRUE if saving was successful.
 */

BOOL winhSaveWindowPos(HWND hwnd,   // in: window to save
                       HINI hIni,   // in: INI file (or HINI_USER/SYSTEM)
                       PSZ pszApp,  // in: INI application name
                       PSZ pszKey)  // in: INI key name
{
    BOOL brc = FALSE;
    SWP swp;
    if (WinQueryWindowPos(hwnd, &swp))
    {
        brc = PrfWriteProfileData(hIni, pszApp, pszKey, &swp, sizeof(swp));
    }
    return (brc);
}

/*
 *@@ winhRestoreWindowPos:
 *      this will retrieve a window position which was
 *      previously stored using winhSaveWindowPos.
 *      The window should not be visible to avoid flickering.
 *      "fl" must contain the SWP_flags as in WinSetWindowPos.
 *      Note that only the following may be used:
 *      --  SWP_MOVE        reposition the window
 *      --  SWP_SIZE        also resize the window to
 *                          the stored position; this might
 *                          lead to problems with different
 *                          video resolutions, so be careful.
 *      --  SWP_SHOW        make window visible too
 *      --  SWP_NOREDRAW    changes are not redrawn
 *      --  SWP_NOADJUST    do not send a WM_ADJUSTWINDOWPOS message
 *                          before moving or sizing
 *      --  SWP_ACTIVATE    activate window (make topmost)
 *      --  SWP_DEACTIVATE  deactivate window (make bottommost)
 *
 *      Do not specify any other SWP_* flags.
 *
 *      If SWP_SIZE is not set, the window will be moved only
 *      (recommended).
 *
 *      This function automatically checks for whether the
 *      window would be positioned outside the visible screen
 *      area and will adjust coordinates accordingly. This can
 *      happen when changing video resolutions.
 */

BOOL winhRestoreWindowPos(HWND hwnd,   // in: window to save
                       HINI hIni,   // in: INI file (or HINI_USER/SYSTEM)
                       PSZ pszApp,  // in: INI application name
                       PSZ pszKey,  // in: INI key name
                       ULONG fl)    // in: "fl" parameter for WinSetWindowPos
{
    BOOL    brc = FALSE;
    SWP     swp, swpNow;
    ULONG   cbswp = sizeof(swp);
    ULONG   fl2 = fl;

    if (PrfQueryProfileData(hIni, pszApp, pszKey, &swp, &cbswp))
    {
        ULONG ulScreenCX = WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN);
        ULONG ulScreenCY = WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN);

        brc = TRUE;

        if ((fl & SWP_SIZE) == 0) {
            // if no resize, we need to get the current position
            brc = WinQueryWindowPos(hwnd, &swpNow);
            swp.cx = swpNow.cx;
            swp.cy = swpNow.cy;
        }

        if (brc) {
            // check for full visibility
            if ( (swp.x + swp.cx) > ulScreenCX)
                swp.x = ulScreenCX - swp.cx;
            if ( (swp.y + swp.cy) > ulScreenCY)
                swp.y = ulScreenCY - swp.cy;
        }
    } else {
        // window pos not found in INI: unset SWP_MOVE etc.
        fl2 &= ~(SWP_MOVE | SWP_SIZE);
    }

    brc = WinSetWindowPos(hwnd,
                    NULLHANDLE,       // insert-behind window
                    swp.x,
                    swp.y,
                    swp.cx,
                    swp.cy,
                    fl2);        // SWP_* flags
    return (brc);
}

/*
 *@@ winhCenterWindow:
 *      centers a window within its parent window. If that's
 *      the PM desktop, it will be centered according to the
 *      whole screen.
 *      For dialog boxes, use WinCenteredDlgBox as a one-shot
 *      function.
 *
 *      Note: When calling this function, the window should
 *      not be visible to avoid flickering.
 *      This func does not show the window either, so call
 *      WinShowWindow afterwards.
 */

void winhCenterWindow(HWND hwnd)
{
   RECTL rclParent;
   RECTL rclWindow;

   WinQueryWindowRect(hwnd, &rclWindow);
   WinQueryWindowRect(WinQueryWindow(hwnd, QW_PARENT), &rclParent);

   rclWindow.xLeft   = (rclParent.xRight - rclWindow.xRight) / 2;
   rclWindow.yBottom = (rclParent.yTop   - rclWindow.yTop  ) / 2;

   WinSetWindowPos(hwnd, NULLHANDLE, rclWindow.xLeft, rclWindow.yBottom,
                    0, 0, SWP_MOVE);
}

/*
 *@@ winhCenteredDlgBox:
 *      just like WinDlgBox, but the dlg box is centered on the screen;
 *      you should mark the dlg template as not visible in the dlg
 *      editor, or display will flicker.
 *      As opposed to winhCenterWindow, this _does_ show the window.
 */

ULONG winhCenteredDlgBox(HWND hwndParent, HWND hwndOwner,
              PFNWP pfnDlgProc, HMODULE hmod, ULONG idDlg, PVOID pCreateParams)
{
    ULONG   ulReply;
    HWND    hwndDlg = WinLoadDlg(hwndParent, hwndOwner, pfnDlgProc,
                                hmod, idDlg, pCreateParams);
    winhCenterWindow(hwndDlg);
    ulReply = WinProcessDlg(hwndDlg);
    WinDestroyWindow(hwndDlg);
    return (ulReply);
}

/*
 *@@ winhQueryPresColor:
 *      returns the specified color. This is queried in the
 *      following order:
 *      1)  hwnd's pres params are searched for ulPP;
 *      2)  if this fails, PM_Colors in OS2.INI is searched
 *          for pszKeyName;
 *      3)  if this fails also, pszDefault is evaluated.
 *
 *      The return value is always an RGB LONG.
 *      The following ulPP / pszKeyName pairs are useful:
 *      --  PP_FOREGROUNDCOLOR             ("WindowText")
 *             Foreground color
 *      --  PP_BACKGROUNDCOLOR             ("Window" or "DialogBackground")
 *             Background color
 *      --  PP_HILITEFOREGROUNDCOLOR       ("HiliteForeground" or "MenuHiliteText")
 *             Highlighted foreground color, for example for selected menu
 *      --  PP_HILITEBACKGROUNDCOLOR       ("HiliteBackground" or "MenuHilite")
 *             Highlighted background color
 *      --  PP_DISABLEDFOREGROUNDCOLOR
 *             Disabled foreground color
 *      --  PP_DISABLEDBACKGROUNDCOLOR
 *             Disabled background color
 *      --  PP_BORDERCOLOR                 ("WindowFrame")
 *             Border color
 *      --  PP_ACTIVECOLOR
 *             Active color
 *      --  PP_INACTIVECOLOR
 *             Inactive color
 *      --  PP_ACTIVETEXTFGNDCOLOR
 *             Active text foreground color
 *      --  PP_ACTIVETEXTBGNDCOLOR
 *             Active text background color
 *      --  PP_INACTIVETEXTFGNDCOLOR
 *             Inactive text foreground color
 *      --  PP_INACTIVETEXTBGNDCOLOR
 *             Inactive text background color
 */

LONG winhQueryPresColor(HWND hwnd,          // in: window to query
                        ULONG ulPP,         // in: PP_* index
                        PSZ pszKeyName,     // in: INI key for PM_Colors
                        PSZ pszDefault)     // in: e.g. "255 255 255"
{
    ULONG ul, attrFound, abValue[32];
    if (ulPP != (ULONG)-1)
        if (ul = WinQueryPresParam(hwnd,
                    ulPP,
                    0,
                    &attrFound,
                    (ULONG)sizeof(abValue),
                    (PVOID)&abValue,
                    0))
            return (abValue[0]);

    return (prfhQueryColor(pszKeyName, pszDefault));
}

/*
 *@@ winhCreateFakeDesktop:
 *      this routine creates and displays a frameless window over
 *      the whole screen to fool the user that all windows
 *      have been closed (which in fact might not be the
 *      case).
 *      This window's background color is set to the Desktop's
 *      (PM's one, not the WPS's one).
 *      Returns the hwnd of this window.
 */

HWND winhCreateFakeDesktop(HWND hwndSibling)
{
    typedef struct _BACKGROUND {
        ULONG   cb;         //  Length of the aparam parameter, in bytes.
        ULONG   id;     //  Attribute type identity.
        ULONG   cb2;     //  Byte count of the ab parameter.
        RGB     rgb;    //  Attribute value.
     } BACKGROUND;
    RECTL       R;
    BACKGROUND  background;
    ULONG       flStyle;          // window style
    LONG        lDesktopColor;

    /* create fake desktop window = empty window with
       the size of full screen */
    lDesktopColor = WinQuerySysColor(HWND_DESKTOP,
        SYSCLR_BACKGROUND, 0);
    flStyle = WS_VISIBLE;
    R.xLeft      = 0;
    R.yBottom    = 0;
    R.xRight     = WinQuerySysValue(HWND_DESKTOP,SV_CXSCREEN);
    R.yTop       = WinQuerySysValue(HWND_DESKTOP,SV_CYSCREEN);
    background.cb = sizeof(background.id)
                  + sizeof(background.cb)
                  + sizeof(background.rgb);
    background.id = PP_BACKGROUNDCOLOR;
    background.cb2 = sizeof(RGB);
    background.rgb.bBlue = (CHAR1FROMMP(lDesktopColor));
    background.rgb.bGreen= (CHAR2FROMMP(lDesktopColor));
    background.rgb.bRed  = (CHAR3FROMMP(lDesktopColor));

    return (WinCreateWindow(HWND_DESKTOP,  // parent window
                           WC_FRAME,      // class name
                           "",            // window text
                           flStyle,       // window style
                           0, 0,          // position (x,y)
                           R.xRight,
                           R.yTop,
                           NULLHANDLE,    // owner window
                           hwndSibling,    // sibling window
                           1,             // window id
                           NULL,          // control data
                           &background)); // presentation parms
}

/*
 *@@ winhQueryCnrFromFrame:
 *      find the handle of a frame window's container; we do this
 *      by enumerating all the frame's child windows and comparing
 *      their window classes to the WC_CONTAINER code.
 *      This is not terribly fast, so use this func economically.
 *      Returns NULLHANDLE if not found.
 */

HWND winhQueryCnrFromFrame(HWND hwndFrame)
{
    HENUM               henum;
    CHAR                szClassName[256];
    HWND                hwndCnr = NULLHANDLE,
                        hwndTemp = NULLHANDLE;

    if (hwndFrame) {
        henum = WinBeginEnumWindows(hwndFrame);
        if (henum) {
            do {
                hwndTemp = WinGetNextWindow(henum);
                if (hwndTemp) {
                    if (WinQueryClassName(hwndTemp, 250, szClassName))
                        if (strcmp(szClassName, "#37") == 0)
                            // unbelievable, this is PM's code for WC_CONTAINER...
                            hwndCnr = hwndTemp;
                }
            } while (hwndTemp);
        }
        WinEndEnumWindows(henum);
    }

    return hwndCnr;
}

/*
 *@@ winhAssertWarp4Notebook:
 *      this takes hwndDlg as a notebook dialog page and
 *      goes thru all its controls. If a control with an
 *      ID <= udIdThreshold is found, this is assumed to
 *      be a button which is to be given the BS_NOTEBOOKBUTTON
 *      style. You should therefore give all your button
 *      controls which should be moved such an ID.
 *      You can also specify how many dialog units
 *      all the other controls will be moved downward in
 *      ulDownUnits; this is useful to fill up the space
 *      which was used by the buttons before moving them.
 *      Returns TRUE if anything was changed.
 *
 *      This function is useful if you wish to create
 *      notebook pages using dlgedit.exe which are compatible
 *      with both Warp 3 and Warp 4. This should be executed
 *      in WM_INITDLG of the notebook dlg function if the app
 *      has determined that it is running on Warp 4.
 */

BOOL winhAssertWarp4Notebook(HWND hwndDlg,
                    USHORT usIdThreshold,    // in: ID threshold
                    ULONG ulDownUnits)       // in: dialog units or 0
{
    BOOL brc = FALSE;
    POINTL ptl;
    HAB hab = WinQueryAnchorBlock(hwndDlg);
    BOOL    fIsVisible = WinIsWindowVisible(hwndDlg);
    if (ulDownUnits) {
        ptl.x = 0;
        ptl.y = ulDownUnits;
        WinMapDlgPoints(hwndDlg, &ptl, 1, TRUE);
    }

    WinEnableWindowUpdate(hwndDlg, FALSE);

    if (doshIsWarp4()) {
        HENUM henum = WinBeginEnumWindows(hwndDlg);
        HWND hwndItem;
        while (hwndItem = WinGetNextWindow(henum)) {
            USHORT usId = WinQueryWindowUShort(hwndItem, QWS_ID);
            _Pmpf(("hwndItem: 0x%lX, ID: 0x%lX", hwndItem, usId));
            if (usId <= usIdThreshold) {
                // pushbutton to change:
                _Pmpf(("  Setting bit"));
                WinSetWindowBits(hwndItem, QWL_STYLE,
                        BS_NOTEBOOKBUTTON, BS_NOTEBOOKBUTTON);
                brc = TRUE;
            } else
                // no pushbutton to change: move downwards
                // if desired
                if (ulDownUnits)
                {
                    SWP swp;
                    _Pmpf(("Moving downwards %d pixels", ptl.y));
                    WinQueryWindowPos(hwndItem, &swp);
                    WinSetWindowPos(hwndItem, 0,
                        swp.x,
                        swp.y - ptl.y,
                        0, 0,
                        SWP_MOVE);
                }
        }
        WinEndEnumWindows(henum);
    }
    if (fIsVisible)
        WinShowWindow(hwndDlg, TRUE);
    return (brc);
}

/*
 *@@ winhQueryItemUnderMouse:
 *      this queries the menu item which corresponds
 *      to the given mouse coordinates.
 *      Returns the ID of the menu item and stores its
 *      rectangle in *prtlItem; returns (-1) upon errors.
 */

SHORT winhQueryItemUnderMouse(HWND hwndMenu,      // in: menu handle
                              POINTL *pptlMouse,  // in: mouse coordinates
                              RECTL *prtlItem)    // out: rectangle of menu item
{
    SHORT   s, sItemId, sItemCount;
    HAB     habDesktop = WinQueryAnchorBlock(HWND_DESKTOP);

    sItemCount = SHORT1FROMMR(WinSendMsg(hwndMenu, MM_QUERYITEMCOUNT, MPNULL, MPNULL));

    for (s = 0;
         s <= sItemCount;
         s++)
    {
        sItemId = SHORT1FROMMR(WinSendMsg(hwndMenu,
                                          MM_ITEMIDFROMPOSITION,
                                          (MPARAM)s, MPNULL));
        WinSendMsg(hwndMenu, MM_QUERYITEMRECT,
                MPFROM2SHORT(sItemId, FALSE),
                (MPARAM)prtlItem);
        if (WinPtInRect(habDesktop, prtlItem, pptlMouse))
            return (sItemId);
    }
    /* sItemId = (SHORT)WinSendMsg(hwndMenu, MM_ITEMIDFROMPOSITION, (MPARAM)(sItemCount-1), MPNULL);
    return (sItemId); */
    return (-1); // error: no valid menu item
}

/*
 *@@ winhDrawFormattedText:
 *      this func takes a rectangle and draws pszText into
 *      it, breaking the words as neccessary. The line spacing
 *      is determined from the font currently selected in hps.
 *      As opposed to WinDrawText, this can draw several lines
 *      at once.
 *      This returns the number of lines drawn.
 */

ULONG winhDrawFormattedText(HPS hps,     // in: presentation space; its settings
                                         // are used, but not altered
                            PRECTL prcl, // in/out: rectangle to use for drawing;
                                         // both y coords are modified to return
                                         // the space which was used for drawing
                            PSZ pszText, // in: text to draw
                            ULONG flCmd) // in: flags like in WinDrawText; I have
                                         // only tested DT_TOP and DT_LEFT though.
                                         // DT_WORDBREAK | DT_TEXTATTRS are always
                                         // set.
                                         // You can specify DT_QUERYEXTENT to only
                                         // have prcl calculated without drawing.
{
    PSZ     p = pszText;
    LONG    lDrawn = 1,
            lTotalDrawn = 0,
            lLineCount = 0,
            lOrigYTop = prcl->yTop;
    ULONG   ulTextLen = strlen(pszText),
            ulCharHeight,
            flCmd2;
    // POINTL  aptlText[TXTBOX_COUNT];
    RECTL   rcl2;

    // printf("Entering winhDrawFormattedText\n");

    flCmd2 = flCmd | DT_WORDBREAK | DT_TEXTATTRS;

    ulCharHeight = gpihQueryLineSpacing(hps, pszText);
    // _Pmpf(("  ulCharHeight: %d\n", ulCharHeight));

    while (   (lDrawn)
           && (lTotalDrawn < ulTextLen)
          )
    {
        memcpy(&rcl2, prcl, sizeof(rcl2));
        lDrawn = WinDrawText(hps,
                ulTextLen-lTotalDrawn,
                p,
                &rcl2,
                0, 0,                       // colors
                flCmd2
            );
        p += lDrawn;
        lTotalDrawn += lDrawn;
        prcl->yTop -= ulCharHeight;
        lLineCount++;
        // printf("  lDrawn: %d, lTotalDrawn: %d\n", lDrawn, lTotalDrawn);
    }
    // printf("  LineCount: %d\n", lLineCount);
    prcl->yBottom = prcl->yTop;
    prcl->yTop = lOrigYTop;

    return (lLineCount);
}

/*
 *@@ winhKillTasklist:
 *      this will destroy the Tasklist (window list) window.
 *      Note: you will only be able to get it back after a
 *      reboot, not a WPS restart. Only for use at shutdown and such.
 *      This trick by Uri J. Stern at
 *      http://zebra.asta.fh-weingarten.de/os2/Snippets/Howt8881.HTML
 */

VOID winhKillTasklist(VOID)
{
    SWBLOCK  swblock;
    HWND     hwndTasklist;
    // the tasklist has entry #0 in the SWBLOCK
    WinQuerySwitchList(NULLHANDLE, &swblock, sizeof(SWBLOCK));
    hwndTasklist = swblock.aswentry[0].swctl.hwnd;
    WinPostMsg(hwndTasklist,
                0x0454,     // undocumented msg for killing tasklist
                NULL, NULL);
}

/*
 *@@ winhQueryPendingSpoolJobs:
 *      returns the number of pending print jobs in the spooler
 *      or 0 if none. Useful for testing before shutdown.
 */

ULONG winhQueryPendingSpoolJobs(VOID)
{
    // BOOL    rcPending = FALSE;
    ULONG   ulTotalJobCount = 0;

    SPLERR splerr;
    USHORT jobCount ;
    ULONG  cbBuf ;
    ULONG  cTotal;
    ULONG  cReturned ;
    ULONG  cbNeeded ;
    ULONG  ulLevel ;
    ULONG  i,j ;
    PSZ    pszComputerName ;
    PBYTE  pBuf = NULL;
    PPRQINFO3 prq ;
    PPRJINFO2 prj2 ;

    ulLevel = 4L;
    pszComputerName = (PSZ)NULL ;
    splerr = SplEnumQueue(pszComputerName, ulLevel, pBuf, 0L, // cbBuf
                          &cReturned, &cTotal,
                          &cbNeeded, NULL);
    if ( splerr == ERROR_MORE_DATA || splerr == NERR_BufTooSmall )
    {
        if (!DosAllocMem( (PPVOID)&pBuf, cbNeeded,
                          PAG_READ | PAG_WRITE | PAG_COMMIT) )
        {
            cbBuf = cbNeeded ;
            splerr = SplEnumQueue(pszComputerName, ulLevel, pBuf, cbBuf,
                                    &cReturned, &cTotal,
                                    &cbNeeded, NULL);
            if (splerr == NO_ERROR)
            {
                // set pointer to point to the beginning of the buffer
                prq = (PPRQINFO3)pBuf ;

                // cReturned has the count of the number of PRQINFO3 structures
                for (i=0;i < cReturned ; i++)
                {
                    // save the count of jobs; there are this many PRJINFO2
                    // structures following the PRQINFO3 structure
                    jobCount = prq->cJobs;
                    // _Pmpf(( "Job count in this queue is %d",jobCount ));

                    // increment the pointer past the PRQINFO3 structure
                    prq++;

                    // set a pointer to point to the first PRJINFO2 structure
                    prj2=(PPRJINFO2)prq;
                    for (j=0;j < jobCount ; j++)
                    {
                        // increment the pointer to point to the next structure
                        prj2++;
                        // increase the job count, which we'll return
                        ulTotalJobCount++;

                    } // endfor jobCount

                    // after doing all the job structures, prj2 points to the next
                    // queue structure; set the pointer for a PRQINFO3 structure
                    prq = (PPRQINFO3)prj2;
                } //endfor cReturned
            } // endif NO_ERROR
            DosFreeMem(pBuf) ;
        }
    } // end if Q level given

    return (ulTotalJobCount);
}


