
/*
 *@@sourcefile notebook.c:
 *      this file is new with V0.82 and contains very useful code for
 *      Settings notebooks pages. Most XFolder notebook pages are
 *      now implemented using these routines.
 *
 *      All the functions in this file have the ntb* prefix.
 *
 *      The concept of this is that when inserting a notebook page
 *      by overriding the proper WPS methods for an object, you call
 *      ntbInsertPage here instead of calling wpInsertSettingsPage.
 *      This function will always use the same window procedure
 *      (fnwpNotebookCommon) and call callbacks for certain notebook
 *      events which you can specify in your call to ntbInsertPage.
 *      Callbacks exist for everything you will need on a notebook page;
 *      this saves you from having to rewrite the same window proc for
 *      every notebook page.
 *
 *      See the declaration of CREATENOTEBOOKPAGE in notebook.h for
 *      details about the callbacks.
 *
 *      These routines maintain a list of currently "initialized"
 *      notebook pages, i.e. pages that are currently instantiated
 *      in memory. You can iterate over these pages in order to
 *      have controls on other pages updated, if this is necessary,
 *      using ntbQueryOpenPages and ntbUpdateVisiblePage.
 *
 *@@include #define INCL_DOSMODULEMGR
 *@@include #define INCL_WINWINDOWMGR
 *@@include #include <os2.h>
 *@@include #include <wpobject.h>
 *@@include #include "notebook.h"
 */

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

/*
 *  Suggested #include order:
 *  1)  os2.h
 *  2)  C library headers
 *  3)  SOM headers which work with precompiled header files
 *  4)  headers in /helpers
 *  5)  headers in /main with dlgids.h and common.h first
 *  6)  #pragma hdrstop to prevent VAC++ crashes
 *  7)  other needed SOM headers
 *  8)  for non-SOM-class files: corresponding header (e.g. classlst.h)
 */

#define INCL_DOSSEMAPHORES      // needed for xthreads.h
#define INCL_DOSEXCEPTIONS      // needed for except.h
#define INCL_DOSERRORS

#define INCL_WINWINDOWMGR
#define INCL_WINFRAMEMGR        // WM_FORMATFRAME, SC_CLOSE etc.
#define INCL_WINDIALOGS
#define INCL_WININPUT           // WinQueryFocus etc.

#define INCL_WINBUTTONS
#define INCL_WINLISTBOXES
#define INCL_WINENTRYFIELDS
#define INCL_WINSTDBOOK         // notebooks
#define INCL_WINSTDSPIN         // spin buttons
#define INCL_WINSTDCNR          // needed for winh.h

#define INCL_WINTIMER

#include <os2.h>

// C library headers
#include <stdio.h>              // needed for except.h
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

// headers in /helpers
#include "winh.h"               // PM helper routines
#include "linklist.h"           // linked list helper routines

// SOM headers which don't crash with prec. header files
#pragma hdrstop
#include "xfdesk.h"

// headers in /main
#include "dlgids.h"             // all the IDs that are shared with NLS
#include "common.h"             // the majestic XFolder include file

#include "except.h"             // XFolder exception handling

// other SOM headers

// finally, our own header file
#include "notebook.h"           // generic XFolder notebook handling

// root of linked list of opened notebook pages
PNOTEBOOKLISTITEM pnbliFirst = NULL;

// mutex semaphore for that list
HMTX hmtxNotebooks = NULLHANDLE;

/*
 *@@ fnwpNotebookCommon:
 *      this is the common notebook window procedure which is
 *      always set if you use ntbInsertPage to insert notebook
 *      pages. This function will analyze all incoming messages
 *      and call the corresponding callback functions which you
 *      passed to ntbInsertPage.
 *
 *      This function installs exception handling during message
 *      processing, i.e. including your callbacks, using
 *      excHandlerLoud in xfdesk.c.
 */

MRESULT EXPENTRY fnwpNotebookCommon(HWND hwndDlg, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT             mrc = NULL;
    BOOL                fSemOwned = FALSE;

    TRY_LOUD(excpt1)   // install "loud" exception handler (except.h)
    {
        PCREATENOTEBOOKPAGE pcnbp = NULL;

        // get the notebook creation struct, which was passed
        // to ntbInsertPage, from the window words
        pcnbp = (PCREATENOTEBOOKPAGE)WinQueryWindowULong(hwndDlg, QWL_USER);

        // go!
        switch(msg)
        {
            /*
             * WM_INITDLG:
             *
             */

            case WM_INITDLG: {
                // store the WM_INITDLG parameter in the
                // window words; the CREATENOTEBOOKPAGE
                // structure is passed to us by ntbInsertPage
                // as a creation parameter in mp2
                pcnbp = (PCREATENOTEBOOKPAGE)mp2;
                WinSetWindowULong(hwndDlg, QWL_USER, (ULONG)pcnbp);

                // make Warp 4 notebook buttons and move controls
                winhAssertWarp4Notebook(hwndDlg,
                        100,         // ID threshold
                    WARP4_NOTEBOOK_OFFSET);   // move other controls offset (common.h)

                // store the dlg hwnd in notebook structure
                pcnbp->hwndPage = hwndDlg;
                // call "initialize" callback
                if (pcnbp->pfncbInitPage)
                    (*(pcnbp->pfncbInitPage))(pcnbp, CBI_INIT | CBI_SET | CBI_ENABLE);

                // timer desired?
                if (pcnbp->ulTimer) {
                    WinStartTimer(WinQueryAnchorBlock(hwndDlg),
                        hwndDlg, 1, pcnbp->ulTimer);
                    // call timer callback already now;
                    // let's not wait until the first downrun
                    if (pcnbp->pfncbTimer)
                        (*(pcnbp->pfncbTimer))(pcnbp, 1);
                }

                mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);

            break; }

            /*
             * WM_CONTROL:
             *
             */

            case WM_CONTROL: {
                // identify the source of the msg
                USHORT usItemID = SHORT1FROMMP(mp1),
                       usNotifyCode = SHORT2FROMMP(mp1);

                // "item changed" callback defined?
                if (pcnbp->pfncbItemChanged)
                {
                    BOOL fValid = FALSE,
                         fSpinButton = FALSE;
                    ULONG ulExtra = -1;
                    MRESULT cbmrc = (MPARAM)-1;

                    HWND hwndControl = WinWindowFromID(hwndDlg, usItemID);
                    CHAR szClassName[100];

                    // we identify the control by querying
                    // its class; the standard PM classes have
                    // wicked "#xxxx" classnames; when we find
                    // a supported control, we filter out messages
                    // which are not of interest, and call the
                    // callbacks only for these messages
                    if (WinQueryClassName(hwndControl,
                            sizeof(szClassName), szClassName))
                    {
                        // checkbox? radio button?
                        if (strcmp(szClassName, "#3") == 0) {
                            if (usNotifyCode == BN_CLICKED) {
                                // code for WC_BUTTON...
                                ULONG ulStyle = WinQueryWindowULong(hwndControl,
                                                        QWL_STYLE);
                                if (ulStyle & BS_PRIMARYSTYLES)
                                        // == 0x000F; BS_PUSHBUTTON has 0x0,
                                        // so we exclude pushbuttons here
                                {
                                    // for checkboxes and radiobuttons, pass
                                    // the new check state to the callback
                                    ulExtra = (ULONG)WinSendMsg(hwndControl,
                                                BM_QUERYCHECK,
                                                MPNULL,
                                                MPNULL);
                                    fValid = TRUE;
                                }
                            }
                        // spinbutton?
                        } else if (strcmp(szClassName, "#32") == 0) {
                            if (    (usNotifyCode == SPBN_UPARROW)
                                 || (usNotifyCode == SPBN_DOWNARROW)
                                 || (usNotifyCode == SPBN_CHANGE)   // manual input
                               )
                            {
                                // for spinbuttons, pass the new spbn
                                // value in ulExtra
                                WinSendDlgItemMsg(hwndDlg, usItemID,
                                      SPBM_QUERYVALUE,
                                      (MPARAM)&ulExtra,
                                      MPFROM2SHORT(0, SPBQ_UPDATEIFVALID));
                                fValid = TRUE;
                            }
                        // listbox?
                        } else if (strcmp(szClassName, "#7") == 0) {
                            if (usNotifyCode == LN_SELECT)
                                fValid = TRUE;
                        // combobox?
                        } else if (strcmp(szClassName, "#2") == 0) {
                            if (usNotifyCode == LN_SELECT)
                                fValid = TRUE;
                        // entry field?
                        } else if (strcmp(szClassName, "#6") == 0) {
                            if (usNotifyCode == EN_KILLFOCUS) {
                                fValid = TRUE;
                            }
                        }
                    }

                    if (fValid) {
                        // "important" message found:
                        // call "item changed" callback
                        cbmrc = (*(pcnbp->pfncbItemChanged))
                                            (pcnbp,
                                            usItemID,
                                            usNotifyCode,
                                            ulExtra);
                    }
                }
            break; }

            /*
             * WM_COMMAND:
             *      for buttons, we also use the callback
             *      for "item changed"; the difference
             *      between WM_CONTROL and WM_COMMAND has
             *      never made sense to me
             */

            case WM_COMMAND: {
                USHORT usItemID = SHORT1FROMMP(mp1);

                // call "item changed" callback
                if (pcnbp->pfncbItemChanged) {
                    (*(pcnbp->pfncbItemChanged))
                            (pcnbp,
                            usItemID,
                            0, (ULONG)mp2);
                }
            break; }

            /*
             * WM_HELP:
             *      results from the "Help" button or
             *      from pressing F1; we display help
             *      depending on the control which has
             *      the focus and depending on the data
             *      which has been passed to us
             */

            case WM_HELP: {
                 // this routine checks the current focus
                 // and retrieves the corresponding help
                 // panel; this only works with XFolder
                 // dialog IDs, on which the calculations
                 // are based
                 ntbDisplayFocusHelp(pcnbp->somSelf,
                                    pcnbp->ulDefaultHelpPanel);
            break; }

            /*
             * WM_WINDOWPOSCHANGED:
             *      cheap trick: this msg is posted when
             *      the user switches to a different notebook
             *      page. Since every notebook page is really
             *      a separate dialog window, PM simulates
             *      switching notebook pages by showing or
             *      hiding the various dialog windows.
             *      We will call the INIT callback with a
             *      show/hide flag then.
             */

            case WM_WINDOWPOSCHANGED: {
                PSWP pswp = (PSWP)mp1;

                if (pswp->fl & SWP_SHOW) {
                    // notebook page is being shown:
                    // call "initialize" callback
                    if (pcnbp->pfncbInitPage) {
                        WinEnableWindowUpdate(hwndDlg, FALSE);
                        pcnbp->fPageVisible = TRUE;
                        (*(pcnbp->pfncbInitPage))(pcnbp,
                                CBI_SHOW | CBI_ENABLE);
                                // we also set the ENABLE flag so
                                // that the callback can re-enable
                                // controls when the page is being
                                // turned to
                        WinEnableWindowUpdate(hwndDlg, TRUE);
                    }
                } else if (pswp->fl & SWP_HIDE) {
                    // notebook page is being hidden:
                    // call "initialize" callback
                    if (pcnbp->pfncbInitPage) {
                        pcnbp->fPageVisible = FALSE;
                        (*(pcnbp->pfncbInitPage))(pcnbp, CBI_HIDE);
                    }
                }

                // call default
                mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
            break; }

            /*
             * WM_TIMER:
             *
             */

            case WM_TIMER: {
                if (pcnbp->pfncbTimer)
                    (*(pcnbp->pfncbTimer))(pcnbp, 1);
            break; }

            /*
             * WM_DESTROY:
             *      clean up the allocated structures
             */

            case WM_DESTROY: {
                // stop timer, if started
                if (pcnbp->ulTimer) {
                    WinStopTimer(WinQueryAnchorBlock(hwndDlg),
                        hwndDlg, 1);
                }

                // call INIT callback with CBI_DESTROY
                if (pcnbp->pfncbInitPage)
                    (*(pcnbp->pfncbInitPage))(pcnbp, CBI_DESTROY);

                // remove the NOTEBOOKLISTITEM from the
                // linked list of open notebook pages
                if (pcnbp->pnbli) {
                    fSemOwned = (DosRequestMutexSem(hmtxNotebooks, 4000) == NO_ERROR);
                    if (fSemOwned) {
                        lstRemoveItem((PLISTITEM*)&pnbliFirst, NULL,
                                (PLISTITEM)pcnbp->pnbli);
                        DosReleaseMutexSem(hmtxNotebooks);
                        fSemOwned = FALSE;
                    }
                }

                // free allocated memory
                if (pcnbp->pBackup)
                    free(pcnbp->pBackup);
                if (pcnbp->pBackup2)
                    free(pcnbp->pBackup2);
                free(pcnbp);

                mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
            break; }

            default:
                mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);

        } // end switch (msg)

        if (pcnbp)
            if (pcnbp->pfncbMessage)
                (*(pcnbp->pfncbMessage))(pcnbp, msg, mp1, mp2);
    }
    CATCH(excpt1) {
        if (fSemOwned) {
            DosReleaseMutexSem(hmtxNotebooks);
            fSemOwned = FALSE;
        }

        mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
    } END_CATCH;

    return (mrc);
}

/*
 *@@ ntbInsertPage:
 *      this function inserts the specified notebook page
 *      using the wpInsertSettingsPage function. However,
 *      this always uses fnwpNotebookCommon for the notebook's
 *      window procedure, which then calls the callbacks which
 *      you may specify in the CREATENOTEBOOKPAGE structure.
 *
 *      This function returns the return code of
 *      wpInsertSettingsPages.
 */

ULONG ntbInsertPage(PCREATENOTEBOOKPAGE pcnbp)
{
    BOOL            fSemOwned = FALSE;
    PAGEINFO        pi;
    ULONG           ulrc;

    memset(&pi, 0, sizeof(PAGEINFO));

    pi.cb                  = sizeof(PAGEINFO);
    pi.hwndPage            = NULLHANDLE;
    pi.pfnwp               = fnwpNotebookCommon;
    pi.resid               = pcnbp->hmod;
    pi.dlgid               = pcnbp->ulDlgID;
    pi.pCreateParams       = pcnbp;
    pi.usPageStyleFlags    = BKA_STATUSTEXTON |
                                    ((pcnbp->fMajorTab) ? BKA_MAJOR : BKA_MINOR);
    pi.usPageInsertFlags   = BKA_FIRST;
    pi.usSettingsFlags     = ((pcnbp->fEnumerate) ? SETTINGS_PAGE_NUMBERS : 0);
                                    // enumerate in status line
    pi.pszName             = pcnbp->pszName;

    pi.pszHelpLibraryName  = cmnQueryHelpLibrary();
    pi.idDefaultHelpPanel  = pcnbp->ulDefaultHelpPanel;

    TRY_QUIET(excpt1)
    {
        ulrc = _wpInsertSettingsPage(pcnbp->somSelf, pcnbp->hwndNotebook, &pi);

        if (ulrc) {
            PNOTEBOOKLISTITEM pnbliNew = malloc(sizeof(NOTEBOOKLISTITEM));
            pnbliNew->pcnbp = pcnbp;
            if (hmtxNotebooks == NULLHANDLE)
                // first call: create mutex semaphore
                DosCreateMutexSem(NULL,         // unnamed
                                &hmtxNotebooks,
                                0,              // unshared
                                FALSE);         // unowned

            fSemOwned = (DosRequestMutexSem(hmtxNotebooks, 4000) == NO_ERROR);
            if (fSemOwned) {
                lstAppendItem((PLISTITEM*)&pnbliFirst, NULL,
                            (PLISTITEM)pnbliNew);
            }
            // store new item in structure, so we can easily
            // find it upon WM_DESTROY
            pcnbp->pnbli = (PVOID)pnbliNew;
        }
    }
    CATCH(excpt1) { } END_CATCH;

    if (fSemOwned) {
        DosReleaseMutexSem(hmtxNotebooks);
        fSemOwned = FALSE;
    }

    return (ulrc);
}

/*
 *@@ ntbQueryOpenPages:
 *      this function returns the CREATENOTEBOOKPAGE
 *      structures for currently open notebook pages, which
 *      are maintained by ntbInsertPage and fnwpNotebookCommon.
 *      This way you can iterate over all open pages and call
 *      the callbacks of certain pages to have pages updated,
 *      if necessary.
 *
 *      If pcnpb == NULL, the first open page is returned;
 *      otherwise, the page which follows after pcnbp in
 *      our internal list.
 *
 *      In order to identify pages properly, you should always
 *      set unique ulPageID identifiers when inserting notebook
 *      pages and evaluate somSelf, if these are instance pages.
 */

PCREATENOTEBOOKPAGE ntbQueryOpenPages(PCREATENOTEBOOKPAGE pcnbp)
{
    PNOTEBOOKLISTITEM   pItem = pnbliFirst;
    BOOL                fSemOwned = FALSE;

    TRY_QUIET(excpt1)
    {
        fSemOwned = (DosRequestMutexSem(hmtxNotebooks, 4000) == NO_ERROR);
        if (fSemOwned)
        {
            if (pcnbp) {
                // page given as param: look for this page
                // and return the following in the list
                while (pItem)
                    if (pItem->pcnbp == pcnbp) {
                        pItem = pItem->pNext;
                        break;
                    }
                    else
                        pItem = pItem->pNext;
            } // else: pItem == first item on list
        }
    }
    CATCH(excpt1) { } END_CATCH;

    if (fSemOwned) {
        DosReleaseMutexSem(hmtxNotebooks);
        fSemOwned = FALSE;
    }

    if (pItem)
        return (pItem->pcnbp);
    else
        return (NULL);
}

/*
 *@@ ntbUpdateVisiblePage:
 *      this will go thru all currently open notebook
 *      pages and update a page (by calling the INIT
 *      callback with CBI_SET | CBI_ENABLE) if it
 *      matches the specified criteria, which are:
 *      --  somSelf         must match somSelf for notebook
 *      --  ulPageID        must match ulPageID for notebook
 *
 *      If any of these criteria is NULL, it's considered
 *      a "don't care", i.e. it is not checked for.
 *      A page is only updated if it's currently visible,
 *      i.e. turned to in an open settings notebook.
 *      Returns the number of pages that were updated.
 */

ULONG ntbUpdateVisiblePage(WPObject *somSelf, ULONG ulPageID)
{
    ULONG ulrc = 0;
    PCREATENOTEBOOKPAGE pcnbp = NULL;
    while (pcnbp = ntbQueryOpenPages(pcnbp)) {
        if (pcnbp->fPageVisible)
            if (    (   (ulPageID == 0)     // don't care?
                     || (pcnbp->ulPageID == ulPageID)
                    )
                 && (   (somSelf == NULL)   // don't care?
                     || (pcnbp->somSelf == somSelf)
                    )
               )
            {
                if (pcnbp->pfncbInitPage)
                    // enable/disable items on visible page
                    (*(pcnbp->pfncbInitPage))(pcnbp, CBI_SET | CBI_ENABLE);
                ulrc++;
            }
    }
    return (ulrc);
}

/*
 *@@ ntbDisplayFocusHelp:
 *      this is used from all kinds of settings dlg
 *      procs to display a help panel according to
 *      the dlg item which currently has the focus;
 *      if no matching help panel is found,
 *      ulPanelIfNotFound is displayed instead.
 *      Here we query the dlg ID of the item which
 *      currently has the focus, subtract ID_XSDI_FIRST
 *      (see dlgids.h) and add the ID of the first help
 *      panel for these help pages.
 *      This only works for XFolder dialogs where the dlg
 *      items have ascending IDs with correlated help panels.
 */

BOOL ntbDisplayFocusHelp(WPObject *somSelf,     // in: input for wpDisplayHelp
                         ULONG ulPanelIfNotFound)   // in: subsidiary help panel
{
    BOOL brc = TRUE;
    PSZ pszHelpLibrary = cmnQueryHelpLibrary();

    HWND hwndFocus = WinQueryFocus(HWND_DESKTOP);
    if (hwndFocus) {
        // WPObject *pHelpSomSelf = _wpclsQueryActiveDesktop(_WPDesktop);

        if (somSelf) {
            USHORT idItem = WinQueryWindowUShort(hwndFocus, QWS_ID);
            #ifdef DEBUG_LANGCODES
                _Pmpf(( "ntbDisplayFocusHelp: lib %s, idItem: %d ulPanel: %d", pszHelpLibrary, idItem, idItem+ID_XSH_FIRST-ID_XSDI_FIRST ));
            #endif
            if (!_wpDisplayHelp(somSelf, (idItem-ID_XSDI_FIRST+ID_XSH_FIRST),
                pszHelpLibrary))
            {
                #ifdef DEBUG_LANGCODES
                    _Pmpf(( "  not found, displaying superpanel %d", ulPanelIfNotFound ));
                #endif
                if (!_wpDisplayHelp(somSelf, ulPanelIfNotFound,
                    pszHelpLibrary))
                {
                    cmnMessageBoxMsg(HWND_DESKTOP, 104, 134, MB_OK);
                    brc = FALSE;
                }
            }
        }
    }
    return (brc);
}


