NT4/private/windows/win4help/winhelp/history.c
2020-09-30 17:12:29 +02:00

770 lines
18 KiB
C

/*****************************************************************************
* *
* HISTORY.C *
* *
* Copyright (C) Microsoft Corporation 1990. *
* All Rights reserved. *
* *
******************************************************************************
* *
* Module Intent *
* App side of the History list. *
* Some of these functions are called by Nav. This may change some day. *
* *
*****************************************************************************/
#include "help.h"
#pragma hdrstop
#include "inc\hwproc.h"
#include "inc\winclass.h"
#define PATH_LB 1 // Child window IDs
#define PATH_STATIC 2
#define PREPEND TRUE
#define STRIP FALSE
// Since we can't say "tlp = tlpNil", we let this suffice.
#define FIsNilQtlp(qtlp) (vaNil == (qtlp) ->va.dword)
#define SetNilQtlp(qtlp) ((qtlp) ->va.dword = vaNil)
// History stack element
typedef struct {
TLP tlp;
WORD ifm;
VA va; // for duplicate removal
CTX ctx; // context number (if non-zero)
HLOCAL hTitle;
} HSE;
HSTACK hstackHistory; // also used by hwproc.c for toggling
static BOOL fHistoryInit;
static LRESULT cSavedMax;
static BOOL fHistoryMagic;
static WRECT rcHist;
static int iTopic;
INLINE void static STDCALL FillPathLB(void);
INLINE static RC STDCALL RcInsertTopic(PCSTR psz);
static RC STDCALL RcModifyString(int i, PCSTR pszFileRoot, BOOL fAdd);
INLINE RC static STDCALL RcMungeList(int, int);
void static STDCALL SetPathRedraw(BOOL);
void STDCALL FreeTitle(QV); // stack callback function
/* FUNCTIONS */
/***************************************************************************\
*
- Function: HistoryProc( hWnd, msg, p1, p2 )
-
* Purpose: History Window proc.
*
* Notes:
*
\***************************************************************************/
LRESULT EXPORT HistoryProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
TLPHELP tlphelp;
FM fm;
switch(msg) {
#if defined(BIDI_MULT) // jgross
case WM_LANGUAGE:
DefWindowProc(hWnd, imsz, wParam, lParam | LANG_NO_PASSON);
if (wParam != -1) {
extern BOOL RtoL;
RtoL = (wParam == Arabic) || (wParam == Hebrew);
SendMessage(hwndList, LB_SETALIGN, RtoL ? LBS_RTL : 0, NULL);
}
break;
#endif
case WM_INITDIALOG:
ASSERT(hfontDefault);
SendMessage(hwndList, WM_SETFONT, (WPARAM) hfontDefault, FALSE);
break;
case HWM_LBTWIDDLE:
// We mess with the listbox selection when the user selects the
// history button, but not if they just mouse click on the history
// window. This scheme avoids the need to use the WM_MOUSEACTIVATE
// message.
SendMessage(hwndList, LB_SETCURSEL, 1, 0L);
SendMessage(hwndList, LB_SETTOPINDEX, 0, 0L);
break;
case WM_ACTIVATE:
if (wParam == WA_INACTIVE)
SendMessage(hwndList, LB_SETCURSEL, (WPARAM) -1, 0L);
break;
case WM_SIZE:
if (wParam != SIZEICONIC)
MoveWindow(hwndList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
break;
case WM_COMMAND:
if (LOWORD(wParam) == PATH_LB) {
switch(HIWORD(wParam)) {
case LBN_DBLCLK:
iTopic = (int) SendMessage(hwndList, LB_GETCURSEL, 0, 0);
if (iTopic == LB_ERR)
break;
RcGetIthTopic(iTopic, &tlphelp.tlp, &fm);
tlphelp.cb = sizeof(TLPHELP);
fHistoryMagic = TRUE;
if (!FWinHelp(PszFromGh(fm), cmdTLP, (DWORD) &tlphelp))
Error(wERRS_OOM, wERRA_RETURN);
break;
default:
goto defwinproc;
break;
}
}
break;
case WM_VKEYTOITEM:
if (LOWORD(wParam) == VK_RETURN)
PostMessage(hwnd, WM_COMMAND, MAKELONG(PATH_LB, LBN_DBLCLK), 0);
else if (LOWORD(wParam) == VK_ESCAPE)
PostMessage(hwnd, WM_CLOSE, 0, 0);
goto defwinproc;
break;
case WM_SETFOCUS:
SetFocus(hwndList);
break;
case WM_DESTROY:
hwndHistory = NULL;
hwndList = NULL;
WriteWinPosHwnd(hwnd, FALSE, WCH_HISTORY);
break;
default:
defwinproc:
return DefWindowProc(hwnd, msg, wParam, lParam);
break;
}
return TRUE;
}
/***************************************************************************\
*
- Function: FCallPath( hIns )
-
* Purpose: Create path windows and initialize listbox list.
*
* ASSUMES
*
* args IN: hIns - instance handle
*
* globals IN: rcHist.left, rcHist.top, rcHist.right, rcHist.bottom
* fHistoryInit
*
* state IN:
*
* PROMISES
* returns: TRUE on success; FALSE on failure (OOM is only case)
*
* globals OUT: hwndHistory, hwndList
*
* state OUT:
*
* Side Effects:
*
* Bugs: Move caption to string table.
* Notes:
*
\***************************************************************************/
BOOL STDCALL FCallPath(void)
{
BOOL fInitialized = FALSE;
if (!fInitialized) {
ReadWinRect(&rcHist, WCH_HISTORY, NULL);
fInitialized = TRUE;
}
// Create Path window
hwndHistory = CreateWindow(pchPath,
GetStringResource(sidHistoryCaption),
WS_CAPTION | WS_THICKFRAME | WS_SYSMENU,
rcHist.left, rcHist.top, rcHist.cx, rcHist.cy,
ahwnd[MAIN_HWND].hwndParent,
NULL, // window menu handle
hInsNow, // instance handle
NULL); // create parameters
if (hwndHistory == NULL)
return FALSE;
// Create the path Listbox.
hwndList = CreateWindow((LPSTR) WC_LISTBOX,
NULL, // window caption
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
LBS_NOTIFY | LBS_NOINTEGRALHEIGHT |
LBS_WANTKEYBOARDINPUT,
0, 0, 0, 0,
hwndHistory, (HMENU) PATH_LB, hInsNow, NULL);
if (!hwndList) {
DestroyWindow(hwndHistory);
return FALSE;
}
#if defined(BIDI_MULT) // jgross - determine if vert scroll bars go on
// the left or right
{
extern BOOL IsSetup, RtoL;
if (IsSetup)
EnableMenuItem(GetSystemMenu(hwndPath, FALSE),
8, MF_BYPOSITION | MF_GRAYED);
MakeScrollBarsRtoL(hwndList, RtoL, TRUE);
SendMessage(hwndList, LB_SETALIGN, RtoL ? LBS_RTL : 0, NULL);
}
#endif
// Use a small font in the list box
SendMessage(hwndList, WM_SETFONT, (WPARAM) HfontGetSmallSysFont(), FALSE);
FillPathLB();
// Show the Path window.
ShowWindow(hwndHistory, SW_SHOW);
return TRUE;
}
/***************************************************************************\
*
- Function: RcHistoryInit( c )
-
* Purpose: Initialize History list
*
* ASSUMES
*
* args IN: c - stack size
*
* globals IN: hstackHistory - our HSTACK
* pszCaption - the name of the win.ini help section
*
* PROMISES
*
* returns: rcSuccess - successful initialization
* rcOutOfMemory - out of memory
*
* globals OUT: path - initialized path struct
* fHistoryInit - TRUE on success
* fDisplayTopicOnce -
*
* Notes: someday we may want to save path in a file (WINHELP.BMK?)
*
\***************************************************************************/
RC STDCALL RcHistoryInit(int c)
{
ASSERT(!fHistoryInit);
cSavedMax = (c <= 0) ? DEFAULT_HISTORY : c;
if (RcInitStack(&hstackHistory, cSavedMax, sizeof(HSE), FreeTitle) ==
rcSuccess)
fHistoryInit = TRUE;
// We return success on failure so we don't try again
return rcSuccess;
}
/***************************************************************************\
*
- Function: RcHistoryPush( tlpOld, va, pszTitle, fm )
-
* Purpose: Push a new topic onto path stack. This happens when we
* leave a topic (i.e. current topic not on path list.)
*
* ASSUMES
*
* args IN: tlpOld - TLP of old topic (so we can update it)
* va - va of first FCL in topic (for unique model)
* pszTitle - topic title
* fm - fm of helpfile
*
* globals IN: szSearchNoTitle -
* fHistoryInit - if FALSE, exit with error code
*
* PROMISES
*
* returns: rcSuccess -
* rcOutOfMemory -
* rcFailure - history stack wasn't initalized successfully
*
* globals OUT: path
*
* state OUT: FMT unlocked
*
* +++
*
* Method: If the TLP of the topic we're leaving is valid, this means
* a Push has failed previously. Otherwise, update it with
* tlpOld.
* Do all the things that can fail if OOM: get title, ifm,
* add string to listbox, munge other strings in listbox.
* If any of these fail, abort.
* If all this succeeded, push new info (va, pszTitle, ifm)
* onto stack, leaving tlp invalid.
*
* Notes: Might want to optimize by storing all the info in one hse.
* The cost would be that RcGetIthTopic() etc get hairier.
* But this happens more often...
*
\***************************************************************************/
RC STDCALL RcHistoryPush(TLP tlpOld, VA va, PSTR pszTitle, FM fm)
{
HSE hse;
static int posOldFm = 0;
RC rc = rcOutOfMemory;
int posCurFm;
// char szUntitled[100];
ASSERT(fHistoryInit);
if (fHistoryMagic) {
/*
* If this call is the result of a history jump, then all we do is
* check to see if our filename has changed, and update our list
* accordingly. We do not record the jump.
*/
posCurFm = GetFmIndex(fm);
fHistoryMagic = FALSE;
if (posOldFm != posCurFm) {
SetPathRedraw(FALSE);
RcMungeList(posOldFm, posCurFm);
SetPathRedraw(TRUE);
posOldFm = posCurFm;
}
SetFocus(hwndHistory); // keep focus on history window
if (iTopic != LB_ERR && iTopic < (SendMessage(hwndList, LB_GETCOUNT, 0, 0) - 1))
SendMessage(hwndList, LB_SETCURSEL, ++iTopic, 0);
return rcSuccess;
}
// Only main window history jumps are recorded in history.
if (ahwnd[iCurWindow].hwndParent != ahwnd[MAIN_HWND].hwndParent)
return rcSuccess;
ASSERT(pszTitle);
#if 0
if (!pszTitle || !strlen(pszTitle)) {
if (!pszUntitled)
pszUntitled = LocalStrDup(GetStringResource(sidUntitled));
wsprintf(szUntitled, pszUntitled, QdeFromGh(HdeGetEnv())->top.mtop.lTopicNo);
pszTitle = szUntitled;
}
#endif
if (va.dword == vaNil)
return rcSuccess;
hse.va = va;
hse.tlp = tlpOld;
SetPathRedraw(FALSE);
if (!(hse.ifm = (WORD) GetFmIndex(fm))) {
goto egress;
}
if (!(hse.hTitle = LocalStrDup(pszTitle)))
goto egress;
// Munge the list if we've changed files.
// The test for posOldFm is for the case where the stack is empty
// because a push has failed and the stack is empty.
if (posOldFm != hse.ifm && posOldFm) {
if (rcSuccess != (rc = RcMungeList((WORD) posOldFm, hse.ifm)))
goto egress;
}
posOldFm = hse.ifm;
if ((rc = RcInsertTopic(pszTitle)) != rcSuccess) {
FreeGh(hse.hTitle);
goto egress;
}
RcPushStack(hstackHistory, &hse);
// if (hwndList)
// SendMessage(hwndList, LB_SETTOPINDEX, 0, 0L);
egress:
SetPathRedraw(TRUE);
return rc;
}
/***************************************************************************\
*
- Function: RcGetIthTopic( i, qtlp, qfm)
-
* Purpose: Get the data associated with the Ith topic in stack.
*
* ASSUMES
*
* args IN: i - index of topic
* qtlp - pointer to user's TLP
* qfm - pointer to user's FM (don't UnlockFmt()
* until you're done with it)
*
* globals IN: hstackHistory
*
* PROMISES
*
* returns: rcSuccess
* rc
*
* args OUT: *qtlp - copy of ith topic's TLP
* *qfm - copy of Ith topic's FM
*
* state OUT: FMT is locked
*
* Notes: The meaning of the index 'I' is the reverse of the stack indices:
* 0 is most recently pushed hse.
*
*
\***************************************************************************/
RC STDCALL RcGetIthTopic(int i, QTLP qtlp, FM* qfm)
{
HSE hse;
int iMax = CElementsStack(hstackHistory);
ASSERT(i < iMax);
i = iMax - i - 1;
ENSURE(RcGetIthStack(hstackHistory, i, &hse), rcSuccess);
*qtlp = hse.tlp;
*qfm = GetFmPtr(hse.ifm);
return rcSuccess;
}
/***************************************************************************\
*
- Function: FreeTitle( qv )
-
* Purpose: Callback function for stack: frees hTitle.
*
* ASSUMES
*
* args IN: qv - points to the HSE that needs title freed
*
* PROMISES
*
* args OUT: qv->hTitle is free (or was NULL)
*
\***************************************************************************/
void STDCALL FreeTitle(QV qv)
{
HLOCAL hTitle = ((HSE *)qv)->hTitle;
if (hTitle )
FreeLh(hTitle);
}
/***************************************************************************\
*
- Function: RcMungeList( posOldFm, posNewFm )
-
* Purpose: Update (i.e. strip or prepend) file root prefixes in
* listbox list because current help file has changed.
*
* ASSUMES
*
* args IN: qpe - initially points to 0th in rgpe array
* posOldFm - old ifm; prepend file names on match
* posNewFm - new ifm; strip file names on match
*
* globals IN: hwndList
*
* PROMISES
*
* returns: rcSuccess -
* rcFailure - weird LB failure
* rcOutOfMemory - out of memory in LB
*
* state OUT: FMT is locked
*
* +++
*
* Method: Munging takes place before the current topic is added to
* the list or pushed onto the stack.
*
\***************************************************************************/
INLINE static RC STDCALL RcMungeList(int posOldFm, int posNewFm)
{
HSE hse;
int i, iMax; // LB indices
char szRoot[MAX_PATH];
RC rc = rcSuccess;
if (hwndList) {
GetFmParts(GetFmPtr(posOldFm), szRoot, PARTBASE);
iMax = CElementsStack(hstackHistory);
for (i = 0; i < iMax; i++) {
RcGetIthStack(hstackHistory, (iMax - 1 - i), &hse);
if (hse.ifm == posOldFm)
rc = RcModifyString(i, szRoot, PREPEND);
else if (hse.ifm == posNewFm)
rc = RcModifyString(i, szRoot, STRIP);
if (rcSuccess != rc)
break;
}
}
return rc;
}
/***************************************************************************\
*
- Function: RcModifyString( i, pszFileRoot, fAdd )
-
* Purpose: Prepend root file name to, or strip it from, a listbox entry.
* e.g.: "Commands" -> "CALC:Commands"
* or "WINHELP:Index" -> "Index"
*
* ASSUMES
*
* args IN: i - index into listbox of string to modify
* pszFileRoot - filename to prepend if fAdd
* fAdd - PREPEND: prepend the root name from *fm
* STRIP: strip the root name from the string
*
* globals IN: path, hwndList valid
*
* state IN: Assume i'th string has file root iff it should
*
* PROMISES
*
* returns: rcSuccess - it worked
* rcOutOfMemory - out of memory (LB_ERRSPACE)
* rcFailure - something else failed
*
* globals OUT: hwndList listbox string modified
*
\***************************************************************************/
static RC STDCALL RcModifyString(int i, PCSTR pszFileRoot, BOOL fAdd)
{
char szBuf[512];
PSTR psz;
LRESULT result;
if (fAdd == PREPEND) {
strcpy(szBuf, pszFileRoot);
psz = szBuf + strlen(szBuf);
*psz++ = ':';
}
else
psz = szBuf;
if (SendMessage(hwndList, LB_GETTEXT, i, (LPARAM) psz) == LB_ERR)
return rcFailure;
if (fAdd == PREPEND)
psz = szBuf;
else {
psz = StrChrDBCS(psz, ':');
if (psz)
psz++;
}
// Insert before deletion so list won't get as screwed up on failure.
result = SendMessage(hwndList, LB_INSERTSTRING, i, (LPARAM) psz);
if (result == LB_ERRSPACE)
return rcOutOfMemory;
else if (result == LB_ERR)
return rcFailure;
if (SendMessage(hwndList, LB_DELETESTRING, i + 1, 0) == LB_ERR)
return rcFailure; // bad news: list screwed up
return rcSuccess;
}
/***************************************************************************\
*
- Function: RcInsertTopic( sz )
-
* Purpose: Insert a new topic name at the top of the listbox.
* If the stack is full, delete the oldest entry.
*
* ASSUMES
*
* args IN: sz - the string to insert
*
* globals IN: hwndList - path listbox
* cSavedMax - max count of topics we can save
* path.cpe-1st element is one to remove (?)
*
* PROMISES
*
* returns: rcSuccess -
* rcFailure - weird LB error
* rcOutOfMemory - out of memory in LB operation
*
* globals OUT: hwndList listbox contains another string
*
\***************************************************************************/
INLINE static RC STDCALL RcInsertTopic(PCSTR psz)
{
LRESULT result;
if (hwndList) {
result = SendMessage(hwndList, LB_GETCOUNT, 0, 0L);
if (result == LB_ERR)
return rcFailure;
else if (cSavedMax == result) {
result = SendMessage(hwndList, LB_DELETESTRING, cSavedMax - 1, 0);
if (result == LB_ERR) {
return rcFailure;
}
}
result = SendMessage(hwndList, LB_INSERTSTRING, 0, (LPARAM) psz);
if (result == LB_ERRSPACE)
return rcOutOfMemory;
else if (result == LB_ERR)
return rcFailure;
}
return rcSuccess;
}
/***************************************************************************\
*
- Function: FillPathLB()
-
* Purpose: Fill the listbox with topic titles and file root names
* from the path stack.
*
* Method:
*
* ASSUMES
*
* globals IN: path, hwndList
*
* state IN:
*
* PROMISES
*
* globals OUT: listbox is full of stuff
*
* Side Effects:
*
* Bugs:
*
* Notes:
*
\***************************************************************************/
INLINE void static STDCALL FillPathLB()
{
int ifm;
HSE hse;
PSTR psz;
int i, iMax;
char szTitle[MAX_TOPIC_TITLE];
iMax = (int) CElementsStack(hstackHistory);
if (iMax == 0)
return;
Ensure(RcTopStack(hstackHistory, &hse), rcSuccess);
ifm = hse.ifm;
for (i = 0; i < iMax; i++) {
Ensure(RcGetIthStack(hstackHistory, i, &hse), rcSuccess);
if (hse.ifm == ifm) {
if (hse.hTitle)
strcpy(szTitle, PszFromGh(hse.hTitle));
}
else {
GetFmParts(GetFmPtr(hse.ifm), szTitle, PARTBASE);
psz = szTitle + strlen(szTitle);
*psz++ = ':';
if (hse.hTitle)
strcpy(psz, PszFromGh(hse.hTitle));
}
SendMessage(hwndList, LB_INSERTSTRING, 0, (LPARAM) szTitle);
}
}
/***************************************************************************\
*
- Function: SetPathRedraw( f )
-
* Purpose: Set or reset redraw of the history listbox.
*
* ASSUMES
*
* args IN: f - TRUE means turn redraw on; FALSE means turn it off
*
* globals IN: hwndList - needn't be valid: we check
*
* PROMISES
*
* globals OUT: hwndList - redraw affected as described
*
\***************************************************************************/
void static STDCALL SetPathRedraw(BOOL fRedraw)
{
if (hwndList) {
SendMessage(hwndList, WM_SETREDRAW, fRedraw, 0L);
if (fRedraw) {
InvalidateRect(hwndList, NULL, TRUE);
UpdateWindow(hwndList);
}
}
}