home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / xwphescr.zip / XWPH0208.ZIP / src / helpers / cnrh.c < prev    next >
C/C++ Source or Header  |  2002-07-05  |  84KB  |  2,228 lines

  1.  
  2. /*
  3.  *@@sourcefile cnrh.c:
  4.  *      contains various PM container helper functions.
  5.  *
  6.  *      These functions used to be in winh.c, but they became
  7.  *      too many finally, so these were moved to this file with
  8.  *      V0.9.0.
  9.  *
  10.  *      Usage: All PM programs.
  11.  *
  12.  *      Function prefixes:
  13.  *      --  cnrh*   container helper functions (was: winh* before V0.9.0)
  14.  *
  15.  *      Note: Version numbering in this file relates to XWorkplace version
  16.  *            numbering.
  17.  *
  18.  *@@added V0.9.0 [umoeller]
  19.  *@@header "helpers\cnrh.h"
  20.  */
  21.  
  22. /*
  23.  *      Copyright (C) 1997-2000 Ulrich Möller.
  24.  *      This file is part of the "XWorkplace helpers" source package.
  25.  *      This is free software; you can redistribute it and/or modify
  26.  *      it under the terms of the GNU General Public License as published
  27.  *      by the Free Software Foundation, in version 2 as it comes in the
  28.  *      "COPYING" file of the XWorkplace main distribution.
  29.  *      This program is distributed in the hope that it will be useful,
  30.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  31.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  32.  *      GNU General Public License for more details.
  33.  */
  34.  
  35. #define OS2EMX_PLAIN_CHAR
  36.     // this is needed for "os2emx.h"; if this is defined,
  37.     // emx will define PSZ as _signed_ char, otherwise
  38.     // as unsigned char
  39.  
  40. #define INCL_WINWINDOWMGR
  41. #define INCL_WINRECTANGLES
  42. #define INCL_WININPUT
  43. #define INCL_WINPOINTERS
  44. #define INCL_WINMENUS
  45. #define INCL_WINSYS
  46. #define INCL_WINSTDDRAG
  47. #define INCL_WINSTDCNR
  48.  
  49. #define INCL_GPILOGCOLORTABLE
  50. #define INCL_GPIPRIMITIVES
  51. #include <os2.h>
  52.  
  53. #include <stdlib.h>
  54. #include <string.h>
  55. #include <stdio.h>
  56.  
  57. #include "setup.h"                      // code generation and debugging options
  58.  
  59. #include "helpers\winh.h"
  60. #include "helpers\cnrh.h"
  61.  
  62. #pragma hdrstop
  63.  
  64. /*
  65.  *@@category: Helpers\PM helpers\Container helpers
  66.  *      See cnrh.c.
  67.  */
  68.  
  69. /*
  70.  *@@category: Helpers\PM helpers\Container helpers\Details view helpers
  71.  *      these functions aid you in setting up the FIELDINFO structures
  72.  *      for a container Details view. Use cnrhSetFieldInfos for that.
  73.  */
  74.  
  75. /* ******************************************************************
  76.  *
  77.  *   Details view field infos
  78.  *
  79.  ********************************************************************/
  80.  
  81. /*
  82.  *@@ cnrhClearFieldInfos:
  83.  *      removes all existing FIELDINFO's from
  84.  *      the given container. Returns the number
  85.  *      of remaining FIELDINFO's, which should
  86.  *      be 0, or -1 upon errors.
  87.  *
  88.  *@@added V0.9.1 (2000-02-13) [umoeller]
  89.  */
  90.  
  91. ULONG cnrhClearFieldInfos(HWND hwndCnr,
  92.                           BOOL fInvalidate) // in: if TRUE, invalidate container
  93. {
  94.     ULONG ulFlags = CMA_FREE;
  95.     if (fInvalidate)
  96.         ulFlags |= CMA_INVALIDATE;
  97.  
  98.     return (ULONG)WinSendMsg(hwndCnr,
  99.                              CM_REMOVEDETAILFIELDINFO,
  100.                              (MPARAM)NULL,
  101.                              MPFROM2SHORT(0,      // all
  102.                                           ulFlags));
  103. }
  104.  
  105. /*
  106.  *@@ cnrhSetFieldInfo:
  107.  *      this sets a FIELDINFO structure to the given
  108.  *      data. Note that ppFieldInfo is a double pointer
  109.  *      to the actual FIELDINFO data.
  110.  *
  111.  *      This gets called from cnrhSetFieldInfos to set
  112.  *      each individual field info.
  113.  *      You can also use this function separately with
  114.  *      the cnrhAllocFieldInfos macro defined in cnrh.h.
  115.  *
  116.  *      After setting the data, <B>*ppFieldInfo</B> is advanced
  117.  *      to the next FIELDINFO structure so that you can
  118.  *      call this function several times for all the
  119.  *      FIELDINFOS that you have allocated. After the last
  120.  *      column, this pointer will be NULL.
  121.  *
  122.  *      Since the pointer is modified, do not invoke this
  123.  *      function on the original pointer returned from
  124.  *      cnrhAllocFieldInfos, because you'll need that
  125.  *      pointer for cnrhAllocFieldInfos later.
  126.  *
  127.  *      <B>Example usage</B>:
  128.  *
  129.  +          PFIELDINFO pFieldInfoFirst, pFieldInfo2;
  130.  +          if (pFieldInfoFirst = cnrhAllocFieldInfos(hwndFilesCnr, NO_OF_COLUMNS))
  131.  +                          // macro defined in cnrh.h
  132.  +          {
  133.  +              pFieldInfo2 = pFieldInfoFirst;
  134.  +
  135.  +              // "File name" column
  136.  +              cnrhSetFieldInfo(&pFieldInfo2,
  137.  +                                  FIELDOFFSET(RECORDCORE, pszIcon),
  138.  +                                      // icon text offset in original RECORDCORE
  139.  +                                  "File name",
  140.  +                                  CFA_STRING,
  141.  +                                  CFA_LEFT,
  142.  +                                  FALSE);     // no draw lines
  143.  +              // "Size" column
  144.  +              cnrhSetFieldInfo(&pFieldInfo2,
  145.  +                                  FIELDOFFSET(FILERECORDCORE, ulSize),
  146.  +                                      // size data field in sample extended RECORDCORE
  147.  +                                  "Size",
  148.  +                                  CFA_ULONG,
  149.  +                                  CFA_RIGHT,
  150.  +                                  FALSE);     // no draw lines
  151.  +              ... // set other field infos
  152.  +          }
  153.  +
  154.  +          // insert field infos
  155.  +          cnrhInsertFieldInfos(hwndFilesCnr,
  156.  +                                  pFieldInfoFirst,
  157.  +                                  NO_OF_COLUMNS);
  158.  *
  159.  *@@added V0.9.0 [umoeller]
  160.  *@@changed V0.9.1 (99-12-18) [umoeller]: fixed memory leak
  161.  */
  162.  
  163. VOID cnrhSetFieldInfo(PFIELDINFO *ppFieldInfo2,  // in/out: double ptr to FIELDINFO
  164.                       ULONG ulFieldOffset,       // in: FIELDOFFSET(YOURRECORDCORE, yourField)
  165.                       PSZ pszColumnTitle,        // in: column title; this must be a static string!!
  166.                       ULONG ulDataType,          // in: column data type (CFA_* flags)
  167.                       ULONG ulOrientation,       // in: vertical and horizontal orientation (CFA_* flags)
  168.                       BOOL fDrawLines)           // in: if TRUE, we'll draw lines around the columns
  169. {
  170.     PFIELDINFO pInfo;
  171.     if (    (ppFieldInfo2)
  172.          && (pInfo = *ppFieldInfo2)
  173.        )
  174.     {
  175.         ULONG flData = ulDataType | ulOrientation;
  176.         if (fDrawLines)
  177.             flData |= CFA_HORZSEPARATOR | CFA_SEPARATOR;
  178.  
  179.         pInfo->cb = sizeof(FIELDINFO);
  180.         pInfo->flData = flData;
  181.         pInfo->flTitle = CFA_FITITLEREADONLY | ulOrientation;
  182.         pInfo->offStruct = ulFieldOffset;
  183.         pInfo->pTitleData = pszColumnTitle;   // strdup removed, V0.9.1 (99-12-18) [umoeller]
  184.         pInfo->pUserData   = NULL;
  185.         pInfo->cxWidth = 0;
  186.         *ppFieldInfo2 = pInfo->pNextFieldInfo;
  187.     }
  188. }
  189.  
  190. /*
  191.  *@@ cnrhInsertFieldInfos:
  192.  *      this inserts field infos for Details view
  193.  *      into the specified container.
  194.  *
  195.  *      pFieldInfoFirst should be the PFIELDINFO
  196.  *      returned by cnrhAllocFieldInfos.
  197.  *
  198.  *      This inserts the FIELDINFOs at the end,
  199.  *      should any columns already exist in the container.
  200.  *      Also, the container is invalidated.
  201.  *
  202.  *      Returns the return value of CM_INSERTDETAILFIELDINFO,
  203.  *      which is the total no. of field infos in the container
  204.  *      or null upon errors.
  205.  *
  206.  *@@added V0.9.0 [umoeller]
  207.  */
  208.  
  209. ULONG cnrhInsertFieldInfos(HWND hwndCnr,                // in: cnr for Details view
  210.                            PFIELDINFO pFieldInfoFirst,  // in: first field info as returned
  211.                                                         // by cnrhAllocFieldInfos
  212.                            ULONG ulFieldCount)          // in: no. of field infos
  213. {
  214.     FIELDINFOINSERT fii;
  215.     fii.cb = sizeof(FIELDINFOINSERT);
  216.     fii.pFieldInfoOrder = (PFIELDINFO)CMA_END;
  217.     fii.fInvalidateFieldInfo = TRUE;
  218.     fii.cFieldInfoInsert = ulFieldCount;
  219.  
  220.     return (ULONG)WinSendMsg(hwndCnr,
  221.                              CM_INSERTDETAILFIELDINFO,
  222.                              (MPARAM)pFieldInfoFirst,
  223.                              (MPARAM)&fii);
  224. }
  225.  
  226. /*
  227.  *@@ cnrhSetFieldInfos:
  228.  *      this combines cnrhAllocFieldInfos,
  229.  *      cnrhSetFieldInfo, and cnrhInsertFieldInfos
  230.  *      into a one-shot func for setting details view
  231.  *      column field infos. This one has proven to
  232.  *      be EXTREMELY useful.
  233.  *
  234.  *      To pass all the arguments normally passed to
  235.  *      cnrhSetFieldInfo, we use an array of XFIELDINFO
  236.  *      structures, which takes the same parameters.
  237.  *
  238.  *      <B>XFIELDINFO.ulDataType</B> specifies the data type of the record
  239.  *      core field. This can be one of the following:
  240.  *      --  CFA_BITMAPORICON: bit-map or icon data
  241.  *      --  CFA_DATE: date format (CDATE structure)
  242.  *      --  CFA_STRING: null-terminated string
  243.  *      --  CFA_TIME: time format (CTIME structure)
  244.  *      --  CFA_ULONG: unsigned number data
  245.  *
  246.  *      You can add the following optional flags to
  247.  *      ulDataType:
  248.  *      --  CFA_FIREADONLY (CFA_STRING only): disable editing
  249.  *      --  CFA_INVISIBLE: make column invisible
  250.  *      --  CFA_OWNER: enable owner draw for this column
  251.  *
  252.  *      If (fDrawLines == TRUE), we'll automatically add
  253.  *      CFA_HORZSEPARATOR | CFA_SEPARATOR.
  254.  *
  255.  *      <B>XFIELDINFO.ulOrientation</B> specifies the vertical and
  256.  *      horizontal orientation of both the column title
  257.  *      and the column data. This should be OR'ed from
  258.  *      the following
  259.  *      --  CFA_CENTER, CFA_LEFT, CFA_RIGHT (horizontal);
  260.  *          the default is CFA_LEFT
  261.  *      --  CFA_BOTTOM, CFA_TOP, CFA_VCENTER (vertical);
  262.  *          the default is CFA_VCENTER.
  263.  *
  264.  *      Note that the container automatically displays
  265.  *      data according to the settings in the "Country" object.
  266.  *
  267.  *      The column title will always be set to string format
  268.  *      and CFA_FITITLEREADONLY.
  269.  *
  270.  *      <B>XFIELDINFO.ulFieldOffset</B> should be set to the return value
  271.  *      of the FIELDOFFSET macro, which is also redefined in
  272.  *      cnrh.h (to work with C++).
  273.  *
  274.  *      The return value is the PFIELDINFO which corresponds
  275.  *      to the column index specified in ulFieldReturn, or
  276.  *      NULL upon errors. This is useful for setting the
  277.  *      position of the split bar afterwards (using the
  278.  *      cnrhSetSplitBarAfter macro).
  279.  *
  280.  *      Example usage:
  281.  +          XFIELDINFO      xfi[3];
  282.  +          PFIELDINFO      pfi;
  283.  +
  284.  +          xfi[0].ulFieldOffset = FIELDOFFSET(DATABASERECORD, pszApplication);
  285.  +          xfi[0].pszColumnTitle = "Application";
  286.  +          xfi[0].ulDataType = CFA_STRING;
  287.  +          xfi[0].ulOrientation = CFA_LEFT;
  288.  +
  289.  +          xfi[1].ulFieldOffset = FIELDOFFSET(RECORDCORE, pszIcon);
  290.  +          xfi[1].pszColumnTitle = "Package name";
  291.  +          xfi[1].ulDataType = CFA_STRING;
  292.  +          xfi[1].ulOrientation = CFA_LEFT;
  293.  +
  294.  +          xfi[2].ulFieldOffset = FIELDOFFSET(DATABASERECORD, pszAuthor);
  295.  +          xfi[2].pszColumnTitle = "Author";
  296.  +          xfi[2].ulDataType = CFA_STRING;
  297.  +          xfi[2].ulOrientation = CFA_CENTER;
  298.  +
  299.  +          pfi = cnrhSetFieldInfos(hwndCnr,
  300.  +                                  &xfi[0],
  301.  +                                  (sizeof(xfi) / sizeof(XFIELDINFO)),
  302.  +                                      // smart way of calculating the array item count
  303.  +                                  FALSE,         // no draw lines
  304.  +                                  0);            // return first column
  305.  *
  306.  *@@added V0.9.0 [umoeller]
  307.  */
  308.  
  309. PFIELDINFO cnrhSetFieldInfos(HWND hwndCnr,            // in: container hwnd
  310.                              PXFIELDINFO paxfi,       // in: pointer to an array of ulFieldCount XFIELDINFO structures
  311.                              ULONG ulFieldCount,      // in: no. of items in paxfi array (> 0)
  312.                              BOOL fDrawLines,         // in: if TRUE, we'll draw lines around the columns
  313.                              ULONG ulFieldReturn)     // in: the column index to return as PFIELDINFO
  314. {
  315.     PFIELDINFO  pFieldInfoFirst,
  316.                 pFieldInfo2,
  317.                 pFieldInfoReturn = NULL;
  318.  
  319.     if ((pFieldInfoFirst = cnrhAllocFieldInfos(hwndCnr, ulFieldCount)))
  320.     {
  321.         ULONG ul = 0;
  322.         PXFIELDINFO pxfi = NULL;
  323.  
  324.         pFieldInfo2 = pFieldInfoFirst;
  325.         pxfi = paxfi;
  326.         for (ul = 0; ul < ulFieldCount; ul++)
  327.         {
  328.             if (ul == ulFieldReturn)
  329.                 // set return value
  330.                 pFieldInfoReturn = pFieldInfo2;
  331.  
  332.             // set current field info;
  333.             // this will modify pFieldInfo to point to the next
  334.             cnrhSetFieldInfo(&pFieldInfo2,
  335.                              pxfi->ulFieldOffset,
  336.                              (PSZ)pxfi->pszColumnTitle,
  337.                              pxfi->ulDataType,
  338.                              pxfi->ulOrientation,
  339.                              fDrawLines);
  340.             pxfi++;
  341.         }
  342.  
  343.         // insert field infos
  344.         if (cnrhInsertFieldInfos(hwndCnr,
  345.                                  pFieldInfoFirst,
  346.                                  ulFieldCount) == 0)
  347.             pFieldInfoReturn = NULL;
  348.     }
  349.  
  350.     return pFieldInfoReturn;
  351. }
  352.  
  353. /*
  354.  *@@category: Helpers\PM helpers\Container helpers\Record core helpers
  355.  *      functions for allocating and inserting records more easily.
  356.  *
  357.  *      Use cnrhAllocRecords first to allocate, then cnrhInsertRecords
  358.  *      to insert the records.
  359.  */
  360.  
  361. /* ******************************************************************
  362.  *
  363.  *   Record core management
  364.  *
  365.  ********************************************************************/
  366.  
  367. /*
  368.  *@@ cnrhAllocRecords:
  369.  *      this is a shortcut to allocating memory for
  370.  *      container record cores.
  371.  *
  372.  *      If (ulCount == 1), this returns the new record core.
  373.  *      If (ulCount > 1), this returns the _first_ record
  374.  *      core; the following record cores may be reached
  375.  *      by following the RECORDCORE.preccNextRecord pointers
  376.  *      (i.e. we have a linked list here).
  377.  *
  378.  *      Returns NULL on errors (what CM_ALLOCRECORD returns).
  379.  *
  380.  *      The record cores returned by the container are
  381.  *      automatically zeroed out, and their "cb" field
  382.  *      is automatically set to the size of the record core.
  383.  *
  384.  *      Note that this function presently does _not_ work
  385.  *      with MINIRECORDCOREs.
  386.  *
  387.  *@@changed V0.9.0 [umoeller]: function prototype changed to allocate more than one record
  388.  */
  389.  
  390. PRECORDCORE cnrhAllocRecords(HWND hwndCnr,    // in: cnr to allocate from
  391.                              ULONG cbrecc,
  392.                                  // in: total size of your record core.
  393.                                  // If you're using the default recc's, this
  394.                                  // must be sizeof(RECORDCORE).
  395.                              ULONG ulCount)  // in: number of records to allocate (> 0)
  396. {
  397.     return (PRECORDCORE)WinSendMsg(hwndCnr,
  398.                                    CM_ALLOCRECORD,
  399.                                    (MPARAM)(cbrecc - sizeof(RECORDCORE)),
  400.                                    (MPARAM)ulCount);
  401. }
  402.  
  403. /*
  404.  *@@ cnrhInsertRecords:
  405.  *      shortcut to inserting record cores into a container
  406.  *      which have been allocated using cnrhAllocRecords.
  407.  *
  408.  *      If (<B>ulCount</B> == 1), this inserts precc.
  409.  *      If (ulCount > 1), this assumes precc to be the first
  410.  *      record to be inserted, while precc->preccNextRecord
  411.  *      must point to the next record in a linked list (as
  412.  *      returned with cnrhAllocRecords). (changed V0.9.0)
  413.  *
  414.  *      Note that ulCount here must be exactly the same as
  415.  *      specified with cnrhAllocRecords.
  416.  *
  417.  *      If (<B>pszText</B> != NULL), this will automatically set precc->pszIcon,
  418.  *      precc->pszText, precc->pszName, precc->pszTree to pszText to have
  419.  *      the same record titles in all views. If (pszText == NULL), those
  420.  *      fields will be left alone. (changed V0.9.0)
  421.  *
  422.  *      <B>flRecordAttr</B> should have the record attributes as
  423.  *      specified in the RECORDCORE structure.
  424.  *
  425.  *      Emphasis flags:
  426.  *      --  CRA_SELECTED:      record is selected
  427.  *                             (other records will be deselected in Tree views
  428.  *                             and when single selection is enabled)
  429.  *      --  CRA_CURSORED:      cursor (keyboard focus) is on the record
  430.  *                             (other records will be de-cursored)
  431.  *      --  CRA_SOURCE:        record has source emphasis (drag'n'drop,
  432.  *                             open context menus)
  433.  *      --  CRA_TARGET:        record has target emphasis (drag'n'drop)
  434.  *      --  CRA_PICKED:        record picked (Lazy Drag)
  435.  *      --  CRA_INUSE:         record has in-use emphasis (diagonal lines,
  436.  *                             as with open objects with the WPS)
  437.  *
  438.  *      Miscellaneous flags:
  439.  *      --  CRA_FILTERED:      record has been filtered (is invisible)
  440.  *      --  CRA_DROPONABLE:    record can be dropped something upon
  441.  *      --  CRA_RECORDREADONLY: record is read-only (no text edit with Alt+MB1)
  442.  *      --  CRA_EXPANDED:      record is expanded (Tree view)
  443.  *      --  CRA_COLLAPSED:     record is collapsed (Tree view)
  444.  *
  445.  *      plus the following half-documented from PMSTDDLG.H (haven't tested these):
  446.  *      --  CRA_IGNORE         record is to be ignored
  447.  *      --  CRA_DISABLED       has no visible effect, but can be used with
  448.  *                             cnrhOwnerDrawRecord
  449.  *      --  CRA_LOCKED         maybe "locked in place"?!?
  450.  *      --  CRA_OWNERFREE      owner must free record
  451.  *      --  CRA_OWNERDRAW      owner must draw record; I'd recommend using
  452.  *                             CA_OWNERDRAW in CNRINFO.flWindowAttr instead.
  453.  *
  454.  *      This func returns the total number of records in the container
  455.  *      or NULL upon errors (return value of CM_INSERTRECORD, changed V0.9.0).
  456.  *
  457.  *@@changed V0.9.0 [umoeller]: function prototype changed to insert more than one record
  458.  *@@changed V0.9.0 [umoeller]: added error checking for pszText
  459.  *@@changed V0.9.2 (2000-02-19) [umoeller]: added fInvalidate field
  460.  */
  461.  
  462. ULONG cnrhInsertRecords(HWND hwndCnr,   // in: container to insert into
  463.                         PRECORDCORE preccParent,
  464.                             // in: record core below which precc should
  465.                             // be inserted (tree view only). If NULL, precc
  466.                             // is inserted at "root" level
  467.                         PRECORDCORE precc,
  468.                             // in: record core to insert (allocated using
  469.                             // cnrhAllocRecords)
  470.                         BOOL fInvalidate,
  471.                         const char *pcszText,
  472.                             // in: text for recc. in all views (or NULL)
  473.                         ULONG flRecordAttr,
  474.                             // in: CRA_* flags
  475.                         ULONG ulCount)  // in: number of records to insert (> 0)
  476. {
  477.     RECORDINSERT    ri;
  478.  
  479.     if (precc)
  480.     {
  481.         // RECORDCORE stuff
  482.         precc->flRecordAttr = flRecordAttr;
  483.         // precc->preccNextRecord = NULL;
  484.  
  485.         if (pcszText) // V0.9.0
  486.         {
  487.             precc->pszIcon = (PSZ)pcszText;
  488.             precc->pszText = (PSZ)pcszText;
  489.             precc->pszName = (PSZ)pcszText;
  490.             precc->pszTree = (PSZ)pcszText;
  491.         }
  492.  
  493.         // setup RECORDINSERT struct
  494.         ri.cb = sizeof(RECORDINSERT);
  495.         ri.pRecordOrder = (PRECORDCORE)CMA_END;
  496.         ri.pRecordParent = (PRECORDCORE)preccParent;
  497.         ri.zOrder = CMA_TOP;
  498.         ri.fInvalidateRecord = fInvalidate;  // V0.9.2 (2000-02-19) [umoeller]
  499.         ri.cRecordsInsert = ulCount;        // V0.9.0
  500.  
  501.         return (ULONG)WinSendMsg(hwndCnr,
  502.                                  CM_INSERTRECORD,
  503.                                  (MPARAM)precc,
  504.                                  (MPARAM)&ri);
  505.     }
  506.  
  507.     return 0;
  508. }
  509.  
  510. /*
  511.  *@@ cnrhInsertRecordAfter:
  512.  *      similar to cnrhInsertRecords, but this inserts
  513.  *      a single record _after_ another one. Parent records
  514.  *      and multiple records are not supported.
  515.  *
  516.  *@@added V0.9.0 [umoeller]
  517.  *@@changed V0.9.4 (2000-06-14) [umoeller]: added fInvalidate
  518.  */
  519.  
  520. ULONG cnrhInsertRecordAfter(HWND hwndCnr,
  521.                             PRECORDCORE precc,
  522.                             PSZ pszText,
  523.                             ULONG flRecordAttr,
  524.                             PRECORDCORE preccAfter,
  525.                             BOOL fInvalidate)           // in: invalidate records?
  526. {
  527.     RECORDINSERT    ri;
  528.  
  529.     if (precc)
  530.     {
  531.         // RECORDCORE stuff
  532.         precc->flRecordAttr = flRecordAttr;
  533.         precc->preccNextRecord = NULL;
  534.  
  535.         if (pszText) // V0.9.0
  536.         {
  537.             precc->pszIcon = pszText;
  538.             precc->pszText = pszText;
  539.             precc->pszName = pszText;
  540.             precc->pszTree = pszText;
  541.  
  542.             // setup RECORDINSERT struct
  543.             ri.cb = sizeof(RECORDINSERT);
  544.             ri.pRecordOrder = (PRECORDCORE)preccAfter;
  545.             ri.pRecordParent = 0;   // no parent here
  546.             ri.zOrder = CMA_TOP;
  547.             ri.fInvalidateRecord = fInvalidate; // V0.9.4 (2000-06-14) [umoeller]
  548.             ri.cRecordsInsert = 1;
  549.  
  550.             return (ULONG)WinSendMsg(hwndCnr,
  551.                                      CM_INSERTRECORD,
  552.                                      (MPARAM)precc,
  553.                                      (MPARAM)&ri);
  554.         }
  555.     }
  556.  
  557.     return 0;
  558. }
  559.  
  560. /*
  561.  *@@ cnrhMoveRecord:
  562.  *      this moves a single record within the same container
  563.  *      window. This only makes sense in ordered views (i.e.
  564.  *      Name, Text, and Details views).
  565.  *
  566.  *      For moving records with child records, use cnrhMoveTree
  567.  *      instead.
  568.  */
  569.  
  570. BOOL cnrhMoveRecord(HWND hwndCnr,
  571.                     PRECORDCORE preccMove,        // in: record to move
  572.                     PRECORDCORE preccInsertAfter)
  573.                          // in: where to move the record to
  574.                          // (can be CMA_FIRST or CMA_END)
  575. {
  576.     // rule out possible errors:
  577.     if (    (preccMove)     // this better be valid
  578.          && (preccInsertAfter)  // this also, because otherwise the record just disappears
  579.          && (preccMove != preccInsertAfter)
  580.                             // same here
  581.        )
  582.     {
  583.         RECORDINSERT ri;
  584.         ULONG        ulrc = 0;
  585.  
  586.         // remove record
  587.         WinSendMsg(hwndCnr,
  588.                    CM_REMOVERECORD,
  589.                    (MPARAM)(&preccMove),
  590.                    MPFROM2SHORT(1,
  591.                                 CMA_INVALIDATE)); // no free
  592.  
  593.         // and re-insert that record at the new position
  594.         ri.cb = sizeof(RECORDINSERT);
  595.         ri.pRecordOrder = (PRECORDCORE)preccInsertAfter;
  596.         ri.pRecordParent = 0;   // no parent here
  597.         ri.zOrder = CMA_TOP;
  598.         ri.fInvalidateRecord = TRUE;
  599.         ri.cRecordsInsert = 1;
  600.         ulrc = (ULONG)WinSendMsg(hwndCnr,
  601.                                  CM_INSERTRECORD,
  602.                                  (MPARAM)preccMove,
  603.                                  (MPARAM)&ri);
  604.  
  605.         return (ulrc != 0);
  606.     }
  607.  
  608.     return FALSE;
  609. }
  610.  
  611. /*
  612.  *@@ cnrhMoveTree:
  613.  *      this function moves a container record core and all its
  614.  *      children from one tree to another.
  615.  *      See the CM_MOVETREE message for more explanations.
  616.  *
  617.  *      The problem with that message is that automatic container
  618.  *      sorting does not work in tree view. This function however
  619.  *      is smart enough to maintain container sorting and insert
  620.  *      the moved record core at the correct position below the
  621.  *      target record core, if you specify a container sort function
  622.  *      (pfnCnrSort, which works just in CNRINFO).
  623.  *
  624.  *      Otherwise, we'll just insert the item as the first child
  625.  *      of preccNewParent.
  626.  *
  627.  *      This function also has a workaround for a nasty container
  628.  *      bug which results in a crash in PMMERGE.DLL when a tree
  629.  *      is moved to the last position in the target parent record
  630.  *      (at least with Warp 4 FP 7).
  631.  *
  632.  *@@added V0.9.0 [umoeller]
  633.  */
  634.  
  635. BOOL cnrhMoveTree(HWND hwndCnr,          // in: container control
  636.                   PRECORDCORE preccMove, // in: record core to move
  637.                   PRECORDCORE preccNewParent, // in: new parent for preccMove
  638.                                               // or NULL if move to root
  639.                   PFNCNRSORT pfnCnrSort) // in: sort function to use or NULL
  640. {
  641.     TREEMOVE    tm;
  642.     PRECORDCORE preccInsertAfter = (PRECORDCORE)CMA_FIRST;
  643.     BOOL        fBugWorkAround = FALSE,
  644.                 brc = FALSE;
  645.  
  646.     if (pfnCnrSort)
  647.     {
  648.         // permanent sort activated:
  649.  
  650.         // since the automatic container sort does
  651.         // not work when moving a record core tree in
  652.         // Tree view, we must find the recc after
  653.         // which we'll insert the tree ourselves
  654.         PRECORDCORE preccSearch = preccNewParent;
  655.         BOOL        fFirstRun = TRUE;
  656.         ULONG       ulFirstCode;
  657.  
  658.         // set the code for first-loop query:
  659.         if (preccNewParent)
  660.             // if new parent is non-root
  661.             ulFirstCode = CMA_FIRSTCHILD;
  662.         else
  663.             // if new parent is root
  664.             ulFirstCode = CMA_FIRST;
  665.  
  666.         while (TRUE)
  667.         {
  668.             preccSearch =
  669.                 (PRECORDCORE)WinSendMsg(hwndCnr,
  670.                                         CM_QUERYRECORD,
  671.                                         // the following gets either the
  672.                                         // first child recc of the target
  673.                                         // record core or the next child
  674.                                         // for consecutive loops
  675.                                         (MPARAM)preccSearch,
  676.                                         MPFROM2SHORT(
  677.                                                 ((fFirstRun)
  678.                                                 ? ulFirstCode  // first loop
  679.                                                 : CMA_NEXT  // works for next child too
  680.                                             ),
  681.                                             CMA_ITEMORDER)
  682.                                         );
  683.             fFirstRun = FALSE;
  684.  
  685.             if (    (preccSearch == NULL)
  686.                  || ((ULONG)preccSearch == -1)
  687.                )
  688.             {
  689.                 // no more items found:
  690.                 // keep preccInsertAfter, which might be CMA_FIRST
  691.                 // or the preccSearch we have found previously.
  692.  
  693.                 if (preccInsertAfter != (PRECORDCORE)CMA_FIRST)
  694.                 {
  695.                     // Unfortunately, there is a bug in the container
  696.                     // control which prohibits CM_MOVETREE from working
  697.                     // if preccInsertAfter turns out to be the last
  698.                     // record core in preccNewParent. This applies to
  699.                     // preccInsertAfter == CMA_LAST also, and CMA_LASTCHILD
  700.                     // doesn't work either.
  701.                     // Duh.
  702.                     // We'll fix this later.
  703.  
  704.                     fBugWorkAround = TRUE;
  705.                 }
  706.                 break;
  707.             }
  708.  
  709.             if (((*pfnCnrSort)(preccSearch, preccMove, 0)) < 0)
  710.             {
  711.                 // found record core is < our tree:
  712.                 // mark current as "insert after" for later and go on
  713.                 preccInsertAfter = preccSearch;
  714.             }
  715.             else
  716.                 break;
  717.         }
  718.  
  719.         /* _Pmpf(("preccInsertAfter %s",
  720.                 (preccInsertAfter == (PRECORDCORE)CMA_FIRST) ? "CMA_FIRST"
  721.                 : (preccInsertAfter == (PRECORDCORE)CMA_LAST) ? "CMA_LAST"
  722.                 : (preccInsertAfter == NULL) ? "NULL"
  723.                 : preccInsertAfter->pszIcon
  724.              )); */
  725.     } // end if (CnrInfo.pSortRecord)
  726.  
  727.     if (fBugWorkAround)
  728.         // this is TRUE only if preccInsertAfter has turned
  729.         // out to be the last child of preccNewParent. This
  730.         // will make the container crash, so we insert as
  731.         // first and sort the whole damn container later.
  732.         // Not terribly fast, but better than crashing. ;-)
  733.         preccInsertAfter = (PRECORDCORE)CMA_FIRST;
  734.  
  735.     // set record to be moved
  736.     tm.preccMove = preccMove;
  737.     // set target record core
  738.     tm.preccNewParent = preccNewParent;
  739.     tm.pRecordOrder = preccInsertAfter;
  740.     tm.flMoveSiblings = FALSE;
  741.                 // move only preccMove
  742.     brc = (BOOL)WinSendMsg(hwndCnr,
  743.                            CM_MOVETREE,
  744.                            (MPARAM)&tm,
  745.                            (MPARAM)NULL);
  746.  
  747.     if (brc)
  748.         if (fBugWorkAround)
  749.             WinSendMsg(hwndCnr, CM_SORTRECORD, (MPARAM)pfnCnrSort, (MPARAM)NULL);
  750.  
  751.     return brc;
  752. }
  753.  
  754. /*
  755.  *@@category: Helpers\PM helpers\Container helpers\View management
  756.  *      functions for easily managing container views.
  757.  *
  758.  *      Most importantly, this has the BEGIN_CNRINFO and END_CNRINFO
  759.  *      macros which make CNRINFO handling easier. Also see
  760.  *      cnrhSetView for that.
  761.  */
  762.  
  763. /* ******************************************************************
  764.  *
  765.  *   View management
  766.  *
  767.  ********************************************************************/
  768.  
  769. /*
  770.  *@@ cnrhSelectAll:
  771.  *      this selects or deselects all records in hwndCnr,
  772.  *      depending on fSelect. Since multiple selections are
  773.  *      not supported in Tree views in the first place, we
  774.  *      only go for the "root" records (and no child records).
  775.  *
  776.  *      Invoking this function on a container in Tree view
  777.  *      will result in not much but display flickering anyway.
  778.  *
  779.  *      This returns the number of records which were processed.
  780.  */
  781.  
  782. ULONG cnrhSelectAll(HWND hwndCnr,
  783.                     BOOL fSelect)
  784. {
  785.     ULONG ulrc = 0;
  786.     PRECORDCORE precc2 = NULL;
  787.  
  788.     do {
  789.         precc2 =
  790.             (PRECORDCORE)WinSendMsg(hwndCnr,
  791.                                     CM_QUERYRECORD,
  792.                                     (MPARAM)precc2,
  793.                                     MPFROM2SHORT(
  794.                                         ((precc2 == NULL) ? CMA_FIRST : CMA_NEXT),
  795.                                         CMA_ITEMORDER)
  796.                                     );
  797.         if ((precc2 == 0) || ((LONG)precc2 == -1))
  798.             // last record or error:
  799.             break;
  800.  
  801.         // select this one
  802.         cnrhSelectRecord(hwndCnr, precc2, fSelect);
  803.         ulrc++;
  804.  
  805.     } while (TRUE);
  806.  
  807.     return ulrc;
  808. }
  809.  
  810. /*
  811.  *@@ cnrhFindRecordFromPoint:
  812.  *      this returns the record under the given
  813.  *      point or NULL if none could be found
  814.  *      (e.g. point is on whitespace).
  815.  *
  816.  *      The point must be specified in container
  817.  *      coordinates, relative to the container
  818.  *      origin (which might be invisible if the
  819.  *      container viewport has been scrolled up).
  820.  *
  821.  *      With fsExtent, specify what part of the
  822.  *      record should be checked. See
  823.  *      cnrhScrollToRecord for more.
  824.  *
  825.  *      This is not terribly fast, because it
  826.  *      will send at least one message for each
  827.  *      record which is currently visible in the
  828.  *      container.
  829.  *
  830.  *      With fl, specify any or none of the following:
  831.  *
  832.  *      --  FRFP_RIGHTSPLITWINDOW: test right split Details view
  833.  *              instead of main cnr.
  834.  *
  835.  *      --  FRFP_SCREENCOORDS: *pptl specifies the point to
  836.  *              check in _screen_ coordinates, instead of
  837.  *              container ccordinates.
  838.  *
  839.  *@@added V0.9.1 (99-11-29) [umoeller]
  840.  *@@changed V0.9.4 (2000-08-08) [umoeller]: fixed viewport/window confusion; now works after cnr resize
  841.  */
  842.  
  843. PRECORDCORE cnrhFindRecordFromPoint(HWND hwndCnr,
  844.                                     PPOINTL pptl,   // in: point to check for
  845.                                     PRECTL prclFoundRecord,
  846.                                             // out: if != NULL and record was
  847.                                             // found, this receives the record rectl
  848.                                             // (or subrectl, depending on fsExtent)
  849.                                     ULONG fsExtent,
  850.                                             // in: one or several of
  851.                                             // CMA_ICON, CMA_TEXT, CMA_TREEICON
  852.                                     ULONG fl)
  853. {
  854.     PRECORDCORE preccReturn = NULL;
  855.     RECTL       rclViewport;
  856.     HAB         habCnr = WinQueryAnchorBlock(hwndCnr);
  857.  
  858.     POINTL      ptlCheck;
  859.  
  860.     // _Pmpf(("Entering cnrhFindRecordFromPoint"));
  861.  
  862.     ptlCheck.x = pptl->x;
  863.     ptlCheck.y = pptl->y;
  864.  
  865.     if (fl & FRFP_SCREENCOORDS)
  866.     {
  867.         // _Pmpf(("  mapping screen %d/%d to cnr", ptlCheck.x, ptlCheck.y));
  868.         WinMapWindowPoints(HWND_DESKTOP,
  869.                            hwndCnr,
  870.                            &ptlCheck,
  871.                            1);
  872.         // _Pmpf(("  got %d/%d", ptlCheck.x, ptlCheck.y));
  873.     }
  874.  
  875.     // get cnr viewport (currently visible client area,
  876.     // relative to cnr origin)
  877.     if (WinSendMsg(hwndCnr,
  878.                    CM_QUERYVIEWPORTRECT,
  879.                    (MPARAM)&rclViewport,
  880.                    MPFROM2SHORT(CMA_WINDOW, // CMA_WORKSPACE, V0.9.4 (2000-08-08) [umoeller]
  881.                                 ((fl & FRFP_RIGHTSPLITWINDOW) != 0))))
  882.                                         // right split window?
  883.     {
  884.         PRECORDCORE         preccFound = (PRECORDCORE)CMA_FIRST;
  885.         QUERYRECFROMRECT    qrfr;
  886.         QUERYRECORDRECT     qrr;
  887.  
  888.         // now enumerate all records which are visible in
  889.         // the viewport
  890.         do
  891.         {
  892.             qrfr.cb = sizeof(qrfr);
  893.             qrfr.rect.xLeft = rclViewport.xLeft;
  894.             qrfr.rect.yBottom = rclViewport.yBottom;
  895.             qrfr.rect.xRight = rclViewport.xRight;
  896.             qrfr.rect.yTop = rclViewport.yTop;
  897.             qrfr.fsSearch = CMA_PARTIAL | CMA_ITEMORDER;
  898.  
  899.             // _Pmpf(("  Enumerating recs in viewport %d, %d, %d, %d",
  900.             //             rclViewport.xLeft, rclViewport.yBottom, rclViewport.xRight, rclViewport.yTop));
  901.  
  902.             preccFound = (PRECORDCORE)WinSendMsg(hwndCnr,
  903.                                                  CM_QUERYRECORDFROMRECT,
  904.                                                  (MPARAM)preccFound, // CMA_FIRST on first loop
  905.                                                  (MPARAM)&qrfr);
  906.             // _Pmpf(("   CM_QUERYRECORDFROMRECT returned 0x%lX", preccFound));
  907.  
  908.             if (    (preccFound != NULL)
  909.                  && (preccFound != (PRECORDCORE)-1)
  910.                )
  911.             {
  912.                 RECTL rclRecord;
  913.                 qrr.cb = sizeof(qrr);
  914.                 qrr.pRecord = preccFound;
  915.                 qrr.fRightSplitWindow = ((fl & FRFP_RIGHTSPLITWINDOW) != 0);
  916.                 qrr.fsExtent = fsExtent;
  917.                 if (WinSendMsg(hwndCnr,
  918.                                CM_QUERYRECORDRECT,
  919.                                (MPARAM)&rclRecord,
  920.                                (MPARAM)&qrr))
  921.                 {
  922.                     if (WinPtInRect(habCnr,
  923.                                     &rclRecord,
  924.                                     &ptlCheck))
  925.                     {
  926.                         // yes, it's in the record:
  927.                         // return that
  928.                         preccReturn = preccFound;
  929.  
  930.                         // if rectangle is queried,
  931.                         // copy that
  932.                         if (prclFoundRecord)
  933.                             memcpy(prclFoundRecord, &rclRecord, sizeof(RECTL));
  934.                         break;
  935.                     }
  936.                 }
  937.             }
  938.             else
  939.                 // last one or error:
  940.                 break;
  941.         } while (TRUE);
  942.     }
  943.     // else
  944.     //     _Pmpf(("  CM_QUERYVIEWPORTRECT failed."));
  945.  
  946.     return preccReturn;
  947. }
  948.  
  949. /*
  950.  *@@ cnrhExpandFromRoot:
  951.  *      expands prec and climbs up all parent
  952.  *      records and expands them all as well.
  953.  *
  954.  *      Returns the no. of records expanded.
  955.  *
  956.  *@@added V0.9.9 (2001-03-13) [umoeller]
  957.  */
  958.  
  959. ULONG cnrhExpandFromRoot(HWND hwndCnr,
  960.                          PRECORDCORE prec)
  961. {
  962.     ULONG ul = 0;
  963.     PRECORDCORE preccParent = prec;
  964.     while (preccParent)
  965.     {
  966.         WinSendMsg(hwndCnr, CM_EXPANDTREE, (MPARAM)preccParent, MPNULL);
  967.         ul++;
  968.  
  969.         preccParent = (PRECORDCORE)WinSendMsg(hwndCnr,
  970.                                               CM_QUERYRECORD,
  971.                                               (MPARAM)preccParent,
  972.                                               MPFROM2SHORT(CMA_PARENT,
  973.                                                            CMA_ITEMORDER));
  974.  
  975.         if (preccParent == (PRECORDCORE)-1)
  976.             preccParent = NULL;
  977.     }
  978.  
  979.     return ul;
  980. }
  981.  
  982. /*
  983.  *@@ cnrhScrollToRecord:
  984.  *      scrolls a given container control to make a given
  985.  *      record visible.
  986.  *
  987.  *      Returns:
  988.  *      --  0:       OK, scrolled
  989.  *      --  1:       record rectangle query failed (error)
  990.  *      --  2:       cnr viewport query failed (error)
  991.  *      --  3:       record is already visible (scrolling not necessary)
  992.  *      --  4:       cnrinfo query failed (error)
  993.  *
  994.  *      Note: All messages are _sent_ to the container, not posted.
  995.  *      Scrolling therefore occurs synchroneously before this
  996.  *      function returns.
  997.  *
  998.  *      This function an improved version of the one (W)(C) Dan Libby, found at
  999.  *      http://zebra.asta.fh-weingarten.de/os2/Snippets/Howt6364.HTML
  1000.  *      Improvements (C) 1998 Ulrich Möller.
  1001.  *
  1002.  *@@changed V0.9.4 (2000-08-07) [umoeller]: now posting scroll messages to avoid sync errors
  1003.  *@@changed V0.9.9 (2001-03-12) [umoeller]: this never scrolled for root records in tree view if KeepParent == TRUE, fixed
  1004.  *@@changed V0.9.9 (2001-03-13) [umoeller]: largely rewritten; this now scrolls x properly too and is faster
  1005.  */
  1006.  
  1007. ULONG cnrhScrollToRecord(HWND hwndCnr,       // in: container window
  1008.                          PRECORDCORE pRec,   // in: record to scroll to
  1009.                          ULONG fsExtent,
  1010.                                  // in: this determines what parts of pRec
  1011.                                  // should be made visible. OR the following
  1012.                                  // flags:
  1013.                                  // -- CMA_ICON: the icon rectangle
  1014.                                  // -- CMA_TEXT: the record text
  1015.                                  // -- CMA_TREEICON: the "+" sign in tree view
  1016.                          BOOL fKeepParent)
  1017.                                  // for tree views only: whether to keep
  1018.                                  // the parent record of pRec visible when scrolling.
  1019.                                  // If scrolling to pRec would make the parent
  1020.                                  // record invisible, we instead scroll so that
  1021.                                  // the parent record appears at the top of the
  1022.                                  // container workspace (Win95 style).
  1023.  
  1024. {
  1025.     QUERYRECORDRECT qRect;
  1026.     RECTL           rclRecord,
  1027.                     rclCnr;
  1028.     LONG            lXOfs = 0,
  1029.                     lYOfs = 0;
  1030.  
  1031.     qRect.cb = sizeof(qRect);
  1032.     qRect.pRecord = (PRECORDCORE)pRec;
  1033.     qRect.fsExtent = fsExtent;
  1034.  
  1035.     if (fKeepParent)
  1036.     {
  1037.         CNRINFO         CnrInfo;
  1038.         // this is only valid in tree view, so check
  1039.         if (!WinSendMsg(hwndCnr,
  1040.                         CM_QUERYCNRINFO,
  1041.                         (MPARAM)&CnrInfo,
  1042.                         (MPARAM)sizeof(CnrInfo)))
  1043.             return 4;
  1044.         else
  1045.             // disable if not tree view
  1046.             fKeepParent = ((CnrInfo.flWindowAttr & CV_TREE) != 0);
  1047.     }
  1048.  
  1049.     // query record location and size of container
  1050.     if (!WinSendMsg(hwndCnr,
  1051.                     CM_QUERYRECORDRECT,
  1052.                     &rclRecord,
  1053.                     &qRect))
  1054.         return 1;
  1055.  
  1056.     if (!WinSendMsg(hwndCnr,
  1057.                     CM_QUERYVIEWPORTRECT,
  1058.                     &rclCnr,
  1059.                     MPFROM2SHORT(CMA_WINDOW,
  1060.                                     // returns the client area rectangle
  1061.                                     // in container window coordinates
  1062.                                  FALSE)) )
  1063.         return 2;
  1064.  
  1065.     // check if left bottom point of pRec is currently visible in container
  1066.  
  1067.     #define IS_BETWEEN(a, b, c) (((a) >= (b)) && ((a) <= (c)))
  1068.  
  1069.     // 1) set lXOfs if we need to scroll horizontally
  1070.     if (!IS_BETWEEN(rclRecord.xLeft, rclCnr.xLeft, rclCnr.xRight))
  1071.         // record xLeft is outside viewport:
  1072.         // scroll horizontally so that xLeft is exactly on left of viewport
  1073.         lXOfs = (rclRecord.xLeft - rclCnr.xLeft);
  1074.     else if (!IS_BETWEEN(rclRecord.xRight, rclCnr.xLeft, rclCnr.xRight))
  1075.         // record xRight is outside viewport:
  1076.         // scroll horizontally so that xRight is exactly on right of viewport
  1077.         lXOfs = (rclRecord.xRight - rclCnr.xRight);
  1078.  
  1079.     // 2) set lYOfs if we need to scroll vertically
  1080.     if (!IS_BETWEEN(rclRecord.yBottom, rclCnr.yBottom, rclCnr.yTop))
  1081.         // record yBottom is outside viewport:
  1082.         // scroll horizontally so that yBottom is exactly on bottom of viewport
  1083.         lYOfs =   (rclCnr.yBottom - rclRecord.yBottom)    // this would suffice
  1084.                 + (rclRecord.yTop - rclRecord.yBottom);
  1085.                             // but we make the next rcl visible too
  1086.     else if (!IS_BETWEEN(rclRecord.yTop, rclCnr.yBottom, rclCnr.yTop))
  1087.         // record yTop is outside viewport:
  1088.         // scroll horizontally so that yTop is exactly on top of viewport
  1089.         lYOfs = (rclRecord.yTop - rclCnr.yTop);
  1090.  
  1091.     if (fKeepParent && (lXOfs || lYOfs))
  1092.     {
  1093.         // keep parent enabled, and we're scrolling:
  1094.         // find the parent record then
  1095.         qRect.cb = sizeof(qRect);
  1096.         qRect.pRecord = (PRECORDCORE)WinSendMsg(hwndCnr,
  1097.                                                 CM_QUERYRECORD,
  1098.                                                 (MPARAM)pRec,
  1099.                                                 MPFROM2SHORT(CMA_PARENT,
  1100.                                                              CMA_ITEMORDER));
  1101.         if (qRect.pRecord)     // V0.9.9 (2001-03-12) [umoeller]
  1102.         {
  1103.             // parent exists:
  1104.             // get PARENT record rectangle then
  1105.             RECTL rclParentRecord;
  1106.             qRect.fsExtent = fsExtent;
  1107.             if (WinSendMsg(hwndCnr,
  1108.                            CM_QUERYRECORDRECT,
  1109.                            &rclParentRecord,
  1110.                            &qRect))
  1111.             {
  1112.                 // check if parent record WOULD still be visible
  1113.                 // if we scrolled what we calculated above
  1114.                 RECTL rclCnr2;
  1115.                 memcpy(&rclCnr2, &rclCnr, sizeof(rclCnr2));
  1116.                 winhOffsetRect(&rclCnr2, lXOfs, -lYOfs);
  1117.  
  1118.                 if (    lXOfs
  1119.                      && (!IS_BETWEEN(rclParentRecord.xLeft, rclCnr2.xLeft, rclCnr2.xRight))
  1120.                    )
  1121.                     // record xLeft is outside viewport:
  1122.                     // scroll horizontally so that xLeft is exactly on left of viewport
  1123.                     lXOfs = (rclParentRecord.xLeft - rclCnr.xLeft);
  1124.  
  1125.                 if (    lYOfs
  1126.                      && (!IS_BETWEEN(rclParentRecord.yBottom, rclCnr2.yBottom, rclCnr2.yTop))
  1127.                    )
  1128.                     // record yBottom is outside viewport:
  1129.                     // recalculate y ofs so that we scroll so
  1130.                     // that parent record is on top of cnr viewport
  1131.                     lYOfs =   (rclCnr.yTop - rclParentRecord.yTop) // this would suffice
  1132.                             - (rclRecord.yTop - rclRecord.yBottom);  // but we make the previous rcl visible too
  1133.             }
  1134.         }
  1135.     }
  1136.  
  1137.     // V0.9.14 (2001-07-28) [umoeller]
  1138.     // tried WinSendMsg (instead of post) because
  1139.     // otherwise we can't get auto-edit on wpshCreateFromTemplate
  1140.     // to work... but this causes two problems:
  1141.     // --  for some reason, the WPS selects the recc under
  1142.     //     the mouse then, which is very wrong in most situations;
  1143.     // --  since the WPS expands the root record in tree views
  1144.     //     after the root has been populated, this can cause
  1145.     //     totally garbled display for complex trees.
  1146.     // So I'm back to WinPostMsg... // @@todo
  1147.  
  1148.     if (lXOfs)
  1149.         // scroll horizontally
  1150.         WinPostMsg(hwndCnr,
  1151.                    CM_SCROLLWINDOW,
  1152.                    (MPARAM)CMA_HORIZONTAL,
  1153.                    (MPARAM)lXOfs);
  1154.  
  1155.     // scroll vertically
  1156.     if (lYOfs)
  1157.         WinPostMsg(hwndCnr,
  1158.                    CM_SCROLLWINDOW,
  1159.                    (MPARAM)CMA_VERTICAL,
  1160.                    (MPARAM)lYOfs);
  1161.  
  1162.     return 0;
  1163. }
  1164.  
  1165. /*
  1166.  *@@ cnrhShowContextMenu:
  1167.  *      this function shows the given menu as a context
  1168.  *      menu for the given record core (using WinPopupMenu).
  1169.  *      This function may be used when receiving WM_CONTROL
  1170.  *      with CN_CONTEXTMENU from the container, which has
  1171.  *      the preccSource in mp2.
  1172.  *
  1173.  *      In detail, this function does the following:
  1174.  *
  1175.  *      1)  query the coordinates where to show the menu;
  1176.  *          if (preccSource), this will be next to the
  1177.  *          record core, otherwise (i.e. menu requested
  1178.  *          for whitespace) the mouse coordinates over
  1179.  *          the container;
  1180.  *
  1181.  *      2)  give preccSource (or, if NULL, the whole
  1182.  *          container) source emphasis;
  1183.  *
  1184.  *      3)  call WinPopupMenu.
  1185.  *
  1186.  *      Note: It is the responsibility of the caller to catch
  1187.  *      WM_MENUEND in the window procedure of hwndMenuOwner later
  1188.  *      to remove the source emphasis for preccSource again.
  1189.  *
  1190.  *      This function returns FALSE if an error occured.
  1191.  *
  1192.  *@@added V0.9.0 [umoeller]
  1193.  */
  1194.  
  1195. BOOL cnrhShowContextMenu(HWND hwndCnr,
  1196.                          PRECORDCORE preccSource, // in: mp2 of CN_CONTEXTMENU
  1197.                          HWND hMenu,              // in: menu to show
  1198.                          HWND hwndMenuOwner)      // in: menu owner (where the
  1199.                                                   // WM_COMMAND will go to)
  1200. {
  1201.     BOOL    brc = FALSE;
  1202.     if (hMenu)
  1203.     {
  1204.         BOOL        fQueried = FALSE;
  1205.  
  1206.         POINTL ptl;
  1207.         if (preccSource)
  1208.         {
  1209.             CNRINFO     CnrInfo;
  1210.             cnrhQueryCnrInfo(hwndCnr, &CnrInfo);
  1211.  
  1212.             if ((CnrInfo.flWindowAttr & CV_DETAIL) == 0)
  1213.             {
  1214.                 // if we're not in Details view:
  1215.                 // calculate the point where to show the context
  1216.                 // menu; we use the lower-right corner of the
  1217.                 // source record core
  1218.                 QUERYRECORDRECT qRect;
  1219.                 RECTL           rclRecc;
  1220.                 qRect.cb = sizeof(qRect);
  1221.                 qRect.pRecord = preccSource;
  1222.                 qRect.fsExtent = CMA_TEXT;
  1223.                 WinSendMsg(hwndCnr,
  1224.                            CM_QUERYRECORDRECT,
  1225.                            &rclRecc,
  1226.                            &qRect);
  1227.                 ptl.x = rclRecc.xRight;
  1228.                 ptl.y = rclRecc.yBottom;
  1229.                     // now we have the lower-right corner in cnr coords
  1230.  
  1231.                 // clip if this is outside the container window
  1232.                 WinQueryWindowRect(hwndCnr, &rclRecc);
  1233.                 if (ptl.x > rclRecc.xRight)
  1234.                     ptl.x = rclRecc.xRight;
  1235.                 if (ptl.y > rclRecc.yTop)
  1236.                     ptl.y = rclRecc.yTop;
  1237.  
  1238.                 // convert this to screen coordinates
  1239.                 WinMapWindowPoints(hwndCnr,
  1240.                                    HWND_DESKTOP,
  1241.                                    &ptl,
  1242.                                    1);
  1243.                 fQueried = TRUE;
  1244.             }
  1245.         }
  1246.  
  1247.         if (!fQueried)
  1248.             // else: use mouse coordinates for context menu
  1249.             WinQueryPointerPos(HWND_DESKTOP, &ptl);
  1250.  
  1251.         // give preccSource source emphasis;
  1252.         // if preccSource == NULL, the whole container will be
  1253.         // given source emphasis
  1254.         WinSendMsg(hwndCnr,
  1255.                    CM_SETRECORDEMPHASIS,
  1256.                    (MPARAM)preccSource,     // might be NULL for whole container
  1257.                    MPFROM2SHORT(TRUE,  // set emphasis
  1258.                             CRA_SOURCE));
  1259.  
  1260.         // finally, show context menu
  1261.         brc = WinPopupMenu(HWND_DESKTOP,               // menu parent
  1262.                            hwndMenuOwner,              // menu owner
  1263.                            hMenu,
  1264.                            (SHORT)ptl.x,
  1265.                            (SHORT)ptl.y,
  1266.                            0,                          // ID
  1267.                            PU_NONE
  1268.                               | PU_MOUSEBUTTON1
  1269.                               | PU_KEYBOARD
  1270.                               | PU_HCONSTRAIN
  1271.                               | PU_VCONSTRAIN);
  1272.     }
  1273.  
  1274.     return brc;
  1275. }
  1276.  
  1277. /*
  1278.  *@@ cnrhQuerySourceRecord:
  1279.  *      this helper function evaluates a given container
  1280.  *      to find out which records have been selected while
  1281.  *      a context menu is open.
  1282.  *
  1283.  *      This is for implementing a WPS-like (probably CUA) behavior
  1284.  *      when invoking actions on container records. That is:
  1285.  *
  1286.  *      1)  If the user opens a context menu on a selected object,
  1287.  *          the selected action should be invoked on _all_ selected
  1288.  *          objects.
  1289.  *
  1290.  *      2)  If the user opens a context menu on an object which is
  1291.  *          _not_ selected, the action should be invoked on that
  1292.  *          object only (which should be given source emphasis),
  1293.  *          no matter what other objects are selected.
  1294.  *
  1295.  *      This function expects in preccSource the record core which
  1296.  *      currently has (or just had) source emphasis.
  1297.  *
  1298.  *      1)  In your own programs, you should have used cnrhShowContextMenu
  1299.  *          above, which sets record source emphasis correctly. Unfortunately,
  1300.  *          PM posts WM_MENUEND _before_ WM_COMMAND, so if you remove source
  1301.  *          emphasis in WM_MENUEND, it is unknown which record was selected at
  1302.  *          the time WM_COMMAND comes in... so you need to store that record
  1303.  *          and pass it to this function later. Sorry.
  1304.  *
  1305.  *      2)  With the WPS, this function works within the wpMenuItemSelected
  1306.  *          method, because that one is invoked during the processing of
  1307.  *          WM_COMMAND and the WPS automatically does the source emphasis
  1308.  *          stuff right.
  1309.  *
  1310.  *      The result of this evaluation is stored in *pulSelection,
  1311.  *      which can be:
  1312.  *
  1313.  *      --   SEL_WHITESPACE (1): the context menu was opened on the
  1314.  *                          whitespace of the container (preccSource
  1315.  *                          is NULL);
  1316.  *                          this func then returns NULL also.
  1317.  *      --   SEL_SINGLESEL (2): the context menu was opened for a
  1318.  *                          single selected object:
  1319.  *                          this func then returns that record core
  1320.  *                          (which is preccSource).
  1321.  *      --   SEL_MULTISEL (3): the context menu was opened on one
  1322.  *                          of a multitude of selected record;
  1323.  *                          this func then returns the first of the
  1324.  *                          selected records. Use
  1325.  *                          cnrhQueryNextSelectedRecord to get the others.
  1326.  *      --   SEL_SINGLEOTHER (4): the context menu was opened for a
  1327.  *                          single record _other_ than the selected
  1328.  *                          records:
  1329.  *                          this func then returns that record
  1330.  *                          (which is preccSource).
  1331.  *
  1332.  *@@added V0.9.0 [umoeller]
  1333.  */
  1334.  
  1335. PRECORDCORE cnrhQuerySourceRecord(HWND hwndCnr,          // in:  cnr
  1336.                                   PRECORDCORE preccSource, // in: record which had source emphasis
  1337.                                   PULONG pulSelection)   // out: selection flags
  1338. {
  1339.     PRECORDCORE preccReturn = NULL;
  1340.  
  1341.     if (preccSource == NULL)
  1342.     {
  1343.         // this probably means that the whole container has
  1344.         // source emphasis --> context menu on folder whitespace
  1345.         *pulSelection = SEL_WHITESPACE;
  1346.                 // and return NULL
  1347.     }
  1348.     else if (((LONG)preccSource) != -1) // no error?
  1349.     {
  1350.         // check whether the source record is also selected
  1351.         if ((preccSource->flRecordAttr & CRA_SELECTED) == 0)
  1352.         {
  1353.             // no:
  1354.             // only one object, but not one of the selected ones
  1355.             // (if any at all)
  1356.             preccReturn = preccSource;
  1357.             *pulSelection = SEL_SINGLEOTHER;
  1358.         }
  1359.         else
  1360.         {
  1361.             // yes, source record _is_ selected:
  1362.             // check whether we have more than one selected record?
  1363.  
  1364.             // get first selected record
  1365.             PRECORDCORE preccSelected = (PRECORDCORE)WinSendMsg(hwndCnr,
  1366.                                                                 CM_QUERYRECORDEMPHASIS,
  1367.                                                                 (MPARAM)CMA_FIRST,
  1368.                                                                 (MPARAM)CRA_SELECTED);
  1369.             // return that one
  1370.             preccReturn = preccSelected;
  1371.  
  1372.             // are several objects selected?
  1373.             preccSelected = (PRECORDCORE)WinSendMsg(hwndCnr,
  1374.                                                    CM_QUERYRECORDEMPHASIS,
  1375.                                                    (MPARAM)preccSelected,
  1376.                                                             // get next selected
  1377.                                                    (MPARAM)CRA_SELECTED);
  1378.             if (preccSelected)
  1379.                 // several objects
  1380.                 *pulSelection = SEL_MULTISEL;
  1381.             else
  1382.                 // only one object
  1383.                 *pulSelection = SEL_SINGLESEL;
  1384.         }
  1385.     }
  1386.  
  1387.     return preccReturn;
  1388. }
  1389.  
  1390. /*
  1391.  *@@ cnrhQueryNextSelectedRecord:
  1392.  *      if cnrhQuerySourceRecord above returns SEL_MULTISEL
  1393.  *      you can use this helper func to loop thru all the
  1394.  *      selected records. This will return the next record
  1395.  *      after preccCurrent which is selected or NULL if it's the last.
  1396.  *
  1397.  *      If you're not using cnrhQuerySourceRecord (because your
  1398.  *      records do not have source emphasis), you can also call
  1399.  *      this function with (preccCurrent == CMA_FIRST) to get the
  1400.  *      first selected record core.
  1401.  */
  1402.  
  1403. PRECORDCORE cnrhQueryNextSelectedRecord(HWND hwndCnr,
  1404.                                         PRECORDCORE preccCurrent)
  1405. {
  1406.     PRECORDCORE preccReturn = 0;
  1407.     if (preccCurrent)
  1408.     {
  1409.         PRECORDCORE preccNext = (PRECORDCORE)WinSendMsg(hwndCnr,
  1410.                                                         CM_QUERYRECORDEMPHASIS,
  1411.                                                         (MPARAM)preccCurrent,
  1412.                                                         (MPARAM)CRA_SELECTED);
  1413.         if ((preccNext) && ((LONG)preccNext != -1) )
  1414.             preccReturn = preccNext;
  1415.     }
  1416.  
  1417.     return preccReturn;
  1418. }
  1419.  
  1420. /*
  1421.  *@@category: Helpers\PM helpers\Container helpers\Record relations/iteration
  1422.  *      functions for querying the relation of records and
  1423.  *      iterating through records.
  1424.  */
  1425.  
  1426. /* ******************************************************************
  1427.  *
  1428.  *   Record relations/iteration
  1429.  *
  1430.  ********************************************************************/
  1431.  
  1432. /*
  1433.  *@@ cnrhQueryRecordIndex:
  1434.  *      returns the "index" of the specified record
  1435.  *      in the specified container. That is, if the
  1436.  *      specified record is the first record in the
  1437.  *      container, 0 is returned. If it's the second,
  1438.  *      1 is returned, and so on.
  1439.  *
  1440.  *      Returns -1 if precc could not be found.
  1441.  *
  1442.  *      Doesn't work in Tree view.
  1443.  *
  1444.  *@@added V0.9.3 (2000-04-19) [umoeller]
  1445.  */
  1446.  
  1447. LONG cnrhQueryRecordIndex(HWND hwndCnr,
  1448.                           PRECORDCORE precc)
  1449. {
  1450.     PRECORDCORE precc2 = (PRECORDCORE)CMA_FIRST;
  1451.     BOOL        fFirstCall = TRUE;
  1452.     LONG        lrc = -1,
  1453.                 lCount = 0;
  1454.  
  1455.     while (TRUE)
  1456.     {
  1457.         precc2 =
  1458.             (PRECORDCORE)WinSendMsg(hwndCnr,
  1459.                                     CM_QUERYRECORD,
  1460.                                     (MPARAM)precc2,  // ignored on first call
  1461.                                     MPFROM2SHORT(
  1462.                                             (fFirstCall)
  1463.                                                 ? CMA_FIRST
  1464.                                                 : CMA_NEXT,
  1465.                                             CMA_ITEMORDER)
  1466.                                     );
  1467.         if (     (precc2 == NULL)
  1468.               || (precc2 == (PRECORDCORE)-1)
  1469.            )
  1470.             // error:
  1471.             // return -1
  1472.             break;
  1473.         else
  1474.             if (precc2 == precc)
  1475.             {
  1476.                 // same as search record:
  1477.                 lrc = lCount;
  1478.                 break;
  1479.             }
  1480.  
  1481.         // else search on
  1482.         fFirstCall = FALSE;
  1483.         lCount++;
  1484.     }
  1485.  
  1486.     return lrc;
  1487. }
  1488.  
  1489. /*
  1490.  *@@ cnrhIsChildOf:
  1491.  *      returns TRUE only if precTest is a child record
  1492.  *      of precParent in a tree view.
  1493.  *
  1494.  *@@added V0.9.7 (2000-12-13) [umoeller]
  1495.  */
  1496.  
  1497. BOOL cnrhIsChildOf(HWND hwndCnr,
  1498.                    PRECORDCORE precTest,        // in: recc to test
  1499.                    PRECORDCORE precParent)      // in: parent to test
  1500. {
  1501.     BOOL brc = FALSE;
  1502.     if ((precTest) && (precParent))
  1503.     {
  1504.         PRECORDCORE precParentThis = precTest;
  1505.  
  1506.         while (precParentThis)
  1507.         {
  1508.             // first call: get parent of precTest;
  1509.             // subsequent calls: climb up
  1510.             precParentThis = (PRECORDCORE)WinSendMsg(hwndCnr,
  1511.                                                      CM_QUERYRECORD,
  1512.                                                      precParentThis,
  1513.                                                      MPFROM2SHORT(CMA_PARENT,
  1514.                                                                   CMA_ITEMORDER));
  1515.             if (precParentThis == (PRECORDCORE)-1)
  1516.                 break;
  1517.             else
  1518.                 if (precParentThis == precParent)
  1519.                 {
  1520.                     brc = TRUE;
  1521.                     break;
  1522.                 }
  1523.         }
  1524.     }
  1525.  
  1526.     return brc;
  1527. }
  1528.  
  1529. /*
  1530.  *@@ cnrhForAllRecords:
  1531.  *      this monster function calls pfnwpCallback
  1532.  *      for really all the records in the container,
  1533.  *      including child records in tree view.
  1534.  *
  1535.  *      This is extremely useful for cleaning up
  1536.  *      all record cores before a container window
  1537.  *      gets destroyed.
  1538.  *
  1539.  *      This function recurses for child records.
  1540.  *      On the first call, preccParent should be
  1541.  *      NULL; you may however specify a certain
  1542.  *      record, and this function will call the
  1543.  *      callback only for that record and children.
  1544.  *
  1545.  *      pfnwpCallback gets called with the following
  1546.  *      parameters:
  1547.  *
  1548.  *      -- HWND hwnd: hwndCnr, as passed to this func
  1549.  *      -- PRECORDCORE precc: current record core, as
  1550.  *                     determined by this func.
  1551.  *      -- ULONG ulUser1/2: what you have specified here.
  1552.  *
  1553.  *      It must be declared as follows:
  1554.  *
  1555.  +          ULONG EXPENTRY fncb(HWND hwndCnr,
  1556.  +                              PRECORDCORE precc,
  1557.  +                              ULONG ulUser1,
  1558.  +                              ULONG ulUser2)
  1559.  *
  1560.  *      If the callback returns anything != 0, this
  1561.  *      function stops even before all records have
  1562.  *      been processed. You can use ulUser for a
  1563.  *      pointer to a return value.
  1564.  *
  1565.  *      This always returns the no. of records which
  1566.  *      were processed.
  1567.  *
  1568.  *      If you use this function for deleting record
  1569.  *      cores, you can be sure that you can delete
  1570.  *      every record, because your callback gets called
  1571.  *      for the child records before the parent record.
  1572.  *
  1573.  *@@added V0.9.0 [umoeller]
  1574.  */
  1575.  
  1576. ULONG cnrhForAllRecords(HWND hwndCnr,
  1577.                         PRECORDCORE preccParent,    // in: NULL for root
  1578.                         PFNCBRECC pfncbRecc,        // in: callback
  1579.                         ULONG ulUser1,
  1580.                         ULONG ulUser2)
  1581. {
  1582.     PRECORDCORE precc2 = preccParent;
  1583.     ULONG       ulrc = 0;
  1584.     USHORT      usQuery;
  1585.     BOOL        fFirstCall = TRUE;
  1586.  
  1587.     while (TRUE)
  1588.     {
  1589.         if (fFirstCall)
  1590.         {
  1591.             // first call:
  1592.             if (preccParent)
  1593.                 // non-root:
  1594.                 usQuery = CMA_FIRSTCHILD;
  1595.             else
  1596.                 // NULL == root:
  1597.                 usQuery = CMA_FIRST;
  1598.         }
  1599.         else
  1600.             // subsequent calls:
  1601.             usQuery = CMA_NEXT;     // works as CMA_NEXTCHILD also
  1602.  
  1603.         precc2 =
  1604.             (PRECORDCORE)WinSendMsg(hwndCnr,
  1605.                                     CM_QUERYRECORD,
  1606.                                     (MPARAM)((fFirstCall)
  1607.                                          // first call (CMA_FIRSTCHILD or CMA_FIRST):
  1608.                                          ? preccParent   // ignored for CMA_FIRST
  1609.                                          // subsequent calls (CMA_NEXTCHILD or CMA_NEXT):
  1610.                                          : precc2),  // what we queried last
  1611.                                     MPFROM2SHORT(
  1612.                                             usQuery,    // set above
  1613.                                             CMA_ITEMORDER)
  1614.                                     );
  1615.  
  1616.         if ((precc2) && ((ULONG)precc2 != -1))
  1617.         {
  1618.             // record found:
  1619.             // recurse for that record
  1620.             ulrc += cnrhForAllRecords(hwndCnr,
  1621.                                       precc2,        // new parent to search
  1622.                                       pfncbRecc,
  1623.                                       ulUser1,
  1624.                                       ulUser2);
  1625.  
  1626.             // _Pmpf(("Calling callback for %s", precc2->pszIcon));
  1627.  
  1628.             // call callback
  1629.             if (pfncbRecc)
  1630.                 if ((*pfncbRecc)(hwndCnr, precc2, ulUser1, ulUser2))
  1631.                     // returns something != NULL:
  1632.                     // stop
  1633.                     break;
  1634.             ulrc++;
  1635.         }
  1636.         else
  1637.             // no more records or error: get outta here
  1638.             break;
  1639.  
  1640.         fFirstCall = FALSE;
  1641.     }
  1642.  
  1643.     return ulrc;
  1644. }
  1645.  
  1646. /*
  1647.  * cnrhForAllChildRecords:
  1648.  *      calls the specified fncbRecc callback for
  1649.  *      the specified recc and all its child records.
  1650.  *
  1651.  *@@added V0.9.0 [umoeller]
  1652.  */
  1653.  
  1654. /* VOID cnrhForAllChildRecords(HWND hwndCnr,
  1655.                             PRECORDCORE precc,
  1656.                             PFNCBRECC pfncbRecc,
  1657.                             ULONG ulp1,
  1658.                             ULONG ulp2)
  1659. {
  1660.     PRECORDCORE precc2 = precc;
  1661.     (*pfncbRecc)(precc, ulp1, ulp2);
  1662.     do {
  1663.         precc2 =
  1664.             (PRECORDCORE)WinSendMsg(hwndCnr,
  1665.                     CM_QUERYRECORD,
  1666.                     (MPARAM)precc2,
  1667.                     MPFROM2SHORT(
  1668.                         (precc2 == precc)
  1669.                             ? CMA_FIRSTCHILD : CMA_NEXT,
  1670.                         CMA_ITEMORDER)
  1671.                     );
  1672.         if ((LONG)precc2 == -1)
  1673.             precc2 = NULL;
  1674.         if (precc2)
  1675.             // recurse again
  1676.             cnrhForAllChildRecords(hwndCnr, precc2, pfncbRecc, ulp1, ulp2);
  1677.     } while (precc2);
  1678. } */
  1679.  
  1680. /*
  1681.  * cnrhForAllRecords2:
  1682.  *      this is a useful function which calls
  1683.  *      the specified callback function for
  1684.  *      really all records in the container of
  1685.  *      the main window, including child records.
  1686.  *
  1687.  *      xxx
  1688.  *
  1689.  *@@added V0.9.0 [umoeller]
  1690.  */
  1691.  
  1692. /* VOID cnrhForAllRecords2(HWND hwndCnr,
  1693.                         PFNCBRECC pfncbRecc,
  1694.                         ULONG ulp1,
  1695.                         ULONG ulp2)
  1696. {
  1697.     PRECORDCORE precc2 = NULL;
  1698.     do {
  1699.         precc2 =
  1700.             (PRECORDCORE)WinSendMsg(hwndCnr,
  1701.                     CM_QUERYRECORD,
  1702.                     (MPARAM)precc2,
  1703.                     MPFROM2SHORT(
  1704.                         ((precc2 == NULL) ? CMA_FIRST : CMA_NEXT),
  1705.                         CMA_ITEMORDER)
  1706.                     );
  1707.         if ((LONG)precc2 == -1)
  1708.             precc2 = NULL;
  1709.         if (precc2)
  1710.             // recurse again
  1711.             cnrhForAllChildRecords(hwndCnr, precc2, pfncbRecc, ulp1, ulp2);
  1712.     } while (precc2);
  1713. } */
  1714.  
  1715. /*
  1716.  * cnrhForAllParentRecords:
  1717.  *      just as above, but climbs up instead.
  1718.  *      The first call of the callback will be
  1719.  *      for the parent record of precc.
  1720.  *
  1721.  *@@added V0.9.0 [umoeller]
  1722.  */
  1723.  
  1724. /* VOID cnrhForAllParentRecords(HWND hwndCnr,
  1725.                              PRECORDCORE precc,
  1726.                              PFNCBRECC pfncbRecc,
  1727.                              ULONG ulp1,
  1728.                              ULONG ulp2)
  1729. {
  1730.     PRECORDCORE precc2 = precc;
  1731.     do {
  1732.         precc2 =
  1733.             (PRECORDCORE)WinSendMsg(hwndCnr,
  1734.                                     CM_QUERYRECORD,
  1735.                                     (MPARAM)precc2,
  1736.                                     MPFROM2SHORT(CMA_PARENT,
  1737.                                         CMA_ITEMORDER)
  1738.                                     );
  1739.         if ((LONG)precc2 == -1)
  1740.             precc2 = NULL;
  1741.         if (precc2)
  1742.             (*pfncbRecc)(precc2, ulp1, ulp2);
  1743.     } while (precc2);
  1744. } */
  1745.  
  1746. /*
  1747.  *@@category: Helpers\PM helpers\Container helpers\Miscellaneous
  1748.  */
  1749.  
  1750. /* ******************************************************************
  1751.  *
  1752.  *   Miscellaneous
  1753.  *
  1754.  ********************************************************************/
  1755.  
  1756. /*
  1757.  *@@ cnrhQueryCnrFromFrame:
  1758.  *      find the handle of a frame window's container; we do this
  1759.  *      by enumerating all the frame's child windows and comparing
  1760.  *      their window classes to the WC_CONTAINER code.
  1761.  *      This is not terribly fast, so use this func economically.
  1762.  *      Returns NULLHANDLE if not found.
  1763.  */
  1764.  
  1765. HWND cnrhQueryCnrFromFrame(HWND hwndFrame)
  1766. {
  1767.     HENUM               henum;
  1768.     CHAR                szClassName[256];
  1769.     HWND                hwndCnr = NULLHANDLE,
  1770.                         hwndTemp = NULLHANDLE;
  1771.  
  1772.     if (hwndFrame)
  1773.     {
  1774.         if (henum = WinBeginEnumWindows(hwndFrame))
  1775.         {
  1776.             do
  1777.             {
  1778.                 if (hwndTemp = WinGetNextWindow(henum))
  1779.                 {
  1780.                     if (WinQueryClassName(hwndTemp, 250, szClassName))
  1781.                         if (strcmp(szClassName, "#37") == 0)
  1782.                             // unbelievable, this is PM's code for WC_CONTAINER...
  1783.                             hwndCnr = hwndTemp;
  1784.                 }
  1785.             } while (hwndTemp);
  1786.         }
  1787.         WinEndEnumWindows(henum);
  1788.     }
  1789.  
  1790.     return hwndCnr;
  1791. }
  1792.  
  1793. /*
  1794.  *@@ cnrhOpenEdit:
  1795.  *      begins direct text editing on the first
  1796.  *      record that is currently cursored in
  1797.  *      the container.
  1798.  *
  1799.  *      This is the recommended response when
  1800.  *      the almost documented WM_TEXTEDIT
  1801.  *      message comes into the container owner's
  1802.  *      window procedure. This is the case when
  1803.  *      the user presses the key combo that was
  1804.  *      set for direct text editing in the "Keyboard"
  1805.  *      object.
  1806.  *
  1807.  *      Returns TRUE if direct editing was
  1808.  *      successfully started. If FALSE is returned,
  1809.  *      there is either no record with CRA_CURSORED
  1810.  *      emphasis, or that record has the read-only
  1811.  *      flag set.
  1812.  *
  1813.  *@@added V0.9.19 (2002-04-02) [umoeller]
  1814.  */
  1815.  
  1816. BOOL cnrhOpenEdit(HWND hwndCnr)
  1817. {
  1818.     BOOL brc = FALSE;
  1819.     CNREDITDATA ced;
  1820.     memset(&ced, 0, sizeof(ced));
  1821.     ced.cb = sizeof(ced);
  1822.     ced.hwndCnr = hwndCnr;
  1823.     if (ced.pRecord = (PRECORDCORE)WinSendMsg(hwndCnr,
  1824.                                               CM_QUERYRECORDEMPHASIS,
  1825.                                               (MPARAM)CMA_FIRST,
  1826.                                               (MPARAM)CRA_CURSORED))
  1827.     {
  1828.         ced.id = WinQueryWindowUShort(hwndCnr, QWS_ID);
  1829.         brc = (BOOL)WinSendMsg(hwndCnr,
  1830.                                CM_OPENEDIT,
  1831.                                (MPARAM)&ced,
  1832.                                0);
  1833.     }
  1834.  
  1835.     return brc;
  1836. }
  1837.  
  1838. /*
  1839.  *@@ cnrhInitDrag:
  1840.  *      this sets up the necessary structures to begin dragging
  1841.  *      a record core from a container. This helper func should be
  1842.  *      called if your container should support being the source
  1843.  *      window of a direct manipulation.
  1844.  *
  1845.  *      This should get called in three situations:
  1846.  *
  1847.  *      --  the container sends CN_INITDRAG ("real" d'n'd desired by user);
  1848.  *
  1849.  *      --  the container sends us CN_PICKUP (Alt+MB2 pressed);
  1850.  *
  1851.  *      --  the user has selected "Pickup" from a record core's
  1852.  *          context menu (ID_XSMI_FILETYPES_PICKUP command).
  1853.  *          In that case, you can also call this function with
  1854.  *          usNotifyCode == CN_PICKUP.
  1855.  *
  1856.  *      In all three cases, preccDrag must be the record core
  1857.  *      which is to be dragged.
  1858.  *
  1859.  *      Depending on usNotifyCode, this function, after having allocated
  1860.  *      the necessary data, will do the following:
  1861.  *
  1862.  *      1)  If (usNotifyCode == CN_PICKUP), we will initiate a non-modal
  1863.  *          (lazy) drag (DrgLazyDrag). This function will then return
  1864.  *          after the records have been given "picked" emphasis.
  1865.  *
  1866.  *          Note: You must intercept CN_DROPNOTIFY in your window proc
  1867.  *          to clean up resources later. For this, call:
  1868.  *
  1869.  +              DrgDeleteDraginfoStrHandles(pdrgInfo);
  1870.  +              DrgFreeDraginfo(pdrgInfo);
  1871.  *
  1872.  *      2)  However, if (usNotifyCode == CN_INITDRAG), we will start
  1873.  *          a regular modal drag here by calling DrgDrag. This function
  1874.  *          will _not_ return until the object has been dropped or d'n'd
  1875.  *          has been cancelled. PM establishes another message loop
  1876.  *          internally for this. In this case, this function managed
  1877.  *          cleanup automatically.
  1878.  *
  1879.  *      This function supports one single record core only. The following
  1880.  *      information will be set in the DRAGITEM structure:
  1881.  *
  1882.  *      --  ulItemID will be set to the preccDrag so that the target
  1883.  *          window can access the dragged record.
  1884.  *
  1885.  *      --  hstrSourceName == hstrTargetName gets the RECORDCORE.pszIcon.
  1886.  *
  1887.  *      The drag icon will be a default system file icon.
  1888.  *
  1889.  *      Preconditions:
  1890.  *
  1891.  *      --  pszIcon must be set in the RECORDCORE. This is used for
  1892.  *          the item source name. This must not be NULL.
  1893.  */
  1894.  
  1895. PDRAGINFO cnrhInitDrag(HWND hwndCnr,
  1896.                                  // in: source container window
  1897.                        PRECORDCORE preccDrag,
  1898.                                  // in: record to be dragged (only one supported)
  1899.                        USHORT usNotifyCode,
  1900.                                  // in: CN_INITDRAG or CN_PICKUP
  1901.                        PSZ   pszRMF,
  1902.                                  // in: rendering mechanism and format,
  1903.                                  // e.g. "(DRM_MYFILETYPE)x(DRF_UNKNOWN)"
  1904.                        USHORT usSupportedOps)
  1905.                                  // stored in DRAGITEM.fsSupportedOps,
  1906.                                  // one or several of DO_COPYABLE, DO_MOVEABLE, DO_LINKABLE
  1907. {
  1908.     DRAGIMAGE    drgImage;
  1909.     PDRAGINFO    pdrgInfo = NULL;
  1910.  
  1911.     memset(&drgImage, 0, sizeof(drgImage));
  1912.  
  1913.     if (pdrgInfo = DrgAllocDraginfo(1)) // one item only
  1914.     {
  1915.         DRAGITEM drgItem;
  1916.         memset(&drgItem, 0, sizeof(drgItem));
  1917.  
  1918.         drgItem.hwndItem  = hwndCnr;
  1919.         drgItem.ulItemID  = (ULONG)preccDrag;
  1920.                     // we use this to store the container rec
  1921.         drgItem.hstrType  = DrgAddStrHandle(DRT_UNKNOWN);
  1922.                     // application defined
  1923.         drgItem.hstrRMF   = DrgAddStrHandle(pszRMF);
  1924.         drgItem.hstrContainerName = 0;
  1925.         drgItem.hstrSourceName = DrgAddStrHandle(preccDrag->pszIcon);
  1926.         drgItem.hstrTargetName = drgItem.hstrSourceName;
  1927.         drgItem.fsSupportedOps = usSupportedOps;
  1928.  
  1929.         // set the DRAGITEM struct into the memory
  1930.         // allocated by DrgAllocDraginfo()
  1931.         DrgSetDragitem(pdrgInfo,
  1932.                        &drgItem,
  1933.                        sizeof(DRAGITEM),
  1934.                        0);     // item index
  1935.  
  1936.         // fill in the DRAGIMAGE structure
  1937.         drgImage.cb       = sizeof(DRAGIMAGE);
  1938.         drgImage.hImage   = WinQuerySysPointer(HWND_DESKTOP,
  1939.                                                SPTR_FILE,
  1940.                                                FALSE);
  1941.                     // pointer used for dragging; we
  1942.                     // use a dull default file icon
  1943.         drgImage.fl       = DRG_ICON;             // hImage is an HPOINTER
  1944.         drgImage.cxOffset = 0; // 5 * iOffset;          // Image offset from mouse pointer
  1945.         drgImage.cyOffset = 0; // 5 * iOffset;          // Image offset from mouse pointer
  1946.  
  1947.         // set source emphasis for the container record
  1948.         WinSendMsg(hwndCnr,
  1949.                    CM_SETRECORDEMPHASIS,
  1950.                    (MPARAM)preccDrag,
  1951.                    MPFROM2SHORT(TRUE,
  1952.                        (usNotifyCode == CN_INITDRAG)
  1953.                            // for dragging: source emphasis
  1954.                            ? CRA_SOURCE
  1955.                            // for lazy drag: picked emphasis
  1956.                            : CRA_PICKED));
  1957.  
  1958.         if (usNotifyCode == CN_INITDRAG)
  1959.         {
  1960.             // "real" dragging:
  1961.             // call modal function for drag'n'drop.
  1962.             // This does not return until either
  1963.             // d'n'd has been cancelled or the
  1964.             // record core has been dropped.
  1965.             /* HWND hwndTarget = */ DrgDrag(hwndCnr,
  1966.                                       pdrgInfo,
  1967.                                       &drgImage,
  1968.                                       1,      // drag image count
  1969.                                       VK_ENDDRAG, // system drag button
  1970.                                       NULL);  // reserved
  1971.  
  1972.             // since it's the source which is responsible
  1973.             // for cleaning up the resources, this is
  1974.             // what we do now (we had a modal d'n'd)
  1975.             DrgDeleteDraginfoStrHandles(pdrgInfo);
  1976.             DrgFreeDraginfo(pdrgInfo);
  1977.  
  1978.             // remove source emphasis for the container record
  1979.             WinSendMsg(hwndCnr,
  1980.                        CM_SETRECORDEMPHASIS,
  1981.                        (MPARAM)preccDrag,
  1982.                        MPFROM2SHORT(FALSE,
  1983.                                CRA_SOURCE));
  1984.         } // end if (usNotifyCode == CN_INITDRAG)
  1985.         else
  1986.         {
  1987.             // "lazy drag" (CN_PICKUP):
  1988.             // initiate lazy drag. This function returns
  1989.             // immediately, since lazy drag is non-modal.
  1990.             // As a result, we cannot clean up the resources
  1991.             // here, but need to do this later (CN_DROPNOTIFY).
  1992.             DrgLazyDrag(hwndCnr,
  1993.                         pdrgInfo,
  1994.                         &drgImage,
  1995.                         1,      // drag image count
  1996.                         0);     // reserved
  1997.         }
  1998.     } // end if (pdrgInfo)
  1999.  
  2000.     return pdrgInfo;
  2001. }
  2002.  
  2003. /*
  2004.  *@@ cnrhOwnerDrawRecord:
  2005.  *      this helper func can be called upon receiving
  2006.  *      WM_DRAWITEM for owner-drawing container record
  2007.  *      cores.
  2008.  *
  2009.  *      What this draws depends on flFlags:
  2010.  *
  2011.  *      --  If CODFL_DISABLEDTEXT is set, this function supports
  2012.  *          drawing record cores in gray color if the record has
  2013.  *          the CRA_DISABLED style (which, AFAIK, PM ignores per
  2014.  *          default).
  2015.  *
  2016.  *      --  If CODFL_MINIICON is set, this function paints the
  2017.  *          record's mini-icon properly. So far I have failed
  2018.  *          to find out how to get a container to paint the
  2019.  *          correct mini-icon if the CV_MINI style is set...
  2020.  *          the container normally _does_ paint a small icon,
  2021.  *          but it won't use the "real" mini icon data if that
  2022.  *          is present.
  2023.  *
  2024.  *          For painting the mini-icon, WinDrawPointer is used
  2025.  *          with DP_MINI set properly and the RECORDCORE's
  2026.  *          hptrMiniIcon field.
  2027.  *
  2028.  *      This returns either TRUE or FALSE as an MPARAM, depending
  2029.  *      on whether we have drawn the item. This return value should
  2030.  *      also be the return value of your window procedure.
  2031.  *
  2032.  *@@changed V0.9.16 (2001-09-29) [umoeller]: added flFlags, icon draw support
  2033.  */
  2034.  
  2035. MRESULT cnrhOwnerDrawRecord(MPARAM mp2,     // in: mp2 of WM_DRAWITEM (POWNERITEM)
  2036.                             ULONG flFlags)  // in: CODFL_* flags
  2037. {
  2038.     MRESULT mrc = (MPARAM)FALSE; // tell cnr to draw the item
  2039.  
  2040.     // get generic DRAWITEM structure
  2041.     POWNERITEM poi;
  2042.  
  2043.     if (poi = (POWNERITEM)mp2)
  2044.     {
  2045.         // get container-specific draw-item struct
  2046.         PCNRDRAWITEMINFO pcdii = (PCNRDRAWITEMINFO)poi->hItem;
  2047.  
  2048.         // check if we're to draw the text
  2049.         // (and not the icon)
  2050.         if (    (poi->idItem == CMA_TEXT)
  2051.              && (flFlags & CODFL_DISABLEDTEXT)
  2052.            )
  2053.         {
  2054.             if (((pcdii->pRecord->flRecordAttr) & CRA_DISABLED) == 0)
  2055.             {
  2056.                 /*
  2057.                 // not disabled == valid WPS class
  2058.                 if ((pcdii->pRecord->flRecordAttr) & CRA_SELECTED)
  2059.                 {
  2060.                     // not disabled, but selected:
  2061.                     lBackground = winhQueryPresColor(hwndDlg, PP_HILITEBACKGROUNDCOLOR, SYSCLR_HILITEBACKGROUND);
  2062.                     lForeground = winhQueryPresColor(hwndDlg, PP_HILITEFOREGROUNDCOLOR, SYSCLR_HILITEFOREGROUND);
  2063.                 }
  2064.                 else
  2065.                 {
  2066.                     // not disabled, not selected:
  2067.                     lBackground = winhQueryPresColor(hwndDlg, PP_BACKGROUNDCOLOR, SYSCLR_BACKGROUND);
  2068.                     lForeground = winhQueryPresColor(hwndDlg, PP_FOREGROUNDCOLOR, SYSCLR_WINDOWTEXT);
  2069.                 } */
  2070.                 mrc = FALSE;
  2071.                     // let cnr draw the thing
  2072.             }
  2073.             else
  2074.             {
  2075.                 // CRA_DISABLED:
  2076.  
  2077.                 ULONG flCmd = DT_LEFT | DT_TOP | DT_ERASERECT;
  2078.                 RECTL rcl2;
  2079.  
  2080.                 // set draw colors
  2081.                 LONG  lBackground,
  2082.                       lForeground;
  2083.  
  2084.                 // switch to RGB
  2085.                 GpiCreateLogColorTable(poi->hps, 0, LCOLF_RGB, 0, 0, NULL);
  2086.  
  2087.                 if ((pcdii->pRecord->flRecordAttr) & CRA_SELECTED)
  2088.                 {
  2089.                     // disabled and selected:
  2090.                     lBackground = WinQuerySysColor(HWND_DESKTOP,
  2091.                                                    SYSCLR_SHADOWTEXT, 0);
  2092.                     lForeground = winhQueryPresColor(poi->hwnd,
  2093.                                                      PP_BACKGROUNDCOLOR,
  2094.                                                      FALSE, // no inherit
  2095.                                                      SYSCLR_WINDOW);
  2096.                 }
  2097.                 else
  2098.                 {
  2099.                     // disabled and not selected:
  2100.                     lBackground = winhQueryPresColor(poi->hwnd,
  2101.                                                      PP_BACKGROUNDCOLOR,
  2102.                                                      FALSE,
  2103.                                                      SYSCLR_WINDOW);
  2104.                     lForeground = WinQuerySysColor(HWND_DESKTOP,
  2105.                                                    SYSCLR_SHADOWTEXT, 0);
  2106.                 }
  2107.  
  2108.                 memcpy(&rcl2, &(poi->rclItem), sizeof(rcl2));
  2109.                 /* WinDrawText(poi->hps,
  2110.                             strlen(pcdii->pRecord->pszText),
  2111.                             pcdii->pRecord->pszText,
  2112.                             &rcl2,
  2113.                             lForeground,  // foreground
  2114.                             lBackground,
  2115.                             flCmd); */
  2116.  
  2117.                 GpiSetBackColor(poi->hps, lBackground);
  2118.                 GpiSetColor(poi->hps, lForeground);
  2119.  
  2120.                 winhDrawFormattedText(poi->hps,
  2121.                                       &rcl2,
  2122.                                       pcdii->pRecord->pszText,
  2123.                                       flCmd);
  2124.  
  2125.                 mrc = (MPARAM)TRUE;     // tell cnr that we've drawn the item
  2126.             }
  2127.         }
  2128.         else if (    (poi->idItem == CMA_ICON)
  2129.                   && (flFlags & CODFL_MINIICON)
  2130.                 )
  2131.         {
  2132.             WinDrawPointer(poi->hps,
  2133.                            // center the icon in the rectangle
  2134.                            (   poi->rclItem.xLeft
  2135.                              + (poi->rclItem.xRight - poi->rclItem.xLeft
  2136.                                 - WinQuerySysValue(HWND_DESKTOP, SV_CXICON) / 2
  2137.                                ) / 2
  2138.                            ),
  2139.                            (   poi->rclItem.yBottom
  2140.                              + (poi->rclItem.yTop - poi->rclItem.yBottom
  2141.                                 - WinQuerySysValue(HWND_DESKTOP, SV_CYICON) / 2
  2142.                                ) / 2
  2143.                            ),
  2144.                            pcdii->pRecord->hptrMiniIcon,
  2145.                            DP_MINI);
  2146.  
  2147.             mrc = (MPARAM)TRUE;         // tell cnr that we've drawn the item
  2148.         }
  2149.     }
  2150.  
  2151.     return mrc;
  2152. }
  2153.  
  2154. /*
  2155.  *@@ cnrhDateTimeDos2Win:
  2156.  *      this converts the information in a Control Program
  2157.  *      DATETIME structure (as returned by DosGetDateTime)
  2158.  *      to the CDATE and CTIME structures used by containers.
  2159.  *
  2160.  *      If any of the target pointers is NULL, that data is
  2161.  *      not converted.
  2162.  */
  2163.  
  2164. BOOL cnrhDateTimeDos2Win(DATETIME* pdt,     // in: Dos format date and time
  2165.                          CDATE* pcdate,     // out: container format date
  2166.                          CTIME* pctime)     // out: container format time
  2167. {
  2168.     if (pdt)
  2169.     {
  2170.         if (pcdate)
  2171.         {
  2172.             pcdate->day = pdt->day;
  2173.             pcdate->month = pdt->month;
  2174.             pcdate->year = pdt->year;
  2175.         }
  2176.         if (pctime)
  2177.         {
  2178.             pctime->seconds = pdt->seconds;
  2179.             pctime->minutes = pdt->minutes;
  2180.             pctime->hours = pdt->hours;
  2181.         }
  2182.         return TRUE;
  2183.     }
  2184.  
  2185.     return FALSE;
  2186. }
  2187.  
  2188. /*
  2189.  *@@ cnrhDateDos2Win:
  2190.  *      converts an FDATE structure (Control program)
  2191.  *      to a container CDATE structure.
  2192.  */
  2193.  
  2194. BOOL cnrhDateDos2Win(FDATE* pfd,    // in: DOS date
  2195.                      CDATE* pcd)    // out: container date
  2196. {
  2197.     if ((pfd) && (pcd))
  2198.     {
  2199.         pcd->day = pfd->day;
  2200.         pcd->month = pfd->month;
  2201.         pcd->year = pfd->year + 1980;
  2202.         return TRUE;
  2203.     }
  2204.  
  2205.     return FALSE;
  2206. }
  2207.  
  2208. /*
  2209.  *@@ cnrhTimeDos2Win:
  2210.  *      converts an FTIME structure (Control program)
  2211.  *      to a container CTIME structure.
  2212.  */
  2213.  
  2214. BOOL cnrhTimeDos2Win(FTIME* pft,    // in: DOS time
  2215.                      CTIME* pct)    // out: container time
  2216. {
  2217.     if ((pft) && (pct))
  2218.     {
  2219.         pct->seconds = pft->twosecs * 2;
  2220.         pct->minutes = pft->minutes;
  2221.         pct->hours = pft->hours;
  2222.         pct->ucReserved = 0;
  2223.         return TRUE;
  2224.     }
  2225.  
  2226.     return FALSE;
  2227. }
  2228.