#ifdef RCSID
static char RCSid[] =
"$Header: d:/cvsroot/tads/html/win32/tadsdlg.cpp,v 1.3 1999/07/11 00:46:47 MJRoberts Exp $";
#endif

/* 
 *   Copyright (c) 1997 by Michael J. Roberts.  All Rights Reserved.
 *   
 *   Please see the accompanying license file, LICENSE.TXT, for information
 *   on using and copying this software.  
 */
/*
Name
  tadsdlg.cpp - dialogs
Function
  
Notes
  
Modified
  10/27/97 MJRoberts  - Creation
*/

#include <stdlib.h>
#include <malloc.h>
#include <windows.h>

#ifndef TADSDLG_H
#include "tadsdlg.h"
#endif
#ifndef TADSHTML_H
#include "tadshtml.h"
#endif
#ifndef TADSAPP_H
#include "tadsapp.h"
#endif
#ifndef TADSWIN_H
#include "tadswin.h"
#endif
#ifndef HTMLRES_H
#include "htmlres.h"
#endif


/* ------------------------------------------------------------------------ */
/*
 *   Dialog implementation 
 */

/* list of open dialogs */
CTadsDialog **CTadsDialog::dialogs_ = 0;
size_t CTadsDialog::dialogs_size_ = 0;
size_t CTadsDialog::dialog_cnt_ = 0;


CTadsDialog::CTadsDialog()
{
    /* we don't know our handle yet */
    handle_ = 0;

    /* not running modally yet */
    is_modal_ = FALSE;
}

CTadsDialog::~CTadsDialog()
{
    /* make sure I'm not still in the dialog list */
    remove_from_active();
}

/* ------------------------------------------------------------------------ */
/*
 *   Open a modeless dialog 
 */
HWND CTadsDialog::open_modeless(unsigned int res_id, HWND owner,
                                HINSTANCE app_inst)
{
    /* mark me as modeless */
    is_modal_ = FALSE;

    /* create the dialog and return its handle */
    return CreateDialogParam(app_inst, MAKEINTRESOURCE(res_id), owner,
                             (DLGPROC)&dialog_proc, (LPARAM)this);
}

/* ------------------------------------------------------------------------ */
/*
 *   Modal dialog operations 
 */

/*
 *   Context structure for modal dialog pre/post routines.  The pre routine
 *   allocates the context, fills it up with information to pass to the post
 *   routine, and the post routine uses the information and deletes the
 *   memory.  
 */
struct modal_dlg_ctx_t
{
    /* accelerator enabled status */
    int accel_ena_;

    /* number of window handles allocated */
    int count_;

    /* window handle of owner, if we disabled it */
    HWND owner_;

    /* window handles */
    HWND disabled_[1];
};

/*
 *   Disable windows prior to running a modal dialog.  The returned context
 *   must be passed to modal_dlg_post() after the dialog is dismissed.  
 */
void *CTadsDialog::modal_dlg_pre(HWND owner, int disable_owner)
{
    int i;
    HWND cur;
    modal_dlg_ctx_t *ctx;

    /*
     *   Determine how many windows we'll need to disable, so that we can
     *   allocate memory for a list of those windows.  
     */
    for (cur = GetTopWindow(0), i = 0 ; cur != 0 ;
         cur = GetWindow(cur, GW_HWNDNEXT))
    {
        /* if we'll need to disable this window, count it */
        if (GetWindow(cur, GW_OWNER) == owner && IsWindowEnabled(cur))
            ++i;
    }

    /* allocate space to keep track of the ones we disable */
    ctx = (modal_dlg_ctx_t *)th_malloc(sizeof(modal_dlg_ctx_t)
                                       + (i-1)*sizeof(HWND));
    ctx->count_ = i;

    /* 
     *   disable all windows owned by the owner that aren't already disabled;
     *   keep track of the ones we disable so that we can re-enable them (and
     *   only them) when we're done 
     */
    for (cur = GetTopWindow(0), i = 0 ; cur != 0 ;
         cur = GetWindow(cur, GW_HWNDNEXT))
    {
        if (GetWindow(cur, GW_OWNER) == owner && IsWindowEnabled(cur))
        {
            /* remember the window so we can re-enable it later */
            ctx->disabled_[i] = cur;
            ++i;

            /* disable it */
            EnableWindow(cur, FALSE);
        }
    }

    /* disable the owner as well, if appropriate */
    if (disable_owner && IsWindowEnabled(owner))
    {
        ctx->owner_ = owner;
        EnableWindow(owner, FALSE);
    }
    else
        ctx->owner_ = 0;

    /* temporarily disable accelerators, so we get ordinary dialog keys */
    if (CTadsApp::get_app() != 0)
        ctx->accel_ena_ = CTadsApp::get_app()->enable_accel(FALSE);

    /* 
     *   return the list of disabled windows - modal_dlg_post() will use it
     *   to re-enable the windows 
     */
    return ctx;
}

/*
 *   Re-enable windows disabled during a modal dialog 
 */
void CTadsDialog::modal_dlg_post(void *ctx0)
{
    modal_dlg_ctx_t *ctx = (modal_dlg_ctx_t *)ctx0;
    int i;

    /* restore the accelerator status */
    if (CTadsApp::get_app() != 0)
        CTadsApp::get_app()->enable_accel(ctx->accel_ena_);

    /* re-enable all windows we changed */
    for (i = ctx->count_ ; i != 0 ; )
    {
        /* move on to the next window */
        --i;

        /* re-enable it */
        EnableWindow(ctx->disabled_[i], TRUE);
    }

    /* re-enable the owner window if appropriate */
    if (ctx->owner_ != 0)
        EnableWindow(ctx->owner_, TRUE);

    /* free the context structure */
    th_free(ctx);
}

/*
 *   Run a modal dialog 
 */
int CTadsDialog::run_modal(unsigned int res_id, HWND owner,
                           HINSTANCE app_inst)
{
    void *ctx;
    int result;

    /* mark me as modal */
    is_modal_ = TRUE;

    /* disable windows while the dialog is running */
    if (owner != 0)
        ctx = modal_dlg_pre(owner, FALSE);

    /* add this dialog to the active list */
    add_to_active();

    /* run the dialog */
    result = DialogBoxParam(app_inst, MAKEINTRESOURCE(res_id), owner,
                            (DLGPROC)&dialog_proc, (LPARAM)this);

    /* re-enable the windows */
    if (owner != 0)
        modal_dlg_post(ctx);

    /* return the result */
    return result;
}

/* ------------------------------------------------------------------------ */
/*
 *   Dialog procedure 
 */
BOOL CALLBACK CTadsDialog::dialog_proc(HWND dlg_hwnd, UINT message,
                                       WPARAM wpar, LPARAM lpar)
{
    CTadsDialog *dlg;

    /* 
     *   if this is the initialization message, get the CTadsDialog object
     *   from the LPARAM, and register the dialog 
     */
    if (message == WM_INITDIALOG)
    {
        /* get the dialog object from the lparam; if we find it, prepare it */
        if ((dlg = (CTadsDialog *)lpar) != 0)
            dlg->prepare_to_open(dlg_hwnd);
    }
    else
    {
        /* find the dialog object */
        dlg = find_dialog(dlg_hwnd);

        /* 
         *   For combo boxes with CBS_OWNERDRAWFIXED, we received a
         *   WM_MEASUREITEM message from the combo box *before* we get our
         *   WM_INITDIALOG.  This is unfortuate, since it means that we don't
         *   have our dialog-to-hwnd association set up yet.  We handle this
         *   by running through our list of active dialogs, looking for
         *   dialogs that haven't been initialized yet, giving each one a
         *   chance to initialize the control.  
         */
        if (message == WM_MEASUREITEM && dlg == 0)
        {
            CTadsDialog **cur;
            size_t i;

            /* get the control ID and handle, and the measuring structure */
            int ctl_id = wpar;
            HWND ctl_hwnd = GetDlgItem(dlg_hwnd, ctl_id);
            MEASUREITEMSTRUCT *mis = (MEASUREITEMSTRUCT *)lpar;

            /* 
             *   scan the list of active dialogs, looking for dialogs that
             *   haven't been initialized yet; give each one a chance to
             *   handle the message 
             */
            for (i = 0, cur = dialogs_ ; i < dialog_cnt_ ; ++i, ++cur)
            {
                /* if this dialog hasn't been initialized, try it */
                if ((*cur)->handle_ == 0
                    && (*cur)->pre_measure_item(ctl_id, ctl_hwnd, mis))
                    return TRUE;
            }

            /* we didn't find a taker */
            return FALSE;
        }

        /* 
         *   if we didn't find the dialog, we can't do the virtual dispatch,
         *   so just return FALSE to indicate we didn't handle the message 
         */
        if (dlg == 0)
            return FALSE;
    }

    /* process the message */
    return dlg->do_dialog_msg(dlg_hwnd, message, wpar, lpar);
}

/* ------------------------------------------------------------------------ */
/*
 *   Initialize the dialog 
 */
void CTadsDialog::init()
{
    RECT drc;
    RECT rc;
    int wid, ht;

    /* get my size */
    GetWindowRect(handle_, &rc);
    wid = rc.right - rc.left;
    ht = rc.bottom - rc.top;
    
    /* center the dialog on the screen */
    GetWindowRect(GetDesktopWindow(), &drc);
    MoveWindow(handle_, drc.left + (drc.right - drc.left - wid)/2,
               drc.top + (drc.bottom - drc.top - ht)/2, wid, ht, TRUE);
}

/*
 *   Process a dialog event 
 */
BOOL CTadsDialog::do_dialog_msg(HWND dlg_hwnd, UINT message,
                                WPARAM wpar, LPARAM lpar)
{
    switch(message)
    {
    case WM_INITDIALOG:
        /* initialize the dialog */
        init();

        /* initialize focus, and return the result */
        return init_focus((int)wpar);

    case WM_DESTROY:
        /* notify the dialog that the window is being destroyed */
        destroy();
        return TRUE;

    case WM_DRAWITEM:
        /* draw an owner-drawn control */
        return draw_item((int)wpar, (DRAWITEMSTRUCT *)lpar);

    case WM_MEASUREITEM:
        /* measure an owner-drawn control */
        return measure_item((int)wpar, (MEASUREITEMSTRUCT *)lpar);

    case WM_DELETEITEM:
        /* delete an item in an owner-drawn control */
        return delete_ctl_item((int)wpar, (DELETEITEMSTRUCT *)lpar);

    case WM_COMMAND:
        /* process a command */
        return do_command(wpar, (HWND)lpar);

    case WM_COMPAREITEM:
        /* return the sorting information */
        return compare_ctl_item((int)wpar, (COMPAREITEMSTRUCT *)lpar);

    case WM_NOTIFY:
        /* process a notification message */
        return do_notify((NMHDR *)lpar, (int)wpar);

    default:
        /* not one of ours */
        break;
    }

    /* we didn't handle it */
    return FALSE;
}

/*
 *   receive notification that the Windows dialog is being destroyed
 */
void CTadsDialog::destroy()
{
    /* remove myself from the active dialog list */
    remove_from_active();

    /* my window handle is no longer valid */
    handle_ = 0;
}

/*
 *   draw an owner-drawn control 
 */
int CTadsDialog::draw_item(int id, DRAWITEMSTRUCT *dis)
{
    /* find the item in the control list and draw it */
    return controls_.draw_item(id, dis);
}

/*
 *   measure an owner-drawn control for drawing 
 */
int CTadsDialog::measure_item(int id, MEASUREITEMSTRUCT *mis)
{
    /* find the item in the control list and measure it */
    return controls_.measure_item(id, GetDlgItem(handle_, id), mis);
}

/*
 *   measure an owner-drawn control for drawing prior to WM_INITDIALOG
 */
int CTadsDialog::pre_measure_item(int id, HWND hwnd, MEASUREITEMSTRUCT *mis)
{
    /* find the item in the control list and measure it */
    return controls_.measure_item(id, hwnd, mis);
}

/*
 *   process a command 
 */
int CTadsDialog::do_command(WPARAM id, HWND)
{
    /* find the item in the control list and send it the command */
    if (controls_.do_command(handle_, id))
        return TRUE;

    /* if we didn't find a control, see if we can handle it ourselves */
    switch(id)
    {
    case IDOK:
    case IDCANCEL:
        /* 
         *   If I'm running modally, dismiss the dialog.  This isn't allowed
         *   for a modeless dialog. 
         */
        if (is_modal_)
            EndDialog(handle_, id);
        return TRUE;

    default:
        /* we couldn't handle it */
        return FALSE;
    }
}

/*
 *   add me to the open dialog list 
 */
void CTadsDialog::add_to_active()
{
    /* make sure I'm not already in the list */
    for (size_t i = 0 ; i < dialog_cnt_ ; ++i)
    {
        if (dialogs_[i] == this)
            return;
    }

    /* if the list isn't big enough, expand it */
    if (dialogs_ == 0)
    {
        dialogs_size_ = 5;
        dialogs_ = (CTadsDialog **)
                   th_malloc(dialogs_size_ * sizeof(*dialogs_));
    }
    else if (dialog_cnt_ >= dialogs_size_)
    {
        dialogs_size_ += 5;
        dialogs_ = (CTadsDialog **)
                   th_realloc(dialogs_, dialogs_size_ * sizeof(*dialogs_));
    }

    /* add myself to the list */
    dialogs_[dialog_cnt_++] = this;
}

/*
 *   remove a dialog from the list 
 */
void CTadsDialog::remove_from_active()
{
    size_t i;
    CTadsDialog **cur;
    
    /* scan the list for me */
    for (i = 0, cur = dialogs_ ; i < dialog_cnt_ ; ++i, ++cur)
    {
        /* see if it's me */
        if (*cur == this)
        {
            /* move everything else in the list down one slot */
            for ( ; i + 1 < dialog_cnt_ ; ++i, ++cur)
                *cur = *(cur + 1);

            /* decrease the count */
            --dialog_cnt_;

            /* 
             *   if the count has dropped to zero, release the memory used
             *   by the list 
             */
            if (dialog_cnt_ == 0)
            {
                th_free(dialogs_);
                dialogs_ = 0;
                dialogs_size_ = 0;
            }

            /* no need to look any further */
            break;
        }
    }
}

/*
 *   find a dialog in the active dialog list, given a window handle 
 */
CTadsDialog *CTadsDialog::find_dialog(HWND hwnd)
{
    CTadsDialog **cur;
    size_t i;

    /* scan the list for a dialog with the given window handle */
    for (i = 0, cur = dialogs_ ; i < dialog_cnt_ ; ++i, ++cur)
    {
        /* if this is the one, return it */
        if ((*cur)->handle_ == hwnd)
            return *cur;
    }

    /* didn't find it */
    return 0;
}

/* our private callback paramaters structure for the font enumerator */
struct ifp_cb_info
{
    HWND ctl;
    int include_proportional;
    int include_fixed;
    int (*selector_func)(void *ctx, ENUMLOGFONTEX *elf, NEWTEXTMETRIC *tm);
    void *selector_func_ctx;
    unsigned int charset_id;
};

/*
 *   font enumeration callback - add the font to the popup if it matches
 *   the font type we're looking for 
 */
int CALLBACK CTadsDialog::init_font_popup_cb(ENUMLOGFONTEX *elf,
                                             NEWTEXTMETRIC *tm,
                                             DWORD /*font_type*/,
                                             LPARAM lpar)
{
    ifp_cb_info *info = (ifp_cb_info *)lpar;
    HWND ctl = info->ctl;
    int proportional;
    int fixed;
    int include_font;

    /* presume we won't include the font */
    include_font = FALSE;

    /* if there's a selector callback function, use it to decide */
    if (info->selector_func != 0)
    {
        /* let the selector decide whether to use it */
        include_font = (*info->selector_func)(info->selector_func_ctx,
                                              elf, tm);
    }
    else
    {
        /* 
         *   note whether this font is proportional (NB: it's *Windows* that
         *   has the constant naming backwards on this, not us!) 
         */
        proportional = ((tm->tmPitchAndFamily & TMPF_FIXED_PITCH) != 0);
        fixed = !proportional;

        /* 
         *   add this font name to the popup's list if it matches our
         *   criteria, and it's not a symbol font 
         */
        if (((proportional && info->include_proportional)
             || (fixed && info->include_fixed))
            && tm->tmCharSet != SYMBOL_CHARSET
            && (elf->elfLogFont.lfPitchAndFamily & 0xf0) != FF_DECORATIVE)
            include_font = TRUE;
    }

    /* add the font to the control's list if we want to include it */
    if (include_font)
    {
        int idx;
        
        /* add the font to the control */
        idx = SendMessage(ctl, CB_ADDSTRING, (WPARAM)0,
                          (LPARAM)elf->elfLogFont.lfFaceName);

        /* set the style flags in the item's data */
        SendMessage(ctl, CB_SETITEMDATA, idx, (LPARAM)tm->tmPitchAndFamily);
    }

    /* continue the enumeration */
    return TRUE;
}

/*
 *   initialize a font popup - load it up with fonts of the appropriate
 *   type 
 */
void CTadsDialog::init_font_popup(int id, ifp_cb_info *info,
                                  const textchar_t *init_setting, int clear)
{
    HWND ctl;
    HDC dc;
    int idx;
    LOGFONT lf;

    /* get the control */
    ctl = GetDlgItem(handle_, id);

    /* empty out the list if the caller so desires */
    if (clear)
        SendMessage(ctl, CB_RESETCONTENT, 0, 0);

    /* get the device context, so we can list its fonts */
    dc = GetDC(handle_);

    /* ask for all fonts in all styles for the requested character set */
    lf.lfFaceName[0] = '\0';
    lf.lfPitchAndFamily = 0;
    lf.lfCharSet = (BYTE)info->charset_id;

    /* enumerate fonts and fill up the popup with font names */
    info->ctl = ctl;
    EnumFontFamiliesEx(dc, &lf, (FONTENUMPROC)init_font_popup_cb, 
                       (LPARAM)info, 0);

    /* select the item with the current setting, if possible */
    idx = SendMessage(ctl, CB_FINDSTRING, (WPARAM)-1, (LPARAM)init_setting);
    if (idx >= 0)
        SendMessage(ctl, CB_SETCURSEL, (WPARAM)idx, 0);

    /* done with the desktop device context */
    ReleaseDC(handle_, dc);
}

/*
 *   initialize a font popup - load it up with fonts of the appropriate
 *   type 
 */
void CTadsDialog::init_font_popup(int id, int include_proportional,
                                  int include_fixed,
                                  const textchar_t *init_setting, int clear,
                                  unsigned int charset_id)
{
    ifp_cb_info info;

    /* enumerate fonts based on fixed/proportional attributes */
    info.include_proportional = include_proportional;
    info.include_fixed = include_fixed;
    info.selector_func = 0;
    info.charset_id = charset_id;
    init_font_popup(id, &info, init_setting, clear);
}

/*
 *   initialize a font popup - load it with fonts selected by a given
 *   callback function 
 */
void CTadsDialog::
   init_font_popup(int id, const textchar_t *init_setting,
                   int (*selector_func)(void *, ENUMLOGFONTEX *,
                                        NEWTEXTMETRIC *),
                   void *selector_func_ctx, int clear,
                   unsigned int charset_id)

{
    ifp_cb_info info;

    /* enumerate fonts based on fixed/proportional attributes */
    info.selector_func = selector_func;
    info.selector_func_ctx = selector_func_ctx;
    info.charset_id = charset_id;
    init_font_popup(id, &info, init_setting, clear);
}

/*
 *   Set up an OPENFILENAME structure to hook to our centering hook 
 */
void CTadsDialog::set_filedlg_center_hook(OPENFILENAME *info)
{
    /* add our flags and hook procedure pointer */
    info->Flags |= OFN_ENABLEHOOK | OFN_EXPLORER;
    info->lpfnHook = &filedlg_center_hook;
}

/*
 *   Hook procedure for GetOpenFileName/GetSaveFileName hook.  This hook
 *   procedure simply centers the dialog on the screen at initialization. 
 */
UINT APIENTRY CTadsDialog::filedlg_center_hook(HWND, UINT msg,
                                               WPARAM, LPARAM lpar)
{
    switch(msg)
    {
    case WM_NOTIFY:
        {
            NMHDR *nm = (NMHDR *)lpar;

            /* check which notification we're receiving */
            switch(nm->code)
            {
            case CDN_INITDONE:
                /* center the dialog on the screen */
                center_dlg_win(nm->hwndFrom);
                
                /* processed */
                return 1;

            default:
                /* ignore others */
                break;
            }
        }
        break;

    default:
        break;
    }

    /* we didn't process the message */
    return 0;
}

/*
 *   Center a dialog window on the screen 
 */
void CTadsDialog::center_dlg_win(HWND dialog_hwnd)
{
    RECT deskrc;
    RECT dlgrc;
    int deskwid, deskht;
    int dlgwid, dlght;

    /* get the desktop area */
    GetWindowRect(GetDesktopWindow(), &deskrc);
    deskwid = deskrc.right - deskrc.left;
    deskht = deskrc.bottom - deskrc.top;

    /* get the dialog box area */
    GetWindowRect(dialog_hwnd, &dlgrc);
    dlgwid = dlgrc.right - dlgrc.left;
    dlght = dlgrc.bottom - dlgrc.top;

    /* center the dialog on the screen */
    MoveWindow(dialog_hwnd,
               deskrc.left + (deskwid - dlgwid)/2,
               deskrc.top + (deskht - dlght)/2,
               dlgwid, dlght, TRUE);
}


/* ------------------------------------------------------------------------ */
/*
 *   Dialog control list 
 */


CTadsDialogCtlList::~CTadsDialogCtlList()
{
    int i;
    CTadsDialogCtl **ctl;

    /* delete each control */
    for (i = 0, ctl = arr_ ; i < cnt_ ; ++i, ++ctl)
        delete *ctl;

    /* delete the array */
    th_free(arr_);
}

void CTadsDialogCtlList::add(class CTadsDialogCtl *ctl)
{
    /* allocate more space if needed */
    if (arr_size_ <= cnt_)
    {
        arr_size_ += 10;
        arr_ = (class CTadsDialogCtl **)
               th_realloc(arr_, arr_size_ * sizeof(*arr_));
    }

    /* add the new item */
    arr_[cnt_++] = ctl;
}

class CTadsDialogCtl *CTadsDialogCtlList::find(int id, HWND hdl)
{
    int i;
    CTadsDialogCtl **p;

    /* scan our array for a matching handle */
    for (i = 0, p = arr_ ; i < cnt_ ; ++i, ++p)
    {
        /* 
         *   if a handle was given, and this control has a handle, match on
         *   handle; otherwise, match on control ID 
         */
        if (hdl != 0 && (*p)->gethdl() != 0
            ? (*p)->gethdl() == hdl
            : (*p)->getid() == id)
            return *p;
    }

    /* didn't find it */
    return 0;
}

/*
 *   draw a control 
 */
int CTadsDialogCtlList::draw_item(int id, DRAWITEMSTRUCT *dis)
{
    CTadsDialogCtl *ctl;

    /* if we can find the item, draw it */
    if ((ctl = find(id, dis->hwndItem)) != 0)
    {
        ctl->draw(id, dis);
        return TRUE;
    }

    /* didn't find it */
    return FALSE;
}

/*
 *   measure a control for drawing 
 */
int CTadsDialogCtlList::measure_item(
    int id, HWND hwnd, MEASUREITEMSTRUCT *mis)
{
    CTadsDialogCtl *ctl;

    /* if we can find the item, draw it */
    if ((ctl = find(id, hwnd)) != 0)
    {
        ctl->measure(id, mis);
        return TRUE;
    }

    /* didn't find it */
    return FALSE;
}

/*
 *   invoke the command on a given control 
 */
int CTadsDialogCtlList::do_command(HWND dlg, WPARAM wpar)
{
    HWND ctlhdl;
    CTadsDialogCtl *ctl;
    int ctl_id = LOWORD(wpar);

    /* get the control handle */
    ctlhdl = GetDlgItem(dlg, ctl_id);

    /* if we can find the item, invoke its do_command method */
    if ((ctl = find(ctl_id, ctlhdl)) != 0)
    {
        ctl->do_command(HIWORD(wpar));
        return TRUE;
    }

    /* didn't find it */
    return FALSE;
}


/* ------------------------------------------------------------------------ */
/*
 *   Property page dialog subclass 
 */
BOOL CALLBACK CTadsDialogPropPage::dialog_proc(HWND dlg_hwnd, UINT message,
                                               WPARAM wpar, LPARAM lpar)
{
    /* if this is a WM_INITDIALOG message, set up the property page */
    if (message == WM_INITDIALOG)
    {
        PROPSHEETPAGE *pg;
        CTadsDialogPropPage *dlg;

        /* the LPARAM is the propsheetpage structure */
        pg = (PROPSHEETPAGE *)lpar;

        /* 
         *   the lParam in the propsheetpage structure is the
         *   CTadsDialogPropPage object 
         */
        dlg = (CTadsDialogPropPage *)pg->lParam;

        /* remember the window handle */
        dlg->prepare_to_open(dlg_hwnd);

        /* process the message */
        return dlg->do_dialog_msg(dlg_hwnd, message, wpar, lpar);
    }

    /* 
     *   if this is a notification with PSN_SETACTIVE, get the property sheet
     *   main dialog handle from the NMHDR structure 
     */
    if (message == WM_NOTIFY && ((NMHDR *)lpar)->code == PSN_SETACTIVE)
    {
        CTadsDialogPropPage *dlg;

        /* find the dialog */
        if ((dlg = (CTadsDialogPropPage *)find_dialog(dlg_hwnd)) != 0)
        {
            /* set its sheet handle */
            dlg->sheet_handle_ = ((NMHDR *)lpar)->hwndFrom;
        }
    }

    /* do the normal processing */
    return CTadsDialog::dialog_proc(dlg_hwnd, message, wpar, lpar);
}

/*
 *   Set the changed flag 
 */
void CTadsDialogPropPage::set_changed(int changed)
{
    /* send the appropriate message */
    SendMessage(sheet_handle_, changed ? PSM_CHANGED : PSM_UNCHANGED,
                (WPARAM)handle_, 0);
}

/*
 *   Process a dialog event 
 */
BOOL CTadsDialogPropPage::do_dialog_msg(HWND dlg_hwnd, UINT message,
                                        WPARAM wpar, LPARAM lpar)
{
    switch(message)
    {
    case PSM_QUERYSIBLINGS:
        /* 
         *   Process the query-siblings request reflected from the parent.
         *   Note that we must return the value through our window long value
         *   at DWL_MSGRESULT.  
         */
        SetWindowLong(handle_, DWL_MSGRESULT, query_siblings(wpar, lpar));

        /* indicate that we handled the message */
        return TRUE;

    default:
        /* proceed to default handling */
        break;
    }

    /* inherit the default handling */
    return CTadsDialog::do_dialog_msg(dlg_hwnd, message, wpar, lpar);
}

