mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1035 lines
30 KiB
1035 lines
30 KiB
/*****************************************************************************
|
|
*
|
|
* frlayout.c
|
|
*
|
|
* Copyright (C) Microsoft Corporation 1990.
|
|
* All Rights reserved.
|
|
*
|
|
******************************************************************************
|
|
*
|
|
* This contains the code which handles laying out, scrolling, and
|
|
* discarding FCs.
|
|
*
|
|
* The layout code is responsible for maintaining the chain of FCMs which
|
|
* represent the layout on the screen. Only visible FCs are stored- as soon
|
|
* as an FC is scrolled off the screen, it is discarded.
|
|
*
|
|
* The layout code maintains a number of variables in the DE:
|
|
* TLP tlp; This is stores the current layout position as
|
|
* two numbers: the FCL corresponding to the FC
|
|
* at the top of the screen, and a long
|
|
* which is proportional to the percentage of
|
|
* the FC which is scrolled off the screen.
|
|
* MLI mli; The MLI contains two flags:
|
|
* fLayoutAtTop is true if the layout cannot be
|
|
* scrolled any further up.
|
|
* fLayoutAtBottom is true if the layout cannot
|
|
* be scrolled any further down.
|
|
* xScrolled; The number of pixels the layout has been scrolled
|
|
* to the right.
|
|
* xScrollMax; The maximum amount that the layout can be
|
|
* scrolled to the right. Note that this number
|
|
* is recalculated everytime the user scrolls.
|
|
* If the user scrolls a wide layout off the
|
|
* screen, it is possible for xScrolled to be
|
|
* greater than xScrollMax.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "help.h"
|
|
|
|
#include "inc\frstuff.h"
|
|
|
|
INLINE INT STDCALL CbitSigBits(unsigned long);
|
|
INLINE static int STDCALL WFindThumb(int, int, VA, DWORD, QDE);
|
|
INLINE static VA STDCALL VaFromThumb(int, QDE);
|
|
INLINE static BOOL STDCALL FFindMatchRect(QDE, int, OBJRG, LPRECT);
|
|
static void STDCALL ShowOrHideWindowQde(QDE qde, BOOL fShow);
|
|
static VOID STDCALL ShowDEScrollBar(QDE qde, WORD wWhich, BOOL fShow);
|
|
|
|
#ifdef DEADCODE
|
|
static INT16 STDCALL WMulDiv(UINT16 wNum, DWORD ulNum, DWORD ulDen);
|
|
#else
|
|
#define WMulDiv MulDiv
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------------+
|
|
| LayoutDEAtQLA |
|
|
| |
|
|
| Layout for an arbitrary object, given its address. |
|
|
+----------------------------------------------------------------------------*/
|
|
|
|
void STDCALL LayoutDEAtQLA(QDE qde, QLA qla)
|
|
{
|
|
HFC hfc;
|
|
VA va;
|
|
OBJRG objrg;
|
|
int wErr;
|
|
int ifcm;
|
|
POINT dpt;
|
|
|
|
ASSERT(qde->wLayoutMagic == wLayMagicValue);
|
|
|
|
VAandOBJRGfromQLA(qla, qde, &va, &objrg);
|
|
|
|
ASSERT(va.dword != vaNil);
|
|
ASSERT(objrg != objrgNil);
|
|
|
|
hfc = HfcNear(qde, va, (TOP *) &qde->top, &wErr);
|
|
|
|
if (wErr == wERRS_NO && hfc == NULL)
|
|
wErr = wERRS_NOTOPIC;
|
|
|
|
if (wErr != wERRS_NO) {
|
|
Error(wErr, wERRA_RETURN);
|
|
return;
|
|
}
|
|
|
|
/* (kevynct)
|
|
* Fix for RAID bug 300:
|
|
*
|
|
* We do layout for jumps to invisible windows, since we need
|
|
* to set the TLP correctly. Another solution would be to
|
|
* create a counterpart to the TLP in the DE, set it here,
|
|
* then check for a nil de.tlp in LayoutDEAtTLP, and do a
|
|
* LayoutDEAtQLA in that case.
|
|
*/
|
|
#if 0
|
|
if (qde->rct.top >= qde->rct.bottom)
|
|
{
|
|
return;
|
|
}
|
|
#endif /* 0 */
|
|
|
|
AccessMRD(((QMRD)&qde->mrdFCM));
|
|
AccessMRD(((QMRD)&qde->mrdLSM));
|
|
qde->wStyleDraw = wStyleNil;
|
|
|
|
FreeLayout(qde);
|
|
|
|
if (QDE_TOPIC(qde)) {
|
|
|
|
if (qde->fHorScrollVis)
|
|
ShowDEScrollBar(qde, SB_HORZ, FALSE);
|
|
|
|
// Fake the layout into thinking we have a vertical scroll bar
|
|
|
|
GetClientRect(qde->hwnd, &qde->rct);
|
|
if (qde->deType == deNote)
|
|
qde->rct.bottom = 9999;
|
|
else {
|
|
qde->rct.right -= GetSystemMetrics(SM_CXVSCROLL);
|
|
qde->fVerScrollVis = TRUE;
|
|
}
|
|
}
|
|
|
|
ifcm = IfcmLayout(qde, hfc, 0, TRUE, FALSE);
|
|
|
|
qde->xScrolled = 0;
|
|
qde->xScrollMaxSoFar = 0;
|
|
DyFinishLayout(qde, 0, TRUE);
|
|
ReviseScrollBar(qde);
|
|
|
|
/*
|
|
* Scan the frames of the given FC, looking for the given object region.
|
|
* We special-case object region 0 of the first FC in the scrolling and
|
|
* non-scrolling regions to allow a jump to the top of a topic layout,
|
|
* instead of the first frame of that layout. This test assumes that
|
|
* the NSR precedes the SR in the topic file: we need to handle the case
|
|
* where the VA points to the topic FC (for a Help 3.0 file).
|
|
* We also do not scroll in the case that the topic fits in one window.
|
|
*
|
|
* If our LA points to a search match, we try to align the match rect
|
|
* one-sixth of the way down the topic window.
|
|
*/
|
|
|
|
dpt.x = dpt.y = 0;
|
|
|
|
{
|
|
RECT rct;
|
|
|
|
if ((qde->fHorScrollVis || qde->fVerScrollVis) &&
|
|
FFindMatchRect(qde, ifcm, objrg, &rct)) {
|
|
QFCM qfcm = QfcmFromIfcm(qde, ifcm);
|
|
|
|
dpt.x = -((qde->fHorScrollVis) ? qfcm->xPos + rct.left : 0);
|
|
dpt.y = -((qde->fVerScrollVis) ? qfcm->yPos + rct.top : 0);
|
|
dpt.x += max(0, (qde->rct.right - qde->rct.left) - (rct.right - rct.left))/2;
|
|
dpt.y += max(0, (qde->rct.bottom - qde->rct.top) / 6);
|
|
}
|
|
}
|
|
|
|
// The following covers same-topic jumps
|
|
|
|
if (objrg != (OBJRG) 0 ||
|
|
(va.dword > qde->top.mtop.vaNSR.dword &&
|
|
va.dword != qde->top.mtop.vaSR.dword)) {
|
|
QFCM qfcm;
|
|
QFR qfr;
|
|
int ifr;
|
|
|
|
if (qde->fHorScrollVis || qde->fVerScrollVis) {
|
|
|
|
/*
|
|
* Following used to grab the 1st FC in layout which can be the
|
|
* wrong one if DyFinishLayout has shifted the layout around to
|
|
* position FC's on the screen. We want to use ifcm layout that was
|
|
* set by the first call to IfcmLayout because that FC is the one
|
|
* with the frame we are trying to jump to. ifcm =
|
|
* IFooFirstMRD(((QMRD)&qde->mrdFCM));
|
|
*/
|
|
|
|
qfcm = (QFCM)QFooInMRD(((QMRD)&qde->mrdFCM), sizeof(FCM), ifcm);
|
|
qfr = (QFR)PtrFromGh(qfcm->hfr);
|
|
|
|
for (ifr = 0; ifr < qfcm->cfr; ifr++, qfr++) {
|
|
if (qfr->objrgFront == objrgNil || qfr->objrgLast == objrgNil)
|
|
continue;
|
|
if (qfr->objrgFront <= objrg && qfr->objrgLast >= objrg) {
|
|
dpt.x = -((qde->fHorScrollVis) ? qfcm->xPos + qfr->xPos : 0);
|
|
dpt.y = -((qde->fVerScrollVis) ? qfcm->yPos + qfr->yPos : 0);
|
|
if (-dpt.x < qde->rct.right)
|
|
dpt.x = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dpt.x != 0 || dpt.y != 0)
|
|
DptScrollLayout(qde, dpt);
|
|
|
|
DeAccessMRD(((QMRD)&qde->mrdLSM));
|
|
DeAccessMRD(((QMRD)&qde->mrdFCM));
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
| LayoutDEAtTLP(qde, tlp, fResize) |
|
|
| |
|
|
| Purpose: This routine destroys any previous layout and creates a new |
|
|
| layout at the given TLP. It does not redraw the layout area: |
|
|
| the caller must take care of doing that. |
|
|
| Params: fResize: This is true if we are just resizing the window. In |
|
|
| this case only, we try to maintain the current |
|
|
| horizontal scrolling. |
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void STDCALL LayoutDEAtTLP(QDE qde, TLP tlp, BOOL fResize)
|
|
{
|
|
HFC hfc;
|
|
IFCM ifcm;
|
|
QFCM qfcm;
|
|
int dy, xScrolledSav;
|
|
int wErr;
|
|
|
|
ASSERT(qde->wLayoutMagic == wLayMagicValue);
|
|
|
|
/*
|
|
* (kevynct) We do this to prevent a TLP layout occurring before a QLA
|
|
* layout.
|
|
*/
|
|
|
|
if (tlp.va.dword == vaNil)
|
|
return;
|
|
|
|
/* Fix for bug 57 (kevynct):
|
|
*
|
|
* The DE's TOP structure is now updated for every layout
|
|
* request, followed by the window size check.
|
|
*/
|
|
|
|
hfc = HfcNear(qde, tlp.va, (TOP *) &qde->top, &wErr);
|
|
|
|
if (wErr == wERRS_NO && hfc == NULL)
|
|
wErr = wERRS_NOTOPIC;
|
|
|
|
if (wErr != wERRS_NO) {
|
|
Error(wErr, wERRA_RETURN);
|
|
return;
|
|
}
|
|
|
|
if (QDE_TOPIC(qde)) {
|
|
if (qde->fHorScrollVis)
|
|
ShowDEScrollBar(qde, SB_HORZ, FALSE);
|
|
|
|
// Fake the layout into thinking we have a vertical scroll bar
|
|
|
|
if (qde->deType == deNote)
|
|
qde->rct.bottom = 9999;
|
|
else {
|
|
qde->rct.right -= GetSystemMetrics(SM_CXVSCROLL);
|
|
qde->fVerScrollVis = TRUE;
|
|
}
|
|
}
|
|
|
|
if (qde->rct.top >= qde->rct.bottom) {
|
|
qde->tlp = tlp;
|
|
FreeGh(hfc);
|
|
return;
|
|
}
|
|
|
|
AccessMRD(((QMRD)&qde->mrdFCM));
|
|
AccessMRD(((QMRD)&qde->mrdLSM));
|
|
qde->wStyleDraw = wStyleNil;
|
|
|
|
xScrolledSav = qde->xScrolled;
|
|
FreeLayout(qde);
|
|
|
|
ifcm = IfcmLayout(qde, hfc, 0, TRUE, FALSE);
|
|
|
|
qfcm = (QFCM)QFooInMRD(((QMRD)&qde->mrdFCM), sizeof(FCM), ifcm);
|
|
if (qfcm->va.dword == tlp.va.dword)
|
|
dy = ((int) - ((int)tlp.lScroll * (int)qfcm->dySize) / 0x10000L);
|
|
else
|
|
dy = 0;
|
|
|
|
if (!fResize) {
|
|
qde->xScrolled = 0;
|
|
|
|
// Initialize the width to 0 as we are relaying out. - maha
|
|
|
|
qde->xScrollMaxSoFar = 0;
|
|
}
|
|
DyFinishLayout(qde, dy, TRUE);
|
|
|
|
if (fResize)
|
|
qde->xScrolled = min(xScrolledSav, qde->xScrollMax);
|
|
ReviseScrollBar(qde);
|
|
|
|
DeAccessMRD(((QMRD)&qde->mrdLSM));
|
|
DeAccessMRD(((QMRD)&qde->mrdFCM));
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
| MoveLayoutToThumb(qde, wThumb, fScrollDir) |
|
|
| |
|
|
| Purpose: This scrolls the layout to a position within the current |
|
|
| topic which corresponds to the given thumb value. It does not redraw |
|
|
| the layout region. |
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void STDCALL MoveLayoutToThumb(QDE qde, int wThumb, SCRLDIR scrldir)
|
|
{
|
|
VA va;
|
|
HFC hfc;
|
|
IFCM ifcm;
|
|
QFCM qfcm;
|
|
int dy;
|
|
int wErr;
|
|
|
|
ASSERT(qde->wLayoutMagic == wLayMagicValue);
|
|
if (qde->rct.top >= qde->rct.bottom)
|
|
return;
|
|
|
|
AccessMRD(((QMRD)&qde->mrdFCM));
|
|
AccessMRD(((QMRD)&qde->mrdLSM));
|
|
|
|
qde->wStyleDraw = wStyleNil;
|
|
|
|
if (scrldir == SCROLL_VERT) {
|
|
FreeLayout(qde);
|
|
|
|
va = VaFromThumb(wThumb, qde);
|
|
hfc = HfcNear(qde, va, (TOP *) &qde->top, &wErr);
|
|
if (wErr == wERRS_NO && hfc == NULL)
|
|
wErr = wERRS_NOTOPIC;
|
|
|
|
if (wErr != wERRS_NO) {
|
|
Error(wErr, wERRA_RETURN);
|
|
return;
|
|
}
|
|
|
|
ASSERT(hfc != NULL);
|
|
ifcm = IfcmLayout(qde, hfc, 0, TRUE, FALSE);
|
|
|
|
qfcm = (QFCM)QFooInMRD(((QMRD)&qde->mrdFCM), sizeof(FCM), ifcm);
|
|
|
|
{ LONG lcbLinearDiff, lcbDisk;
|
|
|
|
/* Find approximate linear diff. Not super accurate when compression*/
|
|
/* is on and we cross a block boundary. */
|
|
|
|
if( QDE_HHDR(qde).wVersionNo == wVersion3_0 ) {
|
|
lcbLinearDiff = VAToOffset30(&va) - VAToOffset30(&qfcm->va);
|
|
} else {
|
|
lcbLinearDiff = VAToOffset(&va) - VAToOffset(&qfcm->va);
|
|
}
|
|
lcbDisk = CbDiskHfc(hfc);
|
|
if( lcbLinearDiff > lcbDisk ) {
|
|
/* The lcbLineDiff arithmetic was off, so apporiximate to
|
|
* "a large dy difference". -Tom
|
|
*/
|
|
dy = qfcm->dySize;
|
|
}
|
|
else {
|
|
/* Ptr 1249: appriximate positioning arithmetic interacting w/
|
|
* compression can cause lcbLinearDiff to be negative. In this
|
|
* case we punt on the dy adjustment and go for zero.
|
|
*/
|
|
if( lcbLinearDiff < 0 ) {
|
|
lcbLinearDiff = 0;
|
|
}
|
|
dy = WMulDiv(qfcm->dySize, lcbLinearDiff, lcbDisk );
|
|
}
|
|
}
|
|
|
|
DyFinishLayout(qde, -dy, FALSE);
|
|
qde->xScrolled = min(qde->xScrolled, qde->xScrollMax);
|
|
}
|
|
else {
|
|
ASSERT(scrldir == SCROLL_HORZ);
|
|
qde->xScrolled = ((wThumb * qde->xScrollMax) / 0x7FFF);
|
|
}
|
|
|
|
ReviseScrollBar(qde);
|
|
DeAccessMRD(((QMRD)&qde->mrdLSM));
|
|
DeAccessMRD(((QMRD)&qde->mrdFCM));
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
| FreeLayout(qde) |
|
|
| |
|
|
| Purpose: This discards all layout constructs associated with the DE. |
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void STDCALL FreeLayout(QDE qde)
|
|
{
|
|
IFCM ifcm, ifcmNext;
|
|
QFCM qfcm;
|
|
|
|
ASSERT(qde->wLayoutMagic == wLayMagicValue);
|
|
|
|
for (ifcm = IFooFirstMRD(((QMRD)&qde->mrdFCM)); ifcm != FOO_NIL;
|
|
ifcm = ifcmNext) {
|
|
ifcmNext = IFooNextMRD(((QMRD)&qde->mrdFCM), sizeof(FCM), ifcm);
|
|
qfcm = (QFCM)QFooInMRD(((QMRD)&qde->mrdFCM), sizeof(FCM), ifcm);
|
|
DiscardIfcm(qde, ifcm);
|
|
}
|
|
qde->imhiSelected = FOO_NIL;
|
|
qde->imhiHit = FOO_NIL;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
| ReviseScrollBar(qde) |
|
|
| |
|
|
| Purpose: This recalculates the thumb positions of the vertical and |
|
|
| horizontal scroll bars, and redraws them. |
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void STDCALL ReviseScrollBar(QDE qde)
|
|
{
|
|
IFCM ifcm;
|
|
QFCM qfcm;
|
|
int wThumb;
|
|
int dxScroll;
|
|
long lBasePos;
|
|
|
|
if (!QDE_TOPIC(qde))
|
|
return;
|
|
|
|
if (qde->fVerScrollVis) {
|
|
if (qde->mli.fLayoutAtTop)
|
|
SetScrollQde(qde, 0, SCROLL_VERT);
|
|
else if (qde->mli.fLayoutAtBottom)
|
|
SetScrollQde(qde, 0x7FFF, SCROLL_VERT);
|
|
else {
|
|
ifcm = IFooFirstMRD(((QMRD)&qde->mrdFCM));
|
|
qfcm = (QFCM)QFooInMRD(((QMRD)&qde->mrdFCM), sizeof(FCM), ifcm);
|
|
wThumb = WFindThumb(-qfcm->yPos, qfcm->dySize, qfcm->va,
|
|
CbDiskHfc(qfcm->hfc), qde);
|
|
if (wThumb == 0x8000)
|
|
wThumb = 0x7FFF;
|
|
if (wThumb < 0x7FFF)
|
|
wThumb++;
|
|
SetScrollQde(qde, wThumb, (SCRLDIR) SCROLL_VERT);
|
|
}
|
|
}
|
|
|
|
// show or hide the horz. scroll bar dependinding on the width remembered..
|
|
dxScroll = qde -> xScrollMaxSoFar - qde->rct.right + qde->rct.left;
|
|
ShowDEScrollBar(qde, SB_HORZ, ((dxScroll > 0 ) ? TRUE : FALSE ));
|
|
|
|
if (dxScroll > 0) { // Horz scroll bar exists, so position thumb.
|
|
if (qde->xScrolled > qde->xScrollMax) {
|
|
SetScrollQde(qde, 0x7FFF, (SCRLDIR) SCROLL_HORZ);
|
|
}
|
|
else if (qde->xScrollMax == 0) {
|
|
SetScrollQde(qde, 0, (SCRLDIR) SCROLL_HORZ);
|
|
}
|
|
else {
|
|
lBasePos = MulDiv(32767, qde->xScrolled, qde->xScrollMax);
|
|
SetScrollQde(qde, (WORD) lBasePos, (SCRLDIR) SCROLL_HORZ);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
| DyFinishLayout(qde, dy, fTrySinglePage) |
|
|
| |
|
|
| Purpose: This is the workhorse of the layout code. It is called when |
|
|
| a valid layout consisting of at least a single FCM exists, and|
|
|
| revises that layout to fill the screen. In addition, it |
|
|
| handles scrolling the layout by up to a screen height. |
|
|
| Params: dy- The amount to add to the positions of the current FCs. |
|
|
| fTrySinglePage - When laying out, check to see if the current |
|
|
| layout will fit onto a single page. |
|
|
| Returns: The offset actually added to the layout. |
|
|
| Method: We use a couple of variables: |
|
|
| -qfcmTop, qfcmBottom: correspond to the first and last |
|
|
| FCMs in the layout. |
|
|
| -yTop: top of the first FCM in the layout. |
|
|
| -yBottom: bottom of the last FCM in the layout. |
|
|
| -yHeight: Height of the layout area. |
|
|
| -fBottomPadded: For aesthetic reasons, there is a blank |
|
|
| space of yHeight / 2 at the bottom of the layout. |
|
|
| fBottomPadded is true if this space has been added. |
|
|
| We proceed in a couple of stages: |
|
|
| |
|
|
| 1) Make sure that the layout fills the layout rectangle. We |
|
|
| loop until the yTop <= 0 and yBottom >= yHeight, or until |
|
|
| certain termination conditions occur. In the loop, |
|
|
| - if yTop > 0, we try to grow the top of the layout. If |
|
|
| we are unable to do this, we shift the layout to the |
|
|
| top of the layout area (there can never be blank space |
|
|
| at the top of the layout). |
|
|
| - if yBottom < yHeight, we try to grow the bottom of the |
|
|
| layout. If we are unable to do this, we pad the bottom|
|
|
| of the layout by yHeight / 2 if we haven't done so |
|
|
| already. Failing this, we terminate if the layout is |
|
|
| pinned at the top (ie, the whole topic fits on one |
|
|
| page), otherwise we shift the layout so that a full |
|
|
| page of layout is always visible. |
|
|
| 2) If we padded yBottom, we unpad it. |
|
|
| 3) We discard any excess FCMs on the top of the bottom of the |
|
|
| layout. |
|
|
| 4) We set the positions of all FCMs in the layout, and |
|
|
| recalculate xScrollMax. |
|
|
-------------------------------------------------------------------------*/
|
|
|
|
enum {
|
|
wStartPageAttempt,
|
|
wInPageAttempt,
|
|
wEndPageAttempt,
|
|
};
|
|
|
|
#define wNoPageAttempt wEndPageAttempt
|
|
|
|
int STDCALL DyFinishLayout(QDE qde, int dy, BOOL fTrySinglePage)
|
|
{
|
|
int dyReturn;
|
|
BOOL fTopStuck;
|
|
BOOL fBottomPadded;
|
|
BOOL fRedoLayout = FALSE;
|
|
HFC hfc;
|
|
IFCM ifcm, ifcmTop, ifcmBottom;
|
|
QFCM qfcm;
|
|
int dyTop, dyBottom, yHeight, yPos, dxAvail;
|
|
int wErr;
|
|
int wPageStatus;
|
|
BOOL fIsScrollDE;
|
|
|
|
// Review: Should be a macro?
|
|
|
|
fIsScrollDE = (BOOL) QDE_TOPIC(qde);
|
|
|
|
if (fTrySinglePage && fIsScrollDE)
|
|
wPageStatus = wStartPageAttempt;
|
|
else
|
|
wPageStatus = wNoPageAttempt;
|
|
|
|
RedoLayout:
|
|
fTopStuck = FALSE;
|
|
fBottomPadded = FALSE;
|
|
|
|
dyReturn = dy;
|
|
|
|
ASSERT(IFooFirstMRD(((QMRD)&qde->mrdFCM)) != FOO_NIL);
|
|
yHeight = qde->rct.bottom - qde->rct.top;
|
|
|
|
ifcmTop = IFooFirstMRD(((QMRD)&qde->mrdFCM));
|
|
dyTop = QfcmFromIfcm(qde, ifcmTop)->yPos + dy;
|
|
ifcmBottom = IFooLastMRD(((QMRD)&qde->mrdFCM));
|
|
dyBottom = QfcmFromIfcm(qde, ifcmBottom)->yPos +
|
|
QfcmFromIfcm(qde, ifcmBottom)->dySize + dy
|
|
- (qde->rct.bottom - qde->rct.top);
|
|
|
|
qde->mli.fLayoutAtTop = qde->mli.fLayoutAtBottom = FALSE;
|
|
|
|
// Step 1. Try to grow the layout to fill the page.
|
|
|
|
for (;;) {
|
|
int dyBottomSav;
|
|
|
|
// Step 1a: Try to grow up towards top of page from current top
|
|
|
|
if (dyTop > 0) {
|
|
/*
|
|
* Note: we check if we are redoing the layout pass. If so, we
|
|
* already know that trying to get the previous FC will fail.
|
|
*/
|
|
|
|
if (!fRedoLayout)
|
|
hfc = HfcPrevHfc(QfcmFromIfcm(qde, ifcmTop)->hfc, &wErr, qde,
|
|
VaMarkTopQde(qde), VaMarkBottomQde(qde));
|
|
else {
|
|
hfc = NULL;
|
|
wErr = wERRS_FCEndOfTopic;
|
|
}
|
|
if (hfc == NULL) {
|
|
if (wErr != wERRS_FCEndOfTopic)
|
|
Error(wErr, wERRA_DIE);
|
|
qde->mli.fLayoutAtTop = TRUE;
|
|
ASSERT(!fTopStuck);
|
|
dyBottom -= dyTop;
|
|
dyReturn -= dyTop;
|
|
dyTop = 0;
|
|
fTopStuck = TRUE;
|
|
continue;
|
|
}
|
|
ifcmTop = IfcmLayout(qde, hfc, 0, TRUE, FALSE);
|
|
dyTop -= QfcmFromIfcm(qde, ifcmTop)->dySize;
|
|
continue;
|
|
}
|
|
|
|
// Step 1b: Try to grow down towards bottom of page from current bottom
|
|
|
|
if (dyBottom < 0 || wPageStatus == wInPageAttempt) {
|
|
if (wPageStatus == wInPageAttempt) {
|
|
|
|
// We already know that we are at the end
|
|
|
|
hfc = NULL;
|
|
wErr = wERRS_FCEndOfTopic;
|
|
}
|
|
else
|
|
hfc = HfcNextHfc(QfcmFromIfcm(qde, ifcmBottom) ->hfc, &wErr, qde,
|
|
VaMarkTopQde(qde), VaMarkBottomQde(qde));
|
|
|
|
if (hfc == NULL) {
|
|
if (wErr != wERRS_FCEndOfTopic)
|
|
Error(wErr, wERRA_DIE);
|
|
|
|
/* We do several things if we hit the end of a topic. These are
|
|
* grouped into the following three "passes".
|
|
*
|
|
* Pass 1: We are at the bottom. Does the whole topic
|
|
* actually fit onto a page? We shift things to the bottom
|
|
* of the page and attempt to fill the top again. If we are
|
|
* successful, fTopStuck will be TRUE when we return,
|
|
* otherwise FALSE.
|
|
*/
|
|
|
|
if (wPageStatus == wStartPageAttempt) {
|
|
dyBottomSav = dyBottom;
|
|
|
|
dyTop -= dyBottom;
|
|
dyBottom = 0;
|
|
wPageStatus = wInPageAttempt;
|
|
continue;
|
|
}
|
|
else if (wPageStatus == wInPageAttempt) {
|
|
if (!fTopStuck) {
|
|
dyBottom = dyBottomSav;
|
|
dyTop += dyBottom;
|
|
}
|
|
/*
|
|
* If fTopStuck, then dyTop and dyBottom have already
|
|
* been set to their correct values. dyReturn may also
|
|
* have been modified, but we don't care since it is
|
|
* going to be recomputed anyway.
|
|
*/
|
|
|
|
wPageStatus = wEndPageAttempt;
|
|
}
|
|
|
|
/*
|
|
* Pass 2: If the whole topic does not fit on a page, we
|
|
* pad the bottom for scrolling.
|
|
*/
|
|
|
|
if (!fTopStuck && !fBottomPadded && fIsScrollDE) {
|
|
fBottomPadded = TRUE;
|
|
dyBottom += yHeight / 2;
|
|
continue;
|
|
}
|
|
|
|
qde->mli.fLayoutAtBottom = TRUE;
|
|
if (fTopStuck || !fIsScrollDE)
|
|
break;
|
|
|
|
/*
|
|
* Pass 3: Even with padding the last FC was still completely on
|
|
* the page, so just align the last (still padded) FC with the
|
|
* bottom of the page and leave.
|
|
*/
|
|
|
|
dyTop -= dyBottom;
|
|
dyReturn -= dyBottom;
|
|
dyBottom = 0;
|
|
continue;
|
|
}
|
|
ifcmBottom = IfcmLayout(qde, hfc, 0, FALSE, FALSE);
|
|
dyBottom += QfcmFromIfcm(qde, ifcmBottom)->dySize;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* For printing, if we have scrolled everything off the page, returning
|
|
* FALSE indicates End-of-Topic.
|
|
*/
|
|
|
|
if (qde->deType == dePrint && yHeight + dyBottom < 0)
|
|
return FALSE;
|
|
|
|
// Step 2: Unpad dyBottom if necessary.
|
|
|
|
if (fBottomPadded)
|
|
dyBottom -= yHeight / 2;
|
|
|
|
// Step 3a: trim excess FCMs on top of layout.
|
|
|
|
while (dyTop + QfcmFromIfcm(qde, ifcmTop) ->dySize < 0) {
|
|
dyTop += QfcmFromIfcm(qde, ifcmTop) ->dySize;
|
|
DiscardIfcm(qde, (IFCM) IFooFirstMRD(((QMRD) &qde->mrdFCM)));
|
|
ASSERT(IFooFirstMRD(((QMRD) &qde->mrdFCM)) != FOO_NIL);
|
|
ifcmTop = IFooFirstMRD(((QMRD) &qde->mrdFCM));
|
|
}
|
|
|
|
// Step 3b: trim excess FCMs on bottom of layout.
|
|
|
|
while (dyBottom - QfcmFromIfcm(qde, ifcmBottom) ->dySize > 0) {
|
|
if (IFooFirstMRD(((QMRD)&qde->mrdFCM)) == IFooLastMRD(((QMRD)&qde->mrdFCM)))
|
|
break;
|
|
dyBottom -= QfcmFromIfcm(qde, ifcmBottom)->dySize;
|
|
DiscardIfcm(qde, (IFCM) IFooLastMRD(((QMRD)&qde->mrdFCM)));
|
|
ASSERT(IFooLastMRD(((QMRD)&qde->mrdFCM)) != FOO_NIL);
|
|
ifcmBottom = IFooLastMRD(((QMRD)&qde->mrdFCM));
|
|
}
|
|
|
|
// Step 4: revise FCM positions, recalculate xScrollMax.
|
|
|
|
qde->xScrollMax = 0;
|
|
dxAvail = qde->rct.right - qde->rct.left;
|
|
yPos = dyTop;
|
|
for (ifcm = IFooFirstMRD(((QMRD) &qde->mrdFCM)); ifcm != FOO_NIL;
|
|
ifcm = IFooNextMRD(((QMRD) &qde->mrdFCM), sizeof(FCM), ifcm)) {
|
|
qfcm = (QFCM) QFooInMRD(((QMRD) &qde->mrdFCM), sizeof(FCM), ifcm);
|
|
qfcm->yPos = yPos;
|
|
yPos += qfcm->dySize;
|
|
qde->xScrollMax = max(qde->xScrollMax,
|
|
qfcm->xPos + qfcm->dxSize - dxAvail);
|
|
|
|
// my stuff - Maha
|
|
|
|
if (qfcm->xPos + qfcm->dxSize - dxAvail > 0)
|
|
qde->xScrollMaxSoFar = max(qde->xScrollMaxSoFar,
|
|
qfcm->xPos + qfcm->dxSize);
|
|
}
|
|
|
|
/*
|
|
* Check to see if we really needed the vertical scrollbar. I blow off
|
|
* the case where the layout fit exactly. We will add the scroll bar in
|
|
* this case, instead of looking ahead and possibly doing the layout
|
|
* again. We need to have figured out xScrollMaxSoFar by this point in
|
|
* order to handle all cases correctly.
|
|
*/
|
|
|
|
/*
|
|
* 13-Sep-1993 [ralphw] -- It's pretty goofy for the user to see the
|
|
* scroll bar added when it isn't needed. So, we do the right thing and
|
|
* get rid of it when we fit. We can bear the penalty of a relayout.
|
|
*/
|
|
|
|
//if (!fRedoLayout && fIsScrollDE &&
|
|
// dyBottom < 1) {
|
|
// dyBottom = -(qde->dyHorScrollHeight + 1);
|
|
// fTopStuck = TRUE;
|
|
//}
|
|
|
|
if (!fRedoLayout && fIsScrollDE && fTopStuck && dyBottom < 0) {
|
|
HFC hfcTop;
|
|
|
|
/*
|
|
* Special case: We are about to take away the vertical scroll bar:
|
|
* Check to see if we will be adding a horz. scroll bar and obscuring
|
|
* stuff within that space. If so, do not remove the vertical
|
|
* scrollbar. This duplicates a test in ReviseScrollBar.
|
|
*/
|
|
|
|
ASSERT(qde->dyHorScrollHeight > 0 && qde->dxVerScrollWidth > 0);
|
|
if (-dyBottom > qde->dyHorScrollHeight ||
|
|
qde->xScrollMaxSoFar <= qde->dxVerScrollWidth + RECT_WIDTH(qde->rct)) {
|
|
hfcTop = QfcmFromIfcm(qde, ifcmTop)->hfc;
|
|
ShowDEScrollBar(qde, SB_VERT, FALSE);
|
|
qde->wStyleDraw = wStyleNil;
|
|
|
|
/*
|
|
* WARNING: For efficiency, we set the FCM's hfc to NULL, so
|
|
* that FreeLayout will not free it: we want to re-use this HFC
|
|
* instead of copying it.
|
|
*/
|
|
|
|
QfcmFromIfcm(qde, ifcmTop)->hfc = NULL;
|
|
FreeLayout(qde);
|
|
IfcmLayout(qde, hfcTop, 0, TRUE, FALSE);
|
|
dy = 0;
|
|
fRedoLayout = TRUE;
|
|
goto RedoLayout;
|
|
}
|
|
}
|
|
|
|
qde->tlp.va = QfcmFromIfcm(qde, ifcmTop)->va;
|
|
|
|
// (kevynct) Deal with empty FCM
|
|
|
|
if (QfcmFromIfcm(qde, ifcmTop) ->dySize == 0)
|
|
qde->tlp.lScroll = 0L;
|
|
else
|
|
qde->tlp.lScroll = (long) (0x10000L * (- dyTop) /
|
|
QfcmFromIfcm(qde, ifcmTop) ->dySize);
|
|
|
|
if (VaFirstQde(qde) .dword == QfcmFromIfcm(qde, ifcmTop) ->va.dword &&
|
|
QfcmFromIfcm(qde, ifcmTop) ->yPos == 0)
|
|
qde->mli.fLayoutAtTop = TRUE;
|
|
|
|
return dyReturn;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------
|
|
| WFindThumb( dyPos, yLen, vaPt, lcbLen, qde ) |
|
|
| |
|
|
| Finds the position of the button in the vertical scroll bar button, given|
|
|
| some notion of the current position in the help file. |
|
|
| |
|
|
| Arguments: |
|
|
| |
|
|
| dyPos The position within the full context in pixels. |
|
|
| yLen The length of the full context in pixels. |
|
|
| vaPt The virtual address of the current full context. |
|
|
| lcbLen The length of the full context in bytes. |
|
|
| qde Display environ, used to get various vaSR values... |
|
|
| |
|
|
| Note: The following assumptions are made. |
|
|
| |
|
|
| 0 <= dyPos <= yLen |
|
|
| 0 <= fclPt + yLen <= lcbTopicLen |
|
|
--------------------------------------------------------------------------*/
|
|
|
|
INLINE static int STDCALL WFindThumb(int dyPos, int yLen, VA va, DWORD ulcbLen,
|
|
QDE qde)
|
|
{
|
|
int wMajor;
|
|
int wMinor;
|
|
DWORD dbIntoTopic;
|
|
|
|
// find a relative byte measure of where we are in the topic based on the VA:
|
|
|
|
DWORD lcbBytesPerBlock;
|
|
DWORD lcbBlocksInTopic;
|
|
|
|
lcbBlocksInTopic =
|
|
qde->top.mtop.vaNextSeqTopic.bf.blknum - qde->top.mtop.vaSR.bf.blknum;
|
|
|
|
if (lcbBlocksInTopic) {
|
|
// First calculate a measure of how many bytes are in an uncompressed
|
|
// block:
|
|
lcbBytesPerBlock = qde->top.cbTopic; // raw uncompressed size
|
|
// adjust for byte offset block overflow:
|
|
lcbBytesPerBlock -=
|
|
qde->top.mtop.vaNextSeqTopic.bf.byteoff - qde->top.mtop.vaSR.bf.byteoff;
|
|
// divide by the number of blocks:
|
|
lcbBytesPerBlock /= lcbBlocksInTopic;
|
|
dbIntoTopic = (va.bf.blknum - qde->top.mtop.vaSR.bf.blknum) * lcbBytesPerBlock;
|
|
dbIntoTopic += (va.bf.byteoff - qde->top.mtop.vaSR.bf.byteoff);
|
|
}
|
|
else
|
|
dbIntoTopic = va.bf.byteoff - qde->top.mtop.vaSR.bf.byteoff;
|
|
|
|
// this may not always be true when compression fudging is innaccurate:
|
|
//AssertF( (LONG)dbIntoTopic >= 0 );
|
|
// Deal with fudging inaccuracies:
|
|
if ((long) dbIntoTopic < 0)
|
|
dbIntoTopic = 0;
|
|
else if ((long) dbIntoTopic > qde->top.cbTopic)
|
|
dbIntoTopic = qde->top.cbTopic;
|
|
|
|
wMajor = WMulDiv(0x7FFF, dbIntoTopic, qde->top.cbTopic);
|
|
wMinor = WMulDiv(0x7FFF, (long) dyPos, (long) yLen);
|
|
|
|
/*
|
|
* And again, ulcbLen is a post-compression size whereas top.cbTopic is a
|
|
* precompression size. Since compression can actually grow the size, and
|
|
* WMulDiv() assumes arg2 <= arg3, we check for the relatively rare case
|
|
* where arg3 > arg2 and fudge it. Help3.1 ptr 1349.
|
|
*/
|
|
|
|
if (ulcbLen > (DWORD) qde->top.cbTopic)
|
|
ulcbLen = (DWORD) qde->top.cbTopic;
|
|
wMinor = WMulDiv(wMinor, ulcbLen, qde->top.cbTopic);
|
|
|
|
// Deal more with fudging inaccuracies:
|
|
if (wMajor + wMinor > 0x8000)
|
|
return(0x8000);
|
|
|
|
return(wMajor + wMinor);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*\
|
|
| VaFromThumb( wThumb, qde )
|
|
|
|
|
| Calculates the VA position from a thumb position and the info in the DE.
|
|
| The scroll bar is assumed to range from 0 - 0x7FFF.
|
|
|
|
|
| Method: The wThumb value gives us a relative measure of how far into the
|
|
| topic to go. Because the topic may be compressed on 2K boudaries
|
|
| we have to fudge around with the VAs (virtual addresses) in the
|
|
| DE structure:
|
|
| de.top.mtop.vaSR - de.top.mtop.vaNextSeqTopic
|
|
| gives us a VA "span", we find the appropriate relative position
|
|
| within the span based on the relative pos of wThumb. To help
|
|
| achieve this we calculate an average bytes-per-2K-block value.
|
|
| When compression is not on, this should end up being exactly 2K.
|
|
|
|
|
| Note: This could be more efficient if we used 0x8000 as the range for
|
|
| scroll bars, but it might leave some error when the thumb was at
|
|
| the very bottom of the topic.
|
|
|
|
|
\*----------------------------------------------------------------------------*/
|
|
|
|
INLINE static VA STDCALL VaFromThumb(int wThumb, QDE qde)
|
|
{
|
|
VA vaRet;
|
|
DWORD lcbBytesPerBlock;
|
|
DWORD lcbBlocksInTopic;
|
|
DWORD lQ, lR, lLinear;
|
|
|
|
lcbBlocksInTopic =
|
|
qde->top.mtop.vaNextSeqTopic.bf.blknum - qde->top.mtop.vaSR.bf.blknum;
|
|
|
|
if( lcbBlocksInTopic ) {
|
|
// First calculate a measure of how many bytes are in an uncompressed
|
|
// block:
|
|
lcbBytesPerBlock = qde->top.cbTopic; // raw uncompressed size
|
|
// adjust for byte offset block overflow:
|
|
lcbBytesPerBlock -=
|
|
qde->top.mtop.vaNextSeqTopic.bf.byteoff - qde->top.mtop.vaSR.bf.byteoff;
|
|
// divide by the number of blocks:
|
|
lcbBytesPerBlock /= lcbBlocksInTopic;
|
|
}
|
|
|
|
// At this point, fcReturn = lcbTopic*(wThumb / 0x7FFF).
|
|
|
|
lQ = ((qde->top.cbTopic-1) / 0x7FFF);
|
|
lR = ((qde->top.cbTopic-1) % 0x7FFF);
|
|
|
|
// At this point, fcReturn = wThumb*(lQ + lR/0x7FFF).
|
|
|
|
// REVIEW: [ralphw] Looks like MulDiv would work here
|
|
|
|
lLinear = (wThumb * lQ) + ((wThumb * lR) / 0x7FFF);
|
|
|
|
// translate the linear offset into a VA:
|
|
vaRet = qde->top.mtop.vaSR;
|
|
if( lcbBlocksInTopic ) {
|
|
vaRet.bf.blknum += (vaRet.bf.byteoff + lLinear) / lcbBytesPerBlock;
|
|
vaRet.bf.byteoff = (vaRet.bf.byteoff + lLinear) % lcbBytesPerBlock;
|
|
}
|
|
else {
|
|
vaRet.bf.byteoff = (vaRet.bf.byteoff + lLinear);
|
|
}
|
|
|
|
/* h3.07 ptr 1270: the above arithmetic can cause vaRet to be lessened
|
|
* with certain large-NSR topics. Must make sure this never
|
|
* happens cause otherwise we display the NSR in the SR region.
|
|
*/
|
|
if( vaRet.dword < qde->top.mtop.vaSR.dword )
|
|
vaRet = qde->top.mtop.vaSR;
|
|
|
|
// Adjust for any slop which may have occurred w/ our
|
|
// "guess the compression" arithmetic -- we must never return a VA
|
|
// in the next topic...
|
|
if( vaRet.dword >= qde->top.mtop.vaNextSeqTopic.dword ) {
|
|
vaRet = qde->top.mtop.vaNextSeqTopic;
|
|
if( vaRet.bf.byteoff ) {
|
|
vaRet.bf.byteoff -= 1;
|
|
}
|
|
else {
|
|
vaRet.bf.blknum -= 1;
|
|
vaRet.bf.byteoff = cbBLOCK_SIZE; // this is sleazy
|
|
}
|
|
}
|
|
return(vaRet);
|
|
}
|
|
|
|
/*******************
|
|
**
|
|
** Name: ShowDEScrollBar
|
|
**
|
|
** Purpose: Shows or hides the scroll bar.
|
|
**
|
|
** Arguments: qde - far pointer to a DE
|
|
** wWhich - which scroll (SCROLL_VERT or SCROLL_HORZ)
|
|
** fShow - Shows if TRUE, Hides if FALSE
|
|
**
|
|
** Returns: Nothing.
|
|
**
|
|
*******************/
|
|
|
|
extern BOOL fHorzBarPending;
|
|
|
|
static VOID STDCALL ShowDEScrollBar(QDE qde, WORD wWhich, BOOL fShow)
|
|
{
|
|
if (!QDE_TOPIC(qde))
|
|
return;
|
|
|
|
switch (wWhich) {
|
|
case SB_VERT:
|
|
if (fShow == qde->fVerScrollVis)
|
|
return;
|
|
qde->fVerScrollVis = fShow;
|
|
break;
|
|
|
|
case SB_HORZ:
|
|
if (fShow == qde->fHorScrollVis)
|
|
return;
|
|
|
|
if (fShow && (!qde->fHorScrollVis && qde->fVerScrollVis))
|
|
fHorzBarPending = TRUE;
|
|
qde->fHorScrollVis = fShow;
|
|
break;
|
|
}
|
|
ShowScrollBar(qde->hwnd, wWhich, fShow);
|
|
GetClientRect(qde->hwnd, &qde->rct);
|
|
}
|
|
|
|
INLINE static BOOL STDCALL FFindMatchRect(QDE qde, int ifcm, OBJRG objrg,
|
|
LPRECT qrct)
|
|
{
|
|
QLSM qlsm;
|
|
int ilsm;
|
|
BOOL fFound = FALSE;
|
|
QFCM qfcm = QfcmFromIfcm(qde, ifcm);
|
|
|
|
ilsm = IFooFirstMRD(((QMRD)&qde->mrdLSM));
|
|
while (ilsm != FOO_NIL) {
|
|
qlsm = ((QLSM)QFooInMRD(((QMRD)&qde->mrdLSM), sizeof(LSM), ilsm));
|
|
if (qlsm->ifcm == ifcm) {
|
|
OBJRG objrgS;
|
|
OBJRG cobjrg;
|
|
|
|
objrgS = OBJRGFromSMP(&qlsm->smp, qfcm);
|
|
cobjrg = COBJRGFromSMP(&qlsm->smp);
|
|
if (objrg >= objrgS && objrg < objrgS + cobjrg) {
|
|
*qrct = qlsm->rctFirst;
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
ilsm = IFooNextMRD(((QMRD)&qde->mrdLSM), sizeof(LSM), ilsm);
|
|
}
|
|
return fFound;
|
|
}
|