
/*
 *@@sourcefile xwpadmin.c:
 *      This file contains SOM code for the following XWorkplace classes:
 *
 *      --  XWPAdmin ("XWorkplace Administrator" object, WPAbstract subclass)
 *
 *      This class implements the "XWorkplace Administrator"
 *      setting object, in which administrators may configure XWPShell.
 *
 *@@added V0.9.9 (2001-02-08) [umoeller]
 *@@somclass XWPAdmin adm_
 *@@somclass M_XWPAdmin admM_
 */

/*
 *      Copyright (C) 2001-2003 Ulrich M”ller.
 *
 *      This file is part of the XWorkplace source package.
 *      XWorkplace is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published
 *      by the Free Software Foundation, in version 2 as it comes in the
 *      "COPYING" file of the XWorkplace main distribution.
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 */

/*
 *  This file was generated by the SOM Compiler and Emitter Framework.
 *  Generated using:
 *      SOM Emitter emitctm: 2.41
 */

#ifndef SOM_Module_xwpadmin_Source
#define SOM_Module_xwpadmin_Source
#endif
#define XWPAdmin_Class_Source
#define M_XWPAdmin_Class_Source

#pragma strings(readonly)

/*
 *  Suggested #include order:
 *  1)  os2.h
 *  2)  C library headers
 *  3)  setup.h (code generation and debugging options)
 *  4)  headers in helpers\
 *  5)  at least one SOM implementation header (*.ih)
 *  6)  dlgids.h, headers in shared\ (as needed)
 *  7)  headers in implementation dirs (e.g. filesys\, as needed)
 *  8)  #pragma hdrstop and then more SOM headers which crash with precompiled headers
 */

#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_DOSQUEUES
#define INCL_DOSMODULEMGR
#define INCL_DOSERRORS

#define INCL_WINWINDOWMGR
#define INCL_WINSTATICS
#define INCL_WINDIALOGS
#define INCL_WINBUTTONS
#define INCL_WINENTRYFIELDS
#define INCL_WINSTDCNR
#include <os2.h>

// C library headers
#include <stdio.h>

// generic headers
#include "setup.h"                      // code generation and debugging options

// headers in /helpers
#include "helpers\cnrh.h"               // container helper routines
#include "helpers\dialog.h"             // dialog helpers
#include "helpers\procstat.h"           // DosQProcStat handling
#include "helpers\standards.h"          // some standard macros
#include "helpers\stringh.h"            // string helper routines
#include "helpers\textview.h"           // PM XTextView control
#include "helpers\tree.h"               // red-black binary trees
#include "helpers\winh.h"               // PM helper routines
#include "helpers\xstring.h"            // extended string helpers

// SOM headers which don't crash with prec. header files
#include "xwpadmin.ih"

// XWorkplace implementation headers
#include "dlgids.h"                     // all the IDs that are shared with NLS
#include "shared\common.h"              // the majestic XWorkplace include file
#include "shared\helppanels.h"          // all XWorkplace help panel IDs
#include "shared\kernel.h"              // XWorkplace Kernel
#include "shared\notebook.h"            // generic XWorkplace notebook handling
#include "shared\xsetup.h"              // XWPSetup implementation

#include "helpers\xwpsecty.h"           // XWorkplace Security base

// other SOM headers
#pragma hdrstop

/* ******************************************************************
 *
 *   Helpers
 *
 ********************************************************************/

/*
 *@@ Error:
 *
 *@@added V0.9.19 (2002-04-02) [umoeller]
 */

STATIC VOID Error(WPObject *somSelf,
                  HWND hwndOwner,
                  APIRET arc)
{
    XSTRING str;
    xstrInit(&str, 0);
    cmnDescribeError(&str, arc, NULL, TRUE);
    cmnMessageBox(hwndOwner,
                  _wpQueryTitle(somSelf),
                  str.psz,
                  NULLHANDLE, // no help
                  MB_CANCEL);
}

/* ******************************************************************
 *
 *   Local User page
 *
 ********************************************************************/

#define RIGHT_COLUMN    100

static const CONTROLDEF
    LocalUserGroup = LOADDEF_GROUP(ID_AMDI_USER_LOCAL_GROUP, SZL_AUTOSIZE),
    LocalUserNameTxt = LOADDEF_TEXT(ID_AMDI_USER_USERNAME_TXT),
    LocalUserNameData = CONTROLDEF_TEXT(
                            "A",
                            ID_AMDI_USER_USERNAME_DATA,
                            RIGHT_COLUMN,
                            -1),
    LocalFullNameTxt = LOADDEF_TEXT(ID_AMDI_USER_FULLNAME_TXT),
    LocalFullNameData = CONTROLDEF_TEXT(
                            "A",
                            ID_AMDI_USER_FULLNAME_DATA,
                            RIGHT_COLUMN,
                            -1),
    LocalUserIDTxt = LOADDEF_TEXT(ID_AMDI_USER_USERID_TXT),
    LocalUserIDData = CONTROLDEF_TEXT(
                            "A",
                            ID_AMDI_USER_USERID_DATA,
                            RIGHT_COLUMN,
                            -1),
    LocalGroupsTxt = LOADDEF_TEXT(ID_AMDI_USER_GROUPS_TXT),
    LocalGroupsData = CONTROLDEF_XTEXTVIEW(
                            "A\nA\nA",
                            ID_AMDI_USER_GROUPS_DATA,
                            RIGHT_COLUMN,
                            NULL),
    LocalSecTxt = LOADDEF_TEXT(ID_AMDI_USER_LOCALSEC_TXT),
    LocalSecData = CONTROLDEF_TEXT_WORDBREAK(
                            "A",
                            ID_AMDI_USER_LOCALSEC_DATA,
                            RIGHT_COLUMN),
    LocalContextsTxt = LOADDEF_TEXT(ID_AMDI_USER_CONTEXTS_TXT),
    LocalContextsData = CONTROLDEF_TEXT_WORDBREAK(
                            "A",
                            ID_AMDI_USER_CONTEXTS_DATA,
                            RIGHT_COLUMN),
    LocalBytesTxt = LOADDEF_TEXT(ID_AMDI_USER_ALLOCBYTES_TXT),
    LocalBytesData = CONTROLDEF_TEXT_WORDBREAK(
                            "A",
                            ID_AMDI_USER_ALLOCBYTES_DATA,
                            RIGHT_COLUMN),
    LocalBufsTxt = LOADDEF_TEXT(ID_AMDI_USER_BUFALLOCS_TXT),
    LocalBufsData = CONTROLDEF_TEXT_WORDBREAK(
                            "A",
                            ID_AMDI_USER_BUFALLOCS_DATA,
                            RIGHT_COLUMN),
    LocalEventsTxt = LOADDEF_TEXT(ID_AMDI_USER_EVENTS_TXT),
    LocalEventsData = CONTROLDEF_TEXT_WORDBREAK(
                            "A",
                            ID_AMDI_USER_EVENTS_DATA,
                            RIGHT_COLUMN);

static const DLGHITEM dlgLocalUser[] =
    {
        START_TABLE,            // root table, required
            START_ROW(0),       // row 1 in the root table, required
                // create group on top
                START_GROUP_TABLE_ALIGN(&LocalUserGroup),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalUserNameTxt),
                        CONTROL_DEF(&LocalUserNameData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalUserIDTxt),
                        CONTROL_DEF(&LocalUserIDData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalFullNameTxt),
                        CONTROL_DEF(&LocalFullNameData),
                    START_ROW(ROW_VALIGN_TOP),
                        CONTROL_DEF(&LocalGroupsTxt),
                        CONTROL_DEF(&LocalGroupsData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalSecTxt),
                        CONTROL_DEF(&LocalSecData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalContextsTxt),
                        CONTROL_DEF(&LocalContextsData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalBytesTxt),
                        CONTROL_DEF(&LocalBytesData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalBufsTxt),
                        CONTROL_DEF(&LocalBufsData),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&LocalEventsTxt),
                        CONTROL_DEF(&LocalEventsData),
                END_TABLE,
            START_ROW(0),       // notebook buttons (will be moved)
                CONTROL_DEF(&G_HelpButton),         // notebook.c
        END_TABLE
    };

/*
 *@@ LocalUserInitPage:
 *
 */

VOID LocalUserInitPage(PNOTEBOOKPAGE pnbp,   // notebook info struct
                       ULONG flFlags)        // CBI_* flags (notebook.h)
{
    if (flFlags & CBI_INIT)
    {
        // insert the controls using the dialog formatter
        ntbFormatPage(pnbp->hwndDlgPage,
                      dlgLocalUser,
                      ARRAYITEMCOUNT(dlgLocalUser));
    }

    if (flFlags & CBI_SET)
    {
        APIRET      arc = NO_ERROR;
        PXWPUSERDBENTRY pLocalUser;

        if (!(arc = xsecQueryLocalUser(&pLocalUser)))
        {
            ULONG   cGroups;
            PXWPGROUPDBENTRY paGroups;

            WinSetDlgItemShort(pnbp->hwndDlgPage,
                               ID_AMDI_USER_USERID_DATA,
                               pLocalUser->User.uid,
                               FALSE);          // unsigned
            WinSetDlgItemText(pnbp->hwndDlgPage,
                              ID_AMDI_USER_USERNAME_DATA,
                              pLocalUser->User.szUserName);
            WinSetDlgItemText(pnbp->hwndDlgPage,
                              ID_AMDI_USER_FULLNAME_DATA,
                              pLocalUser->User.szFullName);

            if (!(arc = xsecQueryGroups(&cGroups,
                                        &paGroups)))
            {
                ULONG   ul;
                XSTRING strGroups;
                xstrInit(&strGroups, 0);

                for (ul = 0;
                     ul < pLocalUser->Membership.cGroups;
                     ++ul)
                {
                    ULONG       ul2;
                    XWPSECID    gidThis = pLocalUser->Membership.aGIDs[ul];

                    if (strGroups.ulLength)
                        xstrcatc(&strGroups, '\n');

                    for (ul2 = 0;
                         ul2 < cGroups;
                         ++ul)
                    {
                        if (gidThis == paGroups[ul2].gid)
                        {
                            xstrcat(&strGroups,
                                    paGroups[ul2].szGroupName,
                                    0);
                            xstrcatc(&strGroups, ' ');
                            break;
                        }
                    }

                    xstrCatf(&strGroups,
                             "[%d]",
                             gidThis);
                }

                WinSetDlgItemText(pnbp->hwndDlgPage,
                                  ID_AMDI_USER_GROUPS_DATA,
                                  strGroups.psz);

                xstrClear(&strGroups);

                free(paGroups);
            }

            free(pLocalUser);
        }
        else
        {
            // error:
            CHAR szError[100];
            sprintf(szError, "Error %d", arc);
            WinSetDlgItemText(pnbp->hwndDlgPage,
                              ID_AMDI_USER_USERNAME_DATA,
                              szError);
        }
    }
}

/*
 *@@ LocalUserTimerPage:
 *
 *@@added V1.0.1 (2003-01-17) [umoeller]
 */

VOID LocalUserTimerPage(PNOTEBOOKPAGE pnbp,
                        ULONG ulTimer)
{
    XWPSECSTATUS Status;
    APIRET arc;
    if (!(arc = xsecQueryStatus(&Status)))
    {
        CHAR    szTemp[100],
                cThousands;

        WinSetDlgItemText(pnbp->hwndDlgPage,
                          ID_AMDI_USER_LOCALSEC_DATA,
                          (Status.fLocalSecurity)
                            ? "Active"          // @@todo localize
                            : "Inactive");

        nlsThousandsULong(szTemp,
                          Status.cContexts,
                          (cThousands = cmnQueryThousandsSeparator()));
        WinSetDlgItemText(pnbp->hwndDlgPage,
                          ID_AMDI_USER_CONTEXTS_DATA,
                          szTemp);

        nlsThousandsULong(szTemp,
                          Status.cbAllocated,
                          cThousands);
        WinSetDlgItemText(pnbp->hwndDlgPage,
                          ID_AMDI_USER_ALLOCBYTES_DATA,
                          szTemp);

        sprintf(szTemp,
                "%d (%d max)",
                Status.cLogBufs,
                Status.cMaxLogBufs);
        WinSetDlgItemText(pnbp->hwndDlgPage,
                          ID_AMDI_USER_BUFALLOCS_DATA,
                          szTemp);

        nlsThousandsULong(szTemp,
                          Status.cLogged,
                          cThousands);
        WinSetDlgItemText(pnbp->hwndDlgPage,
                          ID_AMDI_USER_EVENTS_DATA,
                          szTemp);

    }

};

/* ******************************************************************
 *
 *   All Users page
 *
 ********************************************************************/

/*
 *@@ USERRECORD:
 *
 */

typedef struct _USERRECORD
{
    RECORDCORE      recc;

    PSZ             pszFullName;

    XWPUSERINFO     Entry;

} USERRECORD, *PUSERRECORD;

static const CONTROLDEF
    AllUsersGroup = LOADDEF_GROUP(ID_AMSI_ALL_USERS, SZL_AUTOSIZE),
    AllUsersCnr = CONTROLDEF_CONTAINER(
                            ID_XFDI_CNR_CNR,
                            200,        // for now, will be resized
                            100);       // for now, will be resized

static const DLGHITEM G_dlgAllUsers[] =
    {
        START_TABLE,            // root table, required
            START_ROW(0),
                START_GROUP_TABLE(&AllUsersGroup),
                    START_ROW(0),
                        CONTROL_DEF(&AllUsersCnr),
                    START_ROW(0),
                        CONTROL_DEF(&G_AddButton),
                        CONTROL_DEF(&G_EditButton),
                        CONTROL_DEF(&G_RemoveButton),
                END_TABLE,
            START_ROW(0),       // notebook buttons (will be moved)
                CONTROL_DEF(&G_HelpButton),         // common.c
        END_TABLE
    };

MPARAM G_ampAllUsers[] =
    {
        MPFROM2SHORT(ID_XFDI_CNR_CNR, XAC_SIZEX | XAC_SIZEY),
        MPFROM2SHORT(ID_AMSI_ALL_USERS, XAC_SIZEX | XAC_SIZEY),
    };

/*
 *@@ AllUsersInitPage:
 *
 */

STATIC VOID AllUsersInitPage(PNOTEBOOKPAGE pnbp,   // notebook info struct
                             ULONG flFlags)        // CBI_* flags (notebook.h)
{
    if (flFlags & CBI_INIT)
    {
        HWND            hwndCnr,
                        hwndRemove;
        XFIELDINFO      xfi[5];
        PFIELDINFO      pfi = NULL;
        int             i = 0;

        // insert the controls using the dialog formatter
        // V1.0.1 (2003-01-05) [umoeller]
        ntbFormatPage(pnbp->hwndDlgPage,
                      G_dlgAllUsers,
                      ARRAYITEMCOUNT(G_dlgAllUsers));

        winhAppendDlgItemEllipseText(pnbp->hwndDlgPage, DID_REMOVE);

        hwndCnr = WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR);

        // set up cnr details view
        xfi[i].ulFieldOffset = FIELDOFFSET(USERRECORD, Entry.uid);
        xfi[i].pszColumnTitle = "User ID";      // @@todo localize
        xfi[i].ulDataType = CFA_ULONG;
        xfi[i++].ulOrientation = CFA_RIGHT;

        xfi[i].ulFieldOffset = FIELDOFFSET(RECORDCORE, pszIcon);
        xfi[i].pszColumnTitle = "User name";    // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        xfi[i].ulFieldOffset = FIELDOFFSET(USERRECORD, pszFullName);
        xfi[i].pszColumnTitle = "Full name";    // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        /*
        xfi[i].ulFieldOffset = FIELDOFFSET(USERRECORD, Entry.gid);
        xfi[i].pszColumnTitle = "Group IDs";     // @@todo localize
        xfi[i].ulDataType = CFA_ULONG;
        xfi[i++].ulOrientation = CFA_RIGHT;

        xfi[i].ulFieldOffset = FIELDOFFSET(USERRECORD, pszGroupName);
        xfi[i].pszColumnTitle = "Group names";  // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;
        */

        pfi = cnrhSetFieldInfos(hwndCnr,
                                xfi,
                                i,             // array item count
                                TRUE,          // draw lines
                                2);            // split bar after third column

        BEGIN_CNRINFO()
        {
            cnrhSetView(CV_DETAIL | CA_DETAILSVIEWTITLES);
            cnrhSetSplitBarAfter(pfi);
            cnrhSetSplitBarPos(250);
        } END_CNRINFO(hwndCnr);
    }

    if (flFlags & CBI_SET)
    {
        APIRET  arc;
        ULONG   cUsers;
        PXWPUSERDBENTRY paUsers;
        if (!(arc = xsecQueryAllUsers(&cUsers,
                                      &paUsers)))
        {
            HWND hwndCnr = WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR);

            PUSERRECORD preccFirst;
            if (preccFirst = (PUSERRECORD)cnrhAllocRecords(hwndCnr,
                                                           sizeof(USERRECORD),
                                                           cUsers))
            {
                PBYTE pbUserThis = (PBYTE)paUsers;
                PUSERRECORD preccThis = preccFirst;
                ULONG ul;
                for (ul = 0;
                     ul < cUsers;
                     ++ul)
                {
                    PXWPUSERDBENTRY pUserThis = (PXWPUSERDBENTRY)pbUserThis;
                    memcpy(&preccThis->Entry,
                           &pUserThis->User,
                           sizeof(preccThis->Entry));

                    preccThis->recc.pszIcon = preccThis->Entry.szUserName;
                    preccThis->pszFullName = preccThis->Entry.szFullName;
                    // preccThis->pszGroupName = preccThis->Entry.szGroupName;

                    pbUserThis += pUserThis->cbStruct;
                    preccThis = (PUSERRECORD)preccThis->recc.preccNextRecord;
                }

                cnrhInsertRecords(hwndCnr,
                                  NULL,
                                  (PRECORDCORE)preccFirst,
                                  TRUE, // invalidate
                                  NULL,
                                  CRA_RECORDREADONLY,
                                  cUsers);
            }

            free(paUsers);
        }
        else
            Error(pnbp->inbp.somSelf, pnbp->hwndDlgPage, arc);
    }
}

/*
 *@@ fnwpAddUser:
 *
 *@@added V1.0.1 (2003-01-05) [umoeller]
 */

MRESULT EXPENTRY fnwpAddUser(HWND hwndDlg, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc = 0;

    switch (msg)
    {
        case WM_INITDLG:
            WinSetWindowPtr(hwndDlg, QWL_USER, mp2);
                    // PUSERRECORD to edit or NULL for add mode
            winhSetDlgItemFocus(hwndDlg,
                                ID_AMDI_USER_USERNAME_DATA);
            mrc = (MRESULT)TRUE;
        break;

        case WM_CONTROL:
            switch (SHORT1FROMMP(mp1))
            {
                case ID_AMDI_USER_USERNAME_DATA:
                case ID_AMDI_USER_FULLNAME_DATA:
                case ID_AMDI_USER_PASS_DATA:
                case ID_AMDI_USER_CONFIRMPASS_DATA:
                    if (SHORT2FROMMP(mp1) == EN_CHANGE)
                        WinPostMsg(hwndDlg, XM_ENABLEITEMS, 0, 0);
                break;
            }
        break;

        case XM_ENABLEITEMS:
        {
            BOOL    fEnable = FALSE;
            PSZ     pszUser;
            if (pszUser = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_USERNAME_DATA))
            {
                BOOL    fOK = TRUE;
                PCSZ    p = pszUser;
                CHAR    c;
                while (c = *p++)
                {
                    if (    (c < '!')
                         || (c > 'z')
                       )
                    {
                        fOK = FALSE;
                        break;
                    }
                }

                if (    (fOK)
                     && WinQueryDlgItemTextLength(hwndDlg, ID_AMDI_USER_FULLNAME_DATA)
                   )
                {
                    if (WinQueryWindowPtr(hwndDlg, QWL_USER))
                        // edit mode:
                        fEnable = TRUE;
                    else
                    {
                        // add mode: then we need to check pwds
                        PSZ pszPassword = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_PASS_DATA),
                            pszConfirmPassword = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_CONFIRMPASS_DATA);
                        if (!strhcmp(pszPassword, pszConfirmPassword))
                            fEnable = TRUE;

                        FREE(pszPassword);
                        FREE(pszConfirmPassword);
                    }
                }

                free(pszUser);
            }

            WinEnableControl(hwndDlg, DID_OK, fEnable);
        }
        break;

        default:
            mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
    }

    return mrc;
}

#define EF_WIDTH        80

static const CONTROLDEF
    UserNameTxt = LOADDEF_TEXT(ID_AMDI_USER_USERNAME_TXT),
    UserNameEF = CONTROLDEF_ENTRYFIELD(
                            NULL,
                            ID_AMDI_USER_USERNAME_DATA,
                            EF_WIDTH,
                            SZL_AUTOSIZE),
    UserFullTxt = LOADDEF_TEXT(ID_AMDI_USER_FULLNAME_TXT),
    UserFullEF = CONTROLDEF_ENTRYFIELD(
                            NULL,
                            ID_AMDI_USER_FULLNAME_DATA,
                            EF_WIDTH,
                            SZL_AUTOSIZE),
    UserPassTxt = LOADDEF_TEXT(ID_AMDI_USER_PASS_TXT),
    UserPassEF = CONTROLDEF_ENTRYFIELD_PASSWD(
                            NULL,
                            ID_AMDI_USER_PASS_DATA,
                            EF_WIDTH,
                            SZL_AUTOSIZE),
    UserConfirmPassTxt = LOADDEF_TEXT(ID_AMDI_USER_CONFIRMPASS_TXT),
    UserConfirmPassEF = CONTROLDEF_ENTRYFIELD_PASSWD(
                            NULL,
                            ID_AMDI_USER_CONFIRMPASS_DATA,
                            EF_WIDTH,
                            SZL_AUTOSIZE);

static const DLGHITEM dlgEditUserFront[] =
    {
        START_TABLE,
            START_ROW(ROW_VALIGN_CENTER),
                START_TABLE_ALIGN,
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&UserNameTxt),
                        CONTROL_DEF(&UserNameEF),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&UserFullTxt),
                        CONTROL_DEF(&UserFullEF),
    };

static const DLGHITEM dlgEditUserOnly[] =
    {
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&UserPassTxt),
                        CONTROL_DEF(&UserPassEF),
                    START_ROW(ROW_VALIGN_CENTER),
                        CONTROL_DEF(&UserConfirmPassTxt),
                        CONTROL_DEF(&UserConfirmPassEF),
    };

static const DLGHITEM dlgEditUserTail[] =
    {
            END_TABLE,
            START_ROW(ROW_VALIGN_CENTER),
                CONTROL_DEF(&G_OKButton),
                CONTROL_DEF(&G_CancelButton),
        END_TABLE
    };

/*
 *@@ AddUser:
 *      implementation for the "add" and "edit user" commands.
 *
 *@@added V1.0.1 (2003-01-05) [umoeller]
 */

STATIC APIRET AddOrEditUser(HWND hwndOwner,
                            PUSERRECORD preccEdit)  // in: user to edit or NULL for "add" mode
{
    APIRET arc;
    PDLGARRAY pArray = NULL;

    if (!(arc = dlghCreateArray(   ARRAYITEMCOUNT(dlgEditUserFront)
                                 + ARRAYITEMCOUNT(dlgEditUserOnly)
                                 + ARRAYITEMCOUNT(dlgEditUserTail),
                                &pArray)))
    {
        HWND hwndDlg;

        dlghAppendToArray(pArray,
                          dlgEditUserFront,
                          ARRAYITEMCOUNT(dlgEditUserFront));
        if (!preccEdit)
            dlghAppendToArray(pArray,
                              dlgEditUserOnly,
                              ARRAYITEMCOUNT(dlgEditUserOnly));
        dlghAppendToArray(pArray,
                          dlgEditUserTail,
                          ARRAYITEMCOUNT(dlgEditUserTail));

        if (!(arc = dlghCreateDlg(&hwndDlg,
                                  hwndOwner,
                                  FCF_FIXED_DLG,
                                  fnwpAddUser,
                                  (preccEdit)
                                        ? "Edit User"
                                        : "Add User",       // @@todo localize
                                  pArray->paDlgItems,
                                  pArray->cDlgItemsNow,
                                  preccEdit,
                                  cmnQueryDefaultFont())))
        {
            XWPSECID uid;

            if (preccEdit)
            {
                winhSetDlgItemText(hwndDlg,
                                   ID_AMDI_USER_USERNAME_DATA,
                                   preccEdit->Entry.szUserName);
                winhSetDlgItemText(hwndDlg,
                                   ID_AMDI_USER_FULLNAME_DATA,
                                   preccEdit->Entry.szFullName);
            }

            winhCenterWindow(hwndDlg);
            WinEnableControl(hwndDlg, DID_OK, FALSE);
            if (DID_OK == WinProcessDlg(hwndDlg))
            {
                PSZ pszUser,
                    pszFullUser;

                if (!(pszUser = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_USERNAME_DATA)))
                    arc = ERROR_INVALID_PARAMETER;
                else
                {
                    if (!(pszFullUser = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_FULLNAME_DATA)))
                        arc = ERROR_INVALID_PARAMETER;
                    else
                    {
                        if (preccEdit)
                            // edit mode:
                            arc = xsecSetUserData(preccEdit->Entry.uid,
                                                  pszUser,
                                                  pszFullUser);
                        else
                        {
                            // add mode:
                            PSZ pszPassword = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_PASS_DATA),
                                pszConfirmPassword = winhQueryDlgItemText(hwndDlg, ID_AMDI_USER_CONFIRMPASS_DATA);

                            if (strhcmp(pszPassword, pszConfirmPassword))
                                arc = ERROR_INVALID_PARAMETER;
                            else
                                arc = xsecCreateUser(pszUser,
                                                     pszFullUser,
                                                     pszPassword,
                                                     1,
                                                     &uid);

                            FREE(pszPassword);
                            FREE(pszConfirmPassword);
                        }

                        free(pszFullUser);
                    }

                    free(pszUser);
                }
            }

            WinDestroyWindow(hwndDlg);
        }

        dlghFreeArray(&pArray);
    }

    return arc;
}

/*
 *@@ AllUsersItemChanged:
 *
 *@@added V1.0.1 (2003-01-05) [umoeller]
 */

STATIC MRESULT AllUsersItemChanged(PNOTEBOOKPAGE pnbp,
                                   ULONG ulItemID, USHORT usNotifyCode,
                                   ULONG ulExtra)      // for checkboxes: contains new state
{
    MRESULT     mrc = 0;
    APIRET      arc = NO_ERROR;

    switch (ulItemID)
    {
        case DID_ADD:
        case DID_EDIT:
            arc = AddOrEditUser(pnbp->hwndDlgPage,
                                (ulItemID == DID_EDIT)
                                    ? (PUSERRECORD)pnbp->preccSource
                                    : NULL);      // add mode
        break;

        case DID_REMOVE:
            if (pnbp->preccSource)
            {
                CHAR szUID[30];
                XWPSECID uid = ((PUSERRECORD)pnbp->preccSource)->Entry.uid;
                PCSZ apcsz[2] =
                    {
                        szUID,
                        ((PUSERRECORD)pnbp->preccSource)->Entry.szUserName
                    };
                sprintf(szUID, "%d", uid);
                if (MBID_YES == cmnMessageBoxExt(pnbp->hwndDlgPage,
                                                 262,    // Remove User Account
                                                 apcsz,
                                                 2,
                                                 263,    // You have selected to remove the account for user ID %1 ("%2"). Are you sure you want to do this?
                                                 MB_YESNO))
                {
                    arc = xsecDeleteUser(uid);
                }
            }
        break;
    }

    if (arc)
        cmnErrorMsgBox(pnbp->hwndDlgPage,
                       arc,
                       261, // Operation failed. XWorkplace security reported the following error: %1
                       MB_CANCEL,
                       TRUE);

    return mrc;
}

/* ******************************************************************
 *
 *   All Groups page
 *
 ********************************************************************/

/*
 *@@ GROUPRECORD:
 *
 */

typedef struct _GROUPRECORD
{
    RECORDCORE      recc;

    XWPSECID        gid;
    PSZ             pszGroupName;
    CHAR            szGroupName[XWPSEC_NAMELEN];    // group name
    PSZ             pszMembers;

} GROUPRECORD, *PGROUPRECORD;

static const CONTROLDEF
    AllGroupsGroup = LOADDEF_GROUP(ID_AMSI_ALL_GROUPS, SZL_AUTOSIZE),
    AllGroupsCnr = CONTROLDEF_CONTAINER(
                            ID_XFDI_CNR_CNR,
                            200,        // for now, will be resized
                            100);       // for now, will be resized

static const DLGHITEM G_dlgAllGroups[] =
    {
        START_TABLE,            // root table, required
            START_ROW(0),
                START_GROUP_TABLE(&AllGroupsGroup),
                    START_ROW(0),
                        CONTROL_DEF(&AllGroupsCnr),
                    START_ROW(0),
                        CONTROL_DEF(&G_AddButton),
                        CONTROL_DEF(&G_EditButton),
                        CONTROL_DEF(&G_RemoveButton),
                END_TABLE,
            START_ROW(0),       // notebook buttons (will be moved)
                CONTROL_DEF(&G_HelpButton),         // common.c
        END_TABLE
    };

MPARAM G_ampAllGroups[] =
    {
        MPFROM2SHORT(ID_XFDI_CNR_CNR, XAC_SIZEX | XAC_SIZEY),
        MPFROM2SHORT(ID_AMSI_ALL_GROUPS, XAC_SIZEX | XAC_SIZEY),
    };

/*
 *@@ fncbCleanupMembers:
 *      cleanup proc for CBI_DESTROY in AllGroupsInitPage.
 *
 *@@added V1.0.1 (2003-01-05) [umoeller]
 */

ULONG XWPENTRY fncbCleanupMembers(HWND hwndCnr,
                                  PRECORDCORE precc,
                                  ULONG ulUser)
{
    if (((PGROUPRECORD)precc)->pszMembers)
        free(((PGROUPRECORD)precc)->pszMembers);
    return 0;
}

/*
 *@@ AllGroupsInitPage:
 *
 */

STATIC VOID AllGroupsInitPage(PNOTEBOOKPAGE pnbp,   // notebook info struct
                              ULONG flFlags)        // CBI_* flags (notebook.h)
{
    if (flFlags & CBI_INIT)
    {
        HWND            hwndCnr,
                        hwndRemove;
        XFIELDINFO      xfi[4];
        PFIELDINFO      pfi = NULL;
        int             i = 0;

        // insert the controls using the dialog formatter
        // V1.0.1 (2003-01-05) [umoeller]
        ntbFormatPage(pnbp->hwndDlgPage,
                      G_dlgAllGroups,
                      ARRAYITEMCOUNT(G_dlgAllGroups));

        winhAppendDlgItemEllipseText(pnbp->hwndDlgPage, DID_REMOVE);

        hwndCnr = WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR);

        // set up cnr details view
        xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, gid);
        xfi[i].pszColumnTitle = "Group ID";     // @@todo localize
        xfi[i].ulDataType = CFA_ULONG;
        xfi[i++].ulOrientation = CFA_RIGHT;

        xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, pszGroupName);
        xfi[i].pszColumnTitle = "Group name";   // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        xfi[i].ulFieldOffset = FIELDOFFSET(GROUPRECORD, pszMembers);
        xfi[i].pszColumnTitle = "Members";   // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        pfi = cnrhSetFieldInfos(hwndCnr,
                                xfi,
                                i,             // array item count
                                TRUE,          // draw lines
                                0);            // return first column

        BEGIN_CNRINFO()
        {
            cnrhSetView(CV_DETAIL | CA_DETAILSVIEWTITLES);
        } END_CNRINFO(hwndCnr);
    }

    if (flFlags & CBI_SET)
    {
        APIRET  arc;
        ULONG   cGroups;
        PXWPGROUPDBENTRY paGroups;

        if (!(arc = xsecQueryGroups(&cGroups,
                                    &paGroups)))
        {
            ULONG   cUsers;
            PXWPUSERDBENTRY paUsers;
            if (!(arc = xsecQueryAllUsers(&cUsers,
                                          &paUsers)))
            {
                HWND hwndCnr = WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR);

                PGROUPRECORD preccFirst;
                if (preccFirst = (PGROUPRECORD)cnrhAllocRecords(hwndCnr,
                                                                sizeof(GROUPRECORD),
                                                                cGroups))
                {
                    PXWPGROUPDBENTRY pGroupThis = paGroups;
                    PGROUPRECORD preccThis = preccFirst;
                    ULONG   ul,
                            ulUser;
                    XSTRING str;
                    xstrInit(&str, 0);
                    for (ul = 0;
                         ul < cGroups;
                         ++ul)
                    {
                        PBYTE pbUserThis;
                        preccThis->gid = pGroupThis->gid;

                        memcpy(preccThis->szGroupName,
                               pGroupThis->szGroupName,
                               sizeof(preccThis->szGroupName));
                        preccThis->pszGroupName = preccThis->szGroupName;

                        // now loop through all users and add those to the
                        // "members" string which are a member of this group
                        xstrcpy(&str, NULL, 0);
                        pbUserThis = (PBYTE)paUsers;
                        for (ulUser = 0;
                             ulUser < cUsers;
                             ++ulUser)
                        {
                            PXWPUSERDBENTRY pUserThis = (PXWPUSERDBENTRY)pbUserThis;
                            ULONG ulGroup;
                            for (ulGroup = 0;
                                 ulGroup < pUserThis->Membership.cGroups;
                                 ++ulGroup)
                            {
                                if (pUserThis->Membership.aGIDs[ulGroup] == pGroupThis->gid)
                                {
                                    if (str.ulLength)
                                        xstrcatc(&str, '\n');
                                    xstrcat(&str, pUserThis->User.szUserName, 0);
                                }
                            }

                            pbUserThis += pUserThis->cbStruct;
                        }

                        if (str.ulLength)
                            preccThis->pszMembers = strdup(str.psz);

                        ++pGroupThis;
                        preccThis = (PGROUPRECORD)preccThis->recc.preccNextRecord;
                    }

                    xstrClear(&str);

                    cnrhInsertRecords(hwndCnr,
                                      NULL,
                                      (PRECORDCORE)preccFirst,
                                      TRUE, // invalidate
                                      NULL,
                                      CRA_RECORDREADONLY,
                                      cGroups);
                }

                free(paUsers);
            }

            free(paGroups);
        }
        else
            Error(pnbp->inbp.somSelf, pnbp->hwndDlgPage, arc);
    }

    if (flFlags & CBI_DESTROY)
        cnrhForAllRecords(WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR),
                          (PRECORDCORE)-1,
                          fncbCleanupMembers,
                          0);

}

/*
 *@@ AllGroupsItemChanged:
 *
 *@@added V1.0.1 (2003-01-05) [umoeller]
 */

STATIC MRESULT AllGroupsItemChanged(PNOTEBOOKPAGE pnbp,
                                    ULONG ulItemID, USHORT usNotifyCode,
                                    ULONG ulExtra)      // for checkboxes: contains new state
{
    MRESULT mrc = 0;

    switch (ulItemID)
    {
        case DID_ADD:
            winhDebugBox(pnbp->hwndDlgPage,
                         "Debug",
                         "Add pressed");
        break;

        case DID_EDIT:
            winhDebugBox(pnbp->hwndDlgPage,
                         "Debug",
                         "Edit pressed");
        break;

        case DID_REMOVE:
            winhDebugBox(pnbp->hwndDlgPage,
                         "Debug",
                         "Remove pressed");
        break;
    }

    return mrc;
}

/* ******************************************************************
 *
 *   Processes page
 *
 ********************************************************************/

/*
 *@@ PROCESSRECORD:
 *
 */

typedef struct _PROCESSRECORD
{
    RECORDCORE      recc;

    ULONG           pid;
    PSZ             pszPID;
    CHAR            szPID[10];

    HMODULE         hmod;
    PSZ             pszHModule;
    CHAR            szHModule[10];

    PSZ             pszProcessName;
    CHAR            szProcessName[50];

    XWPSECID        uidOwner;
    PSZ             pszOwner;
    CHAR            szOwner[XWPSEC_NAMELEN + 10];

} PROCESSRECORD, *PPROCESSRECORD;

typedef struct _PROCESSTREENODE
{
    TREE            tree;       // ulKey is PID
    PROCESSRECORD   *pRecord;
    BOOL            fProcessed;
} PROCESSTREENODE, *PPROCESSTREENODE;

typedef struct _PROCESSPAGEDATA
{
    TREE            *ProcessTree;
    LONG            cProcessNodes;
} PROCESSPAGEDATA, *PPROCESSPAGEDATA;

static const CONTROLDEF
    ProcessesGroup = LOADDEF_GROUP(ID_AMSI_PROCESSES, SZL_AUTOSIZE),
    ProcessesCnr = CONTROLDEF_CONTAINER(
                            ID_XFDI_CNR_CNR,
                            200,        // for now, will be resized
                            100);       // for now, will be resized

static const DLGHITEM G_dlgProcesses[] =
    {
        START_TABLE,            // root table, required
            START_ROW(0),
                START_GROUP_TABLE(&ProcessesGroup),
                    START_ROW(0),
                        CONTROL_DEF(&ProcessesCnr),
                END_TABLE,
            START_ROW(0),       // notebook buttons (will be moved)
                CONTROL_DEF(&G_HelpButton),         // common.c
        END_TABLE
    };

MPARAM G_ampProcesses[] =
    {
        MPFROM2SHORT(ID_XFDI_CNR_CNR, XAC_SIZEX | XAC_SIZEY),
        MPFROM2SHORT(ID_AMSI_PROCESSES, XAC_SIZEX | XAC_SIZEY),
    };

/*
 *@@ ProcessesInitPage:
 *      V1.0.2 (2003-11-13) [umoeller]
 */

STATIC VOID ProcessesInitPage(PNOTEBOOKPAGE pnbp,   // notebook info struct
                              ULONG flFlags)        // CBI_* flags (notebook.h)
{
    if (flFlags & CBI_INIT)
    {
        HWND            hwndCnr,
                        hwndRemove;
        XFIELDINFO      xfi[4];
        PFIELDINFO      pfi = NULL;
        int             i = 0;

        PROCESSPAGEDATA *pPageData;
        pnbp->pUser
        = pPageData
        = NEW(PROCESSPAGEDATA);

        treeInit(&pPageData->ProcessTree,
                 &pPageData->cProcessNodes);

        // insert the controls using the dialog formatter
        // V1.0.1 (2003-01-05) [umoeller]
        ntbFormatPage(pnbp->hwndDlgPage,
                      G_dlgProcesses,
                      ARRAYITEMCOUNT(G_dlgProcesses));

        hwndCnr = WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR);

        // set up cnr details view
        xfi[i].ulFieldOffset = FIELDOFFSET(PROCESSRECORD, pszPID);
        xfi[i].pszColumnTitle = "PID";     // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        xfi[i].ulFieldOffset = FIELDOFFSET(PROCESSRECORD, pszHModule);
        xfi[i].pszColumnTitle = "Module";   // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        xfi[i].ulFieldOffset = FIELDOFFSET(PROCESSRECORD, pszProcessName);
        xfi[i].pszColumnTitle = "Executable";   // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        xfi[i].ulFieldOffset = FIELDOFFSET(PROCESSRECORD, pszOwner);
        xfi[i].pszColumnTitle = "Owner";   // @@todo localize
        xfi[i].ulDataType = CFA_STRING;
        xfi[i++].ulOrientation = CFA_LEFT;

        pfi = cnrhSetFieldInfos(hwndCnr,
                                xfi,
                                i,             // array item count
                                TRUE,          // draw lines
                                0);            // return first column

        BEGIN_CNRINFO()
        {
            cnrhSetView(CV_DETAIL | CA_DETAILSVIEWTITLES);
        } END_CNRINFO(hwndCnr);
    }

    if (flFlags & CBI_SET)
    {
    }

    if (flFlags & CBI_DESTROY)
    {
        PROCESSPAGEDATA *pPageData;

        if (pPageData = (PROCESSPAGEDATA*)pnbp->pUser)
        {
            LONG    cItems;
            TREE**  papNodes;
            if (papNodes = treeBuildArray(pPageData->ProcessTree,
                                          &cItems))
            {
                ULONG ul;
                for (ul = 0; ul < cItems; ul++)
                {
                    TREE *pNodeThis = papNodes[ul];
                    free(pNodeThis);
                }

                free(papNodes);
            }

            free(pnbp->pUser);
            pnbp->pUser = NULL;
        }
    }
}

/*
 *@@ ProcessesTimerPage:
 *
 *@@added V1.0.1 (2003-01-05) [umoeller]
 */

VOID ProcessesTimerPage(PNOTEBOOKPAGE pnbp,
                        ULONG ulTimer)
{
    APIRET arc;
    PQTOPLEVEL32 pInfo;
    PROCESSPAGEDATA *pPageData;

    if (    (pPageData = (PROCESSPAGEDATA*)pnbp->pUser)
         && (pInfo = prc32GetInfo(&arc))
       )
    {
        HWND hwndCnr = WinWindowFromID(pnbp->hwndDlgPage, ID_XFDI_CNR_CNR);

        PQPROCESS32 pProcThis = pInfo->pProcessData;

        // flag each tree node so we can, after having gone
        // thru the existing processes, remove all records
        // for which no process exists any longer
        PROCESSTREENODE *pNode;
        for (pNode = (PROCESSTREENODE*)treeFirst(pPageData->ProcessTree);
             pNode;
             pNode = (PROCESSTREENODE*)treeNext((TREE*)pNode))
        {
            pNode->fProcessed = FALSE;
        }

        // go over each process; for each entry, check if
        // it is on the page already; if not, add it
        while (pProcThis && pProcThis->ulRecType == 1)
        {
            PQTHREAD32  t = pProcThis->pThreads;
            PROCESSRECORD *pRecord = NULL;

            if (!(pNode = (PROCESSTREENODE*)treeFind(pPageData->ProcessTree,
                                                     pProcThis->usPID,
                                                     treeCompareKeys)))
            {
                if (pRecord = (PROCESSRECORD*)cnrhAllocRecords(hwndCnr,
                                                               sizeof(PROCESSRECORD),
                                                               1))
                {
                    pRecord->uidOwner = -999;

                    cnrhInsertRecords(hwndCnr,
                                      NULL,
                                      (PRECORDCORE)pRecord,
                                      FALSE, // invalidate
                                      NULL,
                                      CRA_RECORDREADONLY,
                                      1);

                    pNode = NEW(PROCESSTREENODE);
                    pNode->tree.ulKey = pProcThis->usPID;
                    pNode->pRecord = pRecord;
                    // flag this node (avoid deletion below)
                    pNode->fProcessed = TRUE;
                    treeInsert(&pPageData->ProcessTree,
                               &pPageData->cProcessNodes,
                               (TREE*)pNode,
                               treeCompareKeys);
                }
            }
            else
            {
                // flag this node (avoid deletion below)
                pNode->fProcessed = TRUE;
                pRecord = pNode->pRecord;
            }

            if (pRecord)
            {
                // now set up/refresh data
                PQMODULE32  pMod;
                BOOL        fInvalidate = FALSE;
                XWPSECID    uidOwner;

                if (pRecord->pid != pProcThis->usPID)
                {
                    pRecord->pid = pProcThis->usPID;
                    sprintf(pRecord->szPID, "0x%04lX", pProcThis->usPID);
                    pRecord->pszPID = pRecord->szPID;
                    fInvalidate = TRUE;
                }

                if (pRecord->hmod != pProcThis->usHModule)
                {
                    pRecord->hmod = pProcThis->usHModule;
                    sprintf(pRecord->szHModule, "0x%04lX", pProcThis->usHModule);
                    pRecord->pszHModule = pRecord->szHModule;
                    fInvalidate = TRUE;
                }

                if (    (pMod = prc32FindModule(pInfo,
                                                pProcThis->usHModule))
                     && (strcmp(pRecord->szProcessName, pMod->pcName))
                   )
                {
                    strlcpy(pRecord->szProcessName,
                            pMod->pcName,
                            sizeof(pRecord->szProcessName));
                    pRecord->pszProcessName = pRecord->szProcessName;
                    fInvalidate = TRUE;
                }


                if (arc = xsecQueryProcessOwner(pProcThis->usPID, &uidOwner))
                    uidOwner = (XWPSECID)(-(LONG)arc);
                if (pRecord->uidOwner != uidOwner)
                {
                    CHAR szOwner[XWPSEC_NAMELEN];
                    PCSZ pcszOwner = szOwner;
                    pRecord->uidOwner = uidOwner;
                    if (xsecQueryUserName(uidOwner, szOwner))
                        pcszOwner = "?";
                    sprintf(pRecord->szOwner, "%s (%d)", pcszOwner, uidOwner);
                    pRecord->pszOwner = pRecord->szOwner;
                    fInvalidate = TRUE;
                }

                if (fInvalidate)
                    WinSendMsg(hwndCnr,
                               CM_INVALIDATERECORD,
                               (MPARAM)&pRecord,
                               MPFROM2SHORT(1,
                                            CMA_TEXTCHANGED));
            }

            // next process block comes after the
            // threads
            t += pProcThis->usThreadCount;
            pProcThis = (PQPROCESS32)t;
        }

        prc32FreeInfo(pInfo);

        // now remove all tree nodes where fProcessed
        // is still FALSE; those need to go
        pNode = (PROCESSTREENODE*)treeFirst(pPageData->ProcessTree);
        while (pNode)
        {
            PROCESSTREENODE *pNext = (PROCESSTREENODE*)treeNext((TREE*)pNode);
            if (!pNode->fProcessed)
            {
                WinSendMsg(hwndCnr,
                           CM_REMOVERECORD,
                           &pNode->pRecord,
                           MPFROM2SHORT(1,
                                        CMA_FREE | CMA_INVALIDATE));

                treeDelete(&pPageData->ProcessTree,
                           &pPageData->cProcessNodes,
                           (TREE*)pNode);
            }

            pNode = pNext;
        }
    }
}

/* ******************************************************************
 *
 *   XWPAdmin instance methods
 *
 ********************************************************************/

/*
 *@@ xwpAddXWPAdminPages:
 *      this actually adds the new pages into the
 *      "XWorkplace Administrator" notebook.
 */

SOM_Scope ULONG  SOMLINK adm_xwpAddXWPAdminPages(XWPAdmin *somSelf,
                                                 HWND hwndDlg)
{
    ULONG   ulrc;
    INSERTNOTEBOOKPAGE  inbp;

    /* XWPAdminData *somThis = XWPAdminGetData(somSelf); */
    XWPAdminMethodDebug("XWPAdmin","adm_xwpAddXWPAdminPages");

    memset(&inbp, 0, sizeof(INSERTNOTEBOOKPAGE));
    inbp.somSelf = somSelf;
    inbp.hwndNotebook = hwndDlg;
    inbp.hmod = cmnQueryNLSModuleHandle(FALSE);

    // processes page
    inbp.ulDlgID = ID_XFD_EMPTYDLG; // ID_XFD_CONTAINERPAGE;
    inbp.usPageStyleFlags = BKA_MAJOR;
    inbp.pcszName = cmnGetString(ID_AMSI_PROCESSES);
    inbp.ulDefaultHelpPanel  = ID_XSH_ADMIN_PROCESSES;
    inbp.ulPageID = SP_ADMIN_PROCESSES;
    inbp.pampControlFlags = G_ampProcesses;
    inbp.cControlFlags = ARRAYITEMCOUNT(G_ampProcesses);
    inbp.pfncbInitPage    = ProcessesInitPage;
    inbp.pfncbTimer = ProcessesTimerPage;
    inbp.ulTimer = 1000;
    ntbInsertPage(&inbp);

    // check if XWPShell is running; if so the queue must exist
    if (xsecQueryStatus(NULL))
    {
        // error: display XWP page only then
        inbp.usPageStyleFlags = BKA_MAJOR;
        inbp.pcszName = "XWorkplace";
        inbp.ulDlgID = ID_XCD_FIRST;
        inbp.ulPageID = SP_SETUP_XWPLOGO;
        inbp.pfncbInitPage    = setLogoInitPage;
        inbp.pfncbMessage = setLogoMessages;
        ulrc = ntbInsertPage(&inbp);
    }
    else
    {
        // XWPShell running:

        // all groups
        inbp.ulDlgID = ID_XFD_EMPTYDLG; // ID_XFD_CONTAINERPAGE;
        inbp.usPageStyleFlags = BKA_MAJOR;
        inbp.pcszName = cmnGetString(ID_AMSI_ALL_GROUPS);
        inbp.ulDefaultHelpPanel  = ID_XSH_ADMIN_ALL_GROUPS;
        inbp.ulPageID = SP_ADMIN_ALL_GROUPS;
        inbp.pampControlFlags = G_ampAllGroups;
        inbp.cControlFlags = ARRAYITEMCOUNT(G_ampAllGroups);
        // enable the new add/edit/remove support:
        inbp.ulEditCnrID = ID_XFDI_CNR_CNR;
        inbp.pfncbInitPage    = AllGroupsInitPage;
        inbp.pfncbItemChanged = AllGroupsItemChanged;
        ntbInsertPage(&inbp);

        // all users
        inbp.ulDlgID = ID_XFD_EMPTYDLG; // ID_XFD_CONTAINERPAGE;
        inbp.usPageStyleFlags = BKA_MAJOR;
        inbp.pcszName = cmnGetString(ID_AMSI_ALL_USERS);
        inbp.ulDefaultHelpPanel  = ID_XSH_ADMIN_ALL_USERS;
        inbp.ulPageID = SP_ADMIN_ALL_USERS;
        inbp.pampControlFlags = G_ampAllUsers;
        inbp.cControlFlags = ARRAYITEMCOUNT(G_ampAllUsers);
        // enable the new add/edit/remove support:
        inbp.ulEditCnrID = ID_XFDI_CNR_CNR;
        inbp.pfncbInitPage    = AllUsersInitPage;
        inbp.pfncbItemChanged = AllUsersItemChanged;
        ntbInsertPage(&inbp);

        // current local user
        inbp.ulDlgID = ID_XFD_EMPTYDLG; // ID_AMD_USER;
        inbp.usPageStyleFlags = BKA_MAJOR;
        inbp.pcszName = cmnGetString(ID_AMSI_LOCAL_USER);
        inbp.ulDefaultHelpPanel  = ID_XSH_ADMIN_LOCAL_USER;
        inbp.ulPageID = SP_ADMIN_LOCAL_USER;
        inbp.pampControlFlags = NULL;
        inbp.cControlFlags = 0;
        inbp.pfncbInitPage    = LocalUserInitPage;
        inbp.pfncbItemChanged = NULL;
        inbp.pfncbTimer = LocalUserTimerPage;
        inbp.ulTimer = 1000;
        ulrc = ntbInsertPage(&inbp);

    }

    return ulrc;
}

/*
 *@@ wpFilterPopupMenu:
 *      this WPObject instance method allows the object to
 *      filter out unwanted menu items from the context menu.
 *      This gets called before wpModifyPopupMenu.
 */

SOM_Scope ULONG  SOMLINK adm_wpFilterPopupMenu(XWPAdmin *somSelf,
                                               ULONG ulFlags,
                                               HWND hwndCnr,
                                               BOOL fMultiSelect)
{
    /* XWPAdminData *somThis = XWPAdminGetData(somSelf); */
    XWPAdminMethodDebug("XWPAdmin","adm_wpFilterPopupMenu");

    return (XWPAdmin_parent_WPAbstract_wpFilterPopupMenu(somSelf,
                                                         ulFlags,
                                                         hwndCnr,
                                                         fMultiSelect)
            & ~CTXT_NEW
           );
}

/*
 *@@ wpQueryDefaultView:
 *      this WPObject method returns the default view of an object,
 *      that is, which view is opened if the program file is
 *      double-clicked upon. This is also used to mark
 *      the default view in the "Open" context submenu.
 *
 *      This must be overridden for direct WPAbstract subclasses,
 *      because otherwise double-clicks on the object won't
 *      work.
 */

SOM_Scope ULONG  SOMLINK adm_wpQueryDefaultView(XWPAdmin *somSelf)
{
    /* XWPAdminData *somThis = XWPAdminGetData(somSelf); */
    XWPAdminMethodDebug("XWPAdmin","adm_wpQueryDefaultView");

    return OPEN_SETTINGS;
}

/*
 *@@ wpAddObjectWindowPage:
 *      this WPObject instance method normally adds the
 *      "Standard Options" page to the settings notebook
 *      (that's what the WPS reference calls it; it's actually
 *      the "Window" page).
 *
 *      We don't want that page here, so we remove it.
 */

SOM_Scope ULONG  SOMLINK adm_wpAddObjectWindowPage(XWPAdmin *somSelf,
                                                   HWND hwndNotebook)
{
    /* XWPAdminData *somThis = XWPAdminGetData(somSelf); */
    XWPAdminMethodDebug("XWPAdmin","adm_wpAddObjectWindowPage");

    return SETTINGS_PAGE_REMOVED;
}

/*
 *@@ wpAddSettingsPages:
 *      this WPObject instance method gets called by the WPS
 *      when the Settings view is opened to have all the
 *      settings page inserted into hwndNotebook. Override
 *      this method to add new settings pages to either the
 *      top or the bottom of notebooks of a given class.
 *
 *      We add the various new pages here.
 */

SOM_Scope BOOL  SOMLINK adm_wpAddSettingsPages(XWPAdmin *somSelf,
                                               HWND hwndNotebook)
{
    /* XWPAdminData *somThis = XWPAdminGetData(somSelf); */
    XWPAdminMethodDebug("XWPAdmin","adm_wpAddSettingsPages");

    XWPAdmin_parent_WPAbstract_wpAddSettingsPages(somSelf,
                                                  hwndNotebook);

    // add XWorkplace pages on top
    _xwpAddXWPAdminPages(somSelf, hwndNotebook);

    return TRUE;
}

/* ******************************************************************
 *
 *   XWPAdmin instance methods
 *
 ********************************************************************/

/*
 * wpclsQueryStyle:
 *      prevent copy, delete, print.
 *
 */

SOM_Scope ULONG  SOMLINK admM_wpclsQueryStyle(M_XWPAdmin *somSelf)
{
    /* M_XWPAdminData *somThis = M_XWPAdminGetData(somSelf); */
    M_XWPAdminMethodDebug("M_XWPAdmin","admM_wpclsQueryStyle");

    return (M_XWPAdmin_parent_M_WPAbstract_wpclsQueryStyle(somSelf)
                | CLSSTYLE_NEVERTEMPLATE
                | CLSSTYLE_NEVERPRINT
                | CLSSTYLE_NEVERCOPY
                | CLSSTYLE_NEVERDELETE);
}

/*
 *@@ wpclsQueryTitle:
 *      this M_WPObject class method tells the WPS the clear
 *      name of a class, which is shown in the third column
 *      of a Details view and also used as the default title
 *      for new objects of a class.
 */

SOM_Scope PSZ  SOMLINK admM_wpclsQueryTitle(M_XWPAdmin *somSelf)
{
    // PNLSSTRINGS pNLSStrings = cmnQueryNLSStrings();
    /* M_XWPAdminData *somThis = M_XWPAdminGetData(somSelf); */
    M_XWPAdminMethodDebug("M_XWPAdmin","admM_wpclsQueryTitle");

    return (PSZ)cmnGetString(ID_XSSI_ADMINISTRATOR);
}

/*
 *@@ wpclsQueryDefaultHelp:
 *      this M_WPObject class method returns the default help
 *      panel for objects of this class. This gets called
 *      from WPObject::wpQueryDefaultHelp if no instance
 *      help settings (HELPLIBRARY, HELPPANEL) have been
 *      set for an individual object. It is thus recommended
 *      to override this method instead of the instance
 *      method to change the default help panel for a class
 *      in order not to break instance help settings (fixed
 *      with 0.9.20).
 *
 *@@added V0.9.20 (2002-07-12) [umoeller]
 */

SOM_Scope BOOL  SOMLINK admM_wpclsQueryDefaultHelp(M_XWPAdmin *somSelf,
                                                   PULONG pHelpPanelId,
                                                   PSZ pszHelpLibrary)
{
    /* M_XWPAdminData *somThis = M_XWPAdminGetData(somSelf); */
    M_XWPAdminMethodDebug("M_XWPAdmin","admM_wpclsQueryDefaultHelp");

    return M_XWPAdmin_parent_M_WPAbstract_wpclsQueryDefaultHelp(somSelf,
                                                                pHelpPanelId,
                                                                pszHelpLibrary);
}

/*
 *@@ wpclsQueryIconData:
 *      this M_WPObject class method must return information
 *      about how to build the default icon for objects
 *      of a class. This gets called from various other
 *      methods whenever a class default icon is needed;
 *      most importantly, M_WPObject::wpclsQueryIcon
 *      calls this to build a class default icon, which
 *      is then cached in the class's instance data.
 *      If a subclass wants to change a class default icon,
 *      it should always override _this_ method instead of
 *      wpclsQueryIcon.
 *
 *      Note that the default WPS implementation does not
 *      allow for specifying the ICON_FILE format here,
 *      which is why we have overridden
 *      M_XFldObject::wpclsQueryIcon too. This allows us
 *      to return icon _files_ for theming too. For details
 *      about the WPS's crappy icon management, refer to
 *      src\filesys\icons.c.
 *
 *      We override this to give XWPAdmin objects a new
 *      icon (src\shared\xwpadmin.ico).
 */

SOM_Scope ULONG  SOMLINK admM_wpclsQueryIconData(M_XWPAdmin *somSelf,
                                                 PICONINFO pIconInfo)
{
    /* M_XWPAdminData *somThis = M_XWPAdminGetData(somSelf); */
    M_XWPAdminMethodDebug("M_XWPAdmin","admM_wpclsQueryIconData");

    if (pIconInfo)
    {
        pIconInfo->fFormat = ICON_RESOURCE;
        pIconInfo->resid   = ID_ICONXWPADMIN;
        pIconInfo->hmod    = cmnQueryMainResModuleHandle();
    }

    return sizeof(ICONINFO);
}


