hostile-takeover/game/Help.cpp
Nathan Fulton fc00f91f25 Added processing for vchrBack
Android’s system back button will be processed as vchrBack which will
select kidcCancel on most forms. This causes the user to feel that the
game is more integrated with the OS.
2016-08-31 23:54:48 -04:00

1122 lines
28 KiB
C++

#include "ht.h"
namespace wi {
// Supported tags:
// <H1> -- heading on a new line, large font
// <H2> -- heading on a new line, normal font
// <BR> -- line break
// <HR> -- horizontal rule
// <A HREF="#link_target">link text</A> -- a hyperlink
// <A NAME="link_target"> -- a hyperlink target
// <IMG SRC="filename.amx"> -- uses animation strip named "help"
//
// Help Control
//
bool PaintChunk(int x, int y, int cx, int cy, int curIndex, int curY, int nyBottom, Chunk *pChunk, void *ptr) secHelpControl;
bool HitTestChunk(int x, int y, int cx, int cy, int curIndex, int curY, int nyBottom, Chunk *pChunk, void *ptr) secHelpControl;
bool FindPositionCallback(int x, int y, int cx, int cy, int curIndex, int curY, int nyBottom, Chunk *pChunk, void *ptr) secHelpControl;
HelpControl::HelpControl()
{
m_fTimerAdded = false;
m_fDrag = false;
m_fLargeFont = true;
m_nchCurrent = 0;
m_pfil = NULL;
memset(m_nchBack, 0, sizeof(m_nchBack));
memset(&m_hittest, 0, sizeof(m_hittest));
}
HelpControl::~HelpControl()
{
if (m_pfil != NULL) {
gpakr.fclose(m_pfil);
}
if (m_fTimerAdded) {
gtimm.RemoveTimer(this);
}
}
bool HelpControl::Init(Form *pfrm, IniReader *pini, FindProp *pfind)
{
// Base initialization
if (!Control::Init(pfrm, pini, pfind))
return false;
m_cyPageAmount = (int)((long)m_rc.Height() * 85 / 100);
#if defined(IPHONE) || defined(SDL)
m_wf |= kfHelpScrollPosition;
#endif
return true;
}
bool HelpControl::SetFile(const char *pszFile)
{
m_pfil = gpakr.fopen((char *)pszFile, "rb");
if (m_pfil != NULL) {
gpakr.fseek(m_pfil, 0, SEEK_END);
m_cb = gpakr.ftell(m_pfil);
gpakr.fseek(m_pfil, 0, SEEK_SET);
}
return m_pfil != NULL;
}
bool HelpControl::FollowLink(const char *str, int cch)
{
m_fLargeFont = false;
// Get file length
gpakr.fseek(m_pfil, 0, SEEK_END);
dword cbFile = gpakr.ftell(m_pfil);
// Calc the tag length we're searching for
char szTag[128];
strcpy(szTag, "<A NAME=\"");
strncpyz(&szTag[9], (char *)str, 1 + (cch != -1 ? cch : (int)strlen(str))); // + 1 for zero terminator
int cchTag = (int)strlen(szTag);
// Loop until we've found the tag or reached the end
char szBuffer[512];
dword nchFile = 0;
while (nchFile < cbFile) {
// Read the next chunk in, zero terminate it
gpakr.fseek(m_pfil, nchFile, SEEK_SET);
int cchRead = gpakr.fread(szBuffer, 1, sizeof(szBuffer) - 1, m_pfil);
// We don't want a tag to get cut at the end of szBuffer so
// terminate appropriately.
szBuffer[cchRead] = 0;
szBuffer[sizeof(szBuffer) - 1 - cchTag - 1] = 0;
// Search our buffer for the link.
char *pszFind = strstr(szBuffer, szTag);
if (pszFind != NULL) {
// Assume what the link points to is always an <H?> tag
// i.e., a large font
m_fLargeFont = true;
for(int i = 8; i >= 0; i--)
m_nchBack[i + 1] = m_nchBack[i];
m_nchBack[0] = m_nchCurrent;
m_nchCurrent = nchFile + (int)(pszFind - szBuffer);
Invalidate();
return true;
}
// max link length is 30 + 9 for <A HREF="
if (strlen(szBuffer) == 39)
break;
nchFile += strlen(szBuffer) - 39;
}
return false;
}
struct PaintInfo {
int *pnchEnd;
HitTest *phittest;
};
void HelpControl::OnPaint(DibBitmap *pbm)
{
if (m_pfil == NULL) {
return;
}
int nchEnd;
PaintInfo info;
info.pnchEnd = &nchEnd;
info.phittest = &m_hittest;
Layout(m_nchCurrent, m_fLargeFont, pbm, PaintChunk, &info);
if (m_wf & kfHelpScrollPosition) {
Rect rcT;
Rect rcScrollPos;
GetSubRects(&rcT, &rcScrollPos);
pbm->Shadow(rcScrollPos.left, rcScrollPos.top, rcScrollPos.Width(),
rcScrollPos.Height());
pbm->Shadow(rcScrollPos.left, rcScrollPos.top, rcScrollPos.Width(),
rcScrollPos.Height());
int y1 = rcScrollPos.Height() * m_nchCurrent / m_cb;
int y2 = rcScrollPos.Height() * nchEnd / m_cb;
if (y2 - y1 < gcxyBorder * 2) {
y2 = y1 + gcxyBorder * 2;
if (y2 > rcScrollPos.bottom) {
int dy = rcScrollPos.bottom - y2;
y1 += dy;
y2 += dy;
}
}
pbm->Fill(rcScrollPos.left, rcScrollPos.top + y1,
rcScrollPos.Width(), y2 - y1,
GetColor(kiclrMediumGray));
}
}
void HelpControl::OnPenEvent(Event *pevt)
{
switch (pevt->eType) {
case penDownEvent:
m_fDrag = false;
m_yDrag = pevt->y;
memset(&m_hittest, 0, sizeof(m_hittest));
m_hittest.x = pevt->x;
m_hittest.y = pevt->y;
m_hittest.dBest = 10000;
Layout(m_nchCurrent, m_fLargeFont, NULL, HitTestChunk, &m_hittest);
if (pevt->ff & kfEvtFinger) {
// Need a constant here
if (m_hittest.dBest <= 20) {
m_hittest.fHit = true;
}
} else {
if (m_hittest.dBest == 0) {
m_hittest.fHit = true;
}
}
Invalidate();
break;
case penMoveEvent:
if (!m_fDrag) {
if (abs(m_yDrag - pevt->y) >=
gapfnt[kifntDefault]->GetHeight() / 2) {
m_fDrag = true;
m_hittest.fHit = false;
}
}
break;
case penUpEvent:
if (m_fDrag) {
if (m_flics.Init(1, 1.0f, 0.12f, 33, false)) {
m_yDragUp = pevt->y;
gtimm.AddTimer(this, 10);
m_fTimerAdded = true;
}
m_fDrag = false;
return;
} else {
if (m_hittest.fHit) {
m_hittest.fHit = false;
FollowLink(m_hittest.szText, m_hittest.cch);
}
}
break;
default:
m_fDrag = false;
break;
}
if (!m_fDrag) {
m_flics.Clear();
return;
}
DragScroll(pevt->y);
}
void HelpControl::DragScroll(int y)
{
int dy = m_yDrag - y;
if (dy == 0) {
return;
}
// Make it an accelerant since the positioning isn't 1:1 anyway, and
// help is really long
//dy *= 5;
int nchNew;
if (dy < 0) {
nchNew = FindPrevPosition(m_nchCurrent, -dy, &m_fLargeFont);
} else if (dy > 0) {
nchNew = FindNextPosition(m_nchCurrent, dy, &m_fLargeFont, false, true);
}
if (nchNew != m_nchCurrent) {
m_nchCurrent = nchNew;
m_yDrag = y;
Invalidate();
}
}
void HelpControl::OnTimer(long tCurrent)
{
if (!m_flics.HasMagnitude()) {
gtimm.RemoveTimer(this);
m_fTimerAdded = false;
return;
}
// GetPosition returns a delta.
Point pt;
m_flics.GetPosition(&pt);
DragScroll(pt.y + m_yDragUp);
}
void HelpControl::GetSubRects(Rect *prcInterior, Rect *prcScrollPos)
{
if (m_wf & kfHelpScrollPosition) {
if (prcScrollPos != NULL) {
*prcScrollPos = m_rc;
prcScrollPos->left = prcScrollPos->right - gcxyBorder * 2;
}
*prcInterior = m_rc;
prcInterior->right -= gcxyBorder * 3;
} else {
*prcInterior = m_rc;
}
}
bool HelpControl::Layout(dword nchStart, bool fLargeFont, DibBitmap *pbm, ChunkProc pfn, void *pv)
{
bool fDone = false;
dword nchBuffer = nchStart;
int cLines = 0;
int cyCurrent = 0;
Rect rcT;
GetSubRects(&rcT);
int xCurrent = m_rc.left;
int yCurrent = m_rc.top;
Rect rcForm;
m_pfrm->GetRect(&rcForm);
xCurrent += rcForm.left;
yCurrent += rcForm.top;
int xStart = xCurrent;
int yTop = rcForm.top + yCurrent;
int yBottom = rcForm.top + rcT.top + rcT.Height();
// Layout from nchStart until a callback returns true indicating that we've
// reached our finish condition: - HitTestChunk and PaintChunk return when
// we've traversed rcT.bottom - rcT.top - FindPositionCallback will return
// when the index has reached m_nchCurrent index
gpakr.fseek(m_pfil, 0, SEEK_END);
dword cbFile = gpakr.ftell(m_pfil);
gpakr.fseek(m_pfil, nchBuffer, SEEK_SET);
while (nchBuffer < cbFile) {
cyCurrent = yCurrent - yTop;
gpakr.fseek(m_pfil, nchBuffer, SEEK_SET);
int cchRead = gpakr.fread(m_szText, 1, sizeof(m_szText) - 1, m_pfil);
m_szText[cchRead] = 0;
// If we haven't hit a tag then it's normal text
if (m_szText[0] !='<') {
// Ignore CR / LF at the beginning of the line
if (m_szText[0] == '\n' || m_szText[0] == '\r') {
nchBuffer++;
if (m_szText[1] == '\n' || m_szText[1] == '\r')
nchBuffer++;
continue;
}
// Raw text to layout. Find out where this text breaks
char *pszBreakNext = m_szText;
int cchLine = gapfnt[fLargeFont ? kifntTitle : kifntDefault]->CalcBreak(rcT.right - xCurrent, &pszBreakNext);
// Extra breaking rules: <
// Remember if this occurs since we won't progress to the next line
bool fNextLine = true;
for (int i = 0; i < cchLine; i++) {
if (m_szText[i] == '<') {
pszBreakNext = &m_szText[i];
cchLine = i;
fNextLine = false;
break;
}
}
// Set up chunk for callback
Chunk chnk;
chnk.fLargeFont = fLargeFont;
chnk.cch = cchLine;
chnk.pbm = pbm;
chnk.psz = m_szText;
chnk.nType = fLargeFont ? knChunkLargeText : knChunkRawText;
int cx = gapfnt[fLargeFont ? kifntTitle : kifntDefault]->GetTextExtent(chnk.psz, chnk.cch);
int cy = LineHeight(fLargeFont);
// Call back to handle this chunk
if (pfn(xCurrent, yCurrent, cx, cy, nchBuffer, cyCurrent, yBottom, &chnk, pv) )
return true;
// Advance; progress to the next line if necessary
xCurrent += cx;
if (fNextLine) {
xCurrent = rcT.left;
yCurrent += LineHeight(fLargeFont);
cLines++;
}
// Advance to the next text after the break
nchBuffer += pszBreakNext - m_szText;
continue;
}
// H1 or H2?
// H1 means heading on a new line, large font
// H2 means heading on a new line, normal font
if (strncmp(m_szText, "<H1>", 4) == 0 || strncmp(m_szText, "<H2>", 4) == 0) {
// Call back to know if we're stopping since we need to stop before the <H?>.
if (pfn(xCurrent, yCurrent, 0, gapfnt[kifntDefault]->GetHeight(), nchBuffer, cyCurrent, yBottom, NULL, pv))
return true;
// Format to the next line if not already there
if (xCurrent != rcT.left) {
cLines++;
yCurrent += gapfnt[kifntDefault]->GetHeight();
xCurrent = rcT.left;
}
fLargeFont = (m_szText[2] == '1') ? true : false;
nchBuffer += 4;
continue;
}
// BR means line break
if (strncmp(m_szText, "<BR>", 4) == 0) {
// Call back to know if layout is ending
if (pfn(xCurrent, yCurrent, 0, gapfnt[kifntDefault]->GetHeight(), nchBuffer, cyCurrent, yBottom, NULL, pv))
return true;
// Format to the next line
cLines++;
yCurrent += gapfnt[kifntDefault]->GetHeight();
xCurrent = rcT.left;
nchBuffer += 4;
continue;
}
// HR means horizontal rule
// a HR always has it's own line, which occupies gapfnt[kifntDefault]->GetHeight() of vertical space
if (strncmp(m_szText, "<HR>", 4) == 0) {
Chunk chnk;
chnk.fLargeFont = fLargeFont;
chnk.nType = knChunkHRTag;
chnk.pbm = pbm;
if(xCurrent != rcT.left) {
cLines++;
xCurrent = rcT.left;
yCurrent += gapfnt[kifntDefault]->GetHeight();
cyCurrent = yCurrent - yTop;
}
// Call back to know if layout is ending
if (pfn(xCurrent, yCurrent, rcT.Width(), knHelpControlBRHeight, nchBuffer, cyCurrent, yBottom, &chnk, pv))
return true;
// Format to the next line
cLines++;
yCurrent += knHelpControlBRHeight;
xCurrent = rcT.left;
nchBuffer += 4;
continue;
}
// <A HREF="#link">link text</A>
if (strncmp(m_szText, "<A HREF", 7) == 0) { // link or target
// Otherwise we have a link. Find the link text
char *pchLinkStart = strchr(&m_szText[10], '>') + 1;
char *pchLinkEnd = strchr(&m_szText[10], '<');
Assert(pchLinkStart != NULL);
Assert(pchLinkEnd != NULL);
Assert(pchLinkEnd - pchLinkStart < 100);
// Word wrap the link text
char *pchT = pchLinkStart;
while (pchT < pchLinkEnd) {
char *pchBreakNext = pchT;
bool fNewLine = true;
int cchChunk = gapfnt[kifntDefault]->CalcBreak(rcT.right - xCurrent, &pchBreakNext, xCurrent == xStart);
if (cchChunk > pchLinkEnd - pchT) {
cchChunk = (int)(pchLinkEnd - pchT);
pchBreakNext = pchLinkEnd;
fNewLine = false;
}
// Handle this chunk
Chunk chnk;
chnk.fLargeFont = fLargeFont;
chnk.psz = pchT;
chnk.nType = knChunkLinkText;
chnk.pbm = pbm;
chnk.cch = cchChunk;
strncpy(chnk.szText, &m_szText[10],pchLinkStart - &m_szText[10] - 2);
chnk.szText[pchLinkStart - &m_szText[10] - 2] = 0;
int cx = gapfnt[kifntDefault]->GetTextExtent(chnk.psz, chnk.cch);
int cy = gapfnt[kifntDefault]->GetHeight();
if (pfn(xCurrent, yCurrent, cx, cy, nchBuffer, cyCurrent, yBottom, &chnk, pv))
return true;
// Adjust x, y position
xCurrent += cx;
if (fNewLine) {
cLines++;
xCurrent = rcT.left;
yCurrent += cy;
}
// Go to next text chunk
pchT = pchBreakNext;
}
// Advance past the </A>
nchBuffer += pchLinkEnd - m_szText + 4;
continue;
}
// <A NAME="link_target">
if (strncmp(m_szText, "<A NAME=\"", 9) == 0) {
// If it's a link target, just skip over it since nothing is displayed
char *pchT = strchr(m_szText, '>');
if (pchT != NULL)
nchBuffer += (pchT - m_szText) + 1;
continue;
}
// Image?
// <IMG SRC="filename.amx">
if (strncmp(m_szText, "<IMG SRC=\"", 10) == 0) {
// Find the filename delimiters (quotes)
char *pchFilenameStart = &m_szText[10];
char *pchFilenameEnd = strchr(pchFilenameStart, '\"');
Assert(pchFilenameEnd != NULL);
Assert(pchFilenameEnd - pchFilenameStart < 32);
*pchFilenameEnd = 0;
// Load the file, get bounds
AnimationData* panid = LoadAnimationData(pchFilenameStart);
Assert(panid != NULL);
if (panid == NULL) {
nchBuffer += pchFilenameEnd - m_szText + 2;
continue;
}
Rect rc;
panid->GetBounds(panid->GetStripIndex("help"), 0, &rc);
// Images always start on a new line
if (xCurrent != rcT.left) {
yCurrent += LineHeight(fLargeFont);
cLines++;
xCurrent = rcT.left;
}
// Handle this chunk
Chunk chnk;
chnk.nType = knChunkAniData;
chnk.pv = panid;
chnk.fLargeFont = fLargeFont;
chnk.pbm = pbm;
if (pfn(xCurrent, yCurrent, rc.Width(), rc.Height(), nchBuffer, cyCurrent, yBottom, &chnk, pv)) {
delete panid;
return true;
}
// Always go to the next line
yCurrent += rc.Height();
nchBuffer += pchFilenameEnd - m_szText + 2;
delete panid;
}
// skip over any </ tags that indicate ending use of a font
if (strncmp(m_szText, "</", 2) == 0) {
int i;
for (i = 1; m_szText[i] != '>'; i++);
nchBuffer += i + 1;
}
}
return true;
}
int HelpControl::LineHeight(bool fLargeFont)
{
return gapfnt[fLargeFont ? kifntTitle : kifntDefault]->GetHeight();
}
void HelpControl::DoNextPage()
{
//take care of updating the back array
for(int i = 8; i >= 0; i--)
m_nchBack[i + 1] = m_nchBack[i];
m_nchBack[0] = m_nchCurrent;
m_nchCurrent = FindNextPosition(m_nchCurrent, m_cyPageAmount, &m_fLargeFont, false, false);
Invalidate();
}
void HelpControl::DoPrevPage()
{
//take care of updating the back array
for(int i = 8; i >= 0; i--)
m_nchBack[i + 1] = m_nchBack[i];
m_nchBack[0] = m_nchCurrent;
m_nchCurrent = FindPrevPosition(m_nchCurrent, m_cyPageAmount, &m_fLargeFont);
Invalidate();
}
int HelpControl::FindPrevPosition(int nchFrom, int cyAmount, bool *pfLargeFont)
{
// If no room to scroll back, we're done
if (nchFrom == 0)
return 0;
// check if there is an <HR> either directly above the current location
// or there is a link target above the location with an <HR> above that
char sz[64];
gpakr.fseek(m_pfil, nchFrom - (sizeof(sz) - 1), SEEK_SET);
int cchRead = gpakr.fread(sz, 1, sizeof(sz) - 1, m_pfil);
sz[cchRead] = 0;
// if there is a tag directly above us figure out if it is a HR or a link
// and move nchFrom to directly before those tags. Since we're finding the
// previous heading if the HR would have fit on the page anyways it will be
// displayed, but if it wouldn't have fit then we don't want to display it.
if (sz[cchRead - 1] == '>' || sz[cchRead - 3] == '>') { int i = cchRead -
1; while(sz[i] != '<') i--;
// we found the link for the heading and need to check for an HR before
// it
if(sz[i + 1] == 'A') for(i--; sz[i] != '<'; i--);
// we want to ignore the HR at the bottom of the screen in case the
// section above takes up the entire screen.
if(strncmp(&sz[i], "<HR>", 4) == 0)
nchFrom -= cchRead - i;
}
// Search back for a header since we know that'll format starting on a left
// edge. Then layout forward until we've formatted more space than the
// scroll back amount. Then layout forward the difference between the
// heading we found and the desired scroll back amount.
int nchSeek = nchFrom;
FindPositionHelper fph;
fph.nIndex = 0;
fph.nDistY = 0;
while (nchSeek != 0) {
// Find the position to read forward from
char szT[256];
szT[255] = 0;
nchSeek -= sizeof(szT) - 1;
if (nchSeek < 0)
nchSeek = 0;
// Read in this piece
gpakr.fseek(m_pfil, nchSeek, SEEK_SET);
int cchRead = gpakr.fread(szT, 1, sizeof(szT) - 1, m_pfil);
szT[cchRead] = 0;
// Find prev heading <h1> in buffer that when formatted forward from
// gives us a vertical size greater than the desired page amount
bool fDone = false;
for (int i = cchRead - 1; i >= 0; i--) {
if (strncmp(&szT[i], "<H1>", 4) == 0) {
fph.cySpan = 0;
fph.nCondition = knFindPosRunToIndex;
fph.nIndex = nchFrom;
Layout(nchSeek + i, false, NULL, FindPositionCallback, &fph);
fph.nIndex = nchSeek + i;
// CRM temp: alternate means of paging starts here
if (fph.cySpan <= m_rc.Height())
return fph.nIndex;
else
return FindNextPosition(fph.nIndex, fph.cySpan - cyAmount, pfLargeFont, true, false);
}
}
}
return 0;
// temp: ends here
/* CRM Removed to try alternate means of paging
if (fph.cySpan >= cyAmount) {
*pfLargeFont = fph.fLargeFont;
fDone = true;
break;
}
}
}
if (fDone)
break;
}
if (fph.cySpan - cyAmount > 0)
return FindNextPosition(fph.nIndex, fph.cySpan - cyAmount, pfLargeFont, true, false);
*pfLargeFont = fph.fLargeFont;
return fph.nIndex;
*/
return 0;
}
// fCondition is used to indicate whether we are to travel at most cyAmount or at least
// cyAmount
int HelpControl::FindNextPosition(int nchFrom, int cyAmount, bool *pfLargeFont, bool fCondition, bool fSmooth)
{
FindPositionHelper fph;
memset(&fph, 0, sizeof(fph));
fph.nIndex = nchFrom;
fph.nDistY = cyAmount;
fph.cySpan = cyAmount;
fph.cyControl = m_rc.Height();
#if defined(IPHONE) || defined(SDL)
fph.nCondition = knFindPosFingerScroll;
#else
fph.nCondition = fCondition ? knFindPosAtLeastY : knFindPosAtMostY;
#endif
fph.nchLastHR = -1;
fph.nchFirstHR = -1;
fph.nchSpanMet = -1;
// advance fph.nDistY
Layout(nchFrom, *pfLargeFont, NULL, FindPositionCallback, &fph);
// if we found a HR that isn't directly above the screen we are paging up
// from account for both "\cr\lf<HR>" and "<HR>\cr\lf
if (!fSmooth && fph.nchLastHR != -1 && fph.nchFirstHR + 4 != m_nchCurrent
&& fph.nchFirstHR + 6 != m_nchCurrent) {
// decide which HR rule to use. If we are paging up then we use the
// first and if we are paging down we use the last
/* CRM temp: trying the heading at a time thing
int nchIndexOfHR = fph.nchLastHR;
*/
// trying out the one heading at a time method
int nchIndexOfHR = fph.nchFirstHR;
// end
bool fLargeFontT = fph.fLargeFontLastHR;
if (fph.nCondition == knFindPosAtLeastY) {
nchIndexOfHR = fph.nchFirstHR;
fLargeFontT = fph.fLargeFontFirstHR;
}
// find <HR>
char szBuffer[20];
gpakr.fseek(m_pfil, nchIndexOfHR, SEEK_SET);
int cchRead = gpakr.fread(szBuffer, 1, sizeof(szBuffer) - 1, m_pfil);
szBuffer[cchRead] = 0;
char *pszFind = strstr(szBuffer, "<HR>");
*pfLargeFont = fLargeFontT;
return nchIndexOfHR + (int)(pszFind + 4 - szBuffer);
} else {
// otherwise we should use the position at which the span was met
if (fph.nchSpanMet != -1) {
*pfLargeFont = fph.fLargeFontSpanMet;
return fph.nchSpanMet;
} else {
return nchFrom;
}
}
// Should get to here because the span should always be met.
Assert(false);
return nchFrom;
}
void HelpControl::DoIndex()
{
m_nchCurrent = 0;
m_fLargeFont = false;
Invalidate();
}
void HelpControl::DoBack()
{
m_nchCurrent = m_nchBack[0];
m_fLargeFont = false;
for(int i = 0; i < 9; i++)
m_nchBack[i] = m_nchBack[i + 1];
Invalidate();
}
bool PaintChunk(int x, int y, int cx, int cy, int nchBuffer, int cyTotal, int nyBottom, Chunk *pChunk, void *pv = NULL)
{
PaintInfo *pinfo = (PaintInfo *)pv;
int *pnchEnd = pinfo->pnchEnd;
if (y < nyBottom) {
*pnchEnd = nchBuffer;
}
if (y + cy >= nyBottom)
return true;
if (pChunk != NULL) {
switch (pChunk->nType) {
case knChunkRawText:
gapfnt[kifntDefault]->DrawText(pChunk->pbm, pChunk->psz, x, y, pChunk->cch);
break;
case knChunkLargeText:
gapfnt[kifntTitle]->DrawText(pChunk->pbm, pChunk->psz, x, y, pChunk->cch);
break;
case knChunkBitmap:
( (TBitmap *)pChunk->pv)->BltTo(pChunk->pbm, x, y);
break;
case knChunkAniData:
{
int index = ( (AnimationData *)pChunk->pv)->GetStripIndex("help");
Rect rc;
( (AnimationData *)pChunk->pv)->GetBounds(index, 0, &rc);
if (rc.Height() + y > nyBottom)
return true;
( (AnimationData *)pChunk->pv)->DrawFrame(index, 0, pChunk->pbm, x - rc.left, y - rc.top, kside1);
}
break;
case knChunkLinkText:
{
// +10 is a hack since nchBuffer points to the anchor tag
if (pinfo->phittest->fHit &&
(pinfo->phittest->nchBuffer == nchBuffer ||
(pinfo->phittest->nchBuffer < nchBuffer &&
pinfo->phittest->nchBuffer + 10 >= nchBuffer))) {
int cx = gapfnt[kifntDefault]->GetTextExtent(pChunk->psz,
pChunk->cch);
int cy = gapfnt[kifntDefault]->GetHeight();
pChunk->pbm->Fill(x, y - 1, cx, cy + 2,
GetColor(kiclrButtonFillHighlight));
}
Color clr = 6;
gapfnt[kifntDefault]->DrawText(pChunk->pbm, pChunk->psz, x, y, pChunk->cch);
// BUG: fix for descender height
// HACK: this will not work properly if the help control doesn't span below half the
// screen.
if (pChunk->cch != 0) {
int descenderAdjust = (nyBottom <= 160? 1: 2);
y += gapfnt[kifntDefault]->GetHeight() - descenderAdjust;
pChunk->pbm->DrawLine(x, y, x + cx, y, clr);
}
}
break;
case knChunkHRTag:
{
// draw three rectangles
// draw center rectangle
int cxDot = PcFromFc(2);
pChunk->pbm->Fill(x + cx * 3 / 8 - cxDot / 2, y + cy / 2 - cxDot / 2, cxDot, cxDot, kiclrWhite);
// draw left rectangle
pChunk->pbm->Fill(x + cx * 4 / 8 - cxDot / 2, y + cy / 2 - cxDot / 2, cxDot, cxDot, kiclrWhite);
// draw right rectangle
pChunk->pbm->Fill(x + cx * 5 / 8 - cxDot / 2, y + cy / 2 - cxDot / 2, cxDot, cxDot, kiclrWhite);
}
break;
}
}
return false;
}
bool HitTestChunk(int x, int y, int cx, int cy, int nchBuffer, int cyTotal, int nyBottom, Chunk *pChunk, void *pv)
{
if (y + cy >= nyBottom)
return true;
Rect rc;
if (pChunk != NULL) {
switch (pChunk->nType) {
case knChunkLinkText: // we only care to hittest text
HitTest *pHitTest = (HitTest *)pv;
rc.Set(x, y, x + cx, y + cy);
int d = rc.GetDistance(pHitTest->x, pHitTest->y);
if (d < pHitTest->dBest) {
pHitTest->dBest = d;
pHitTest->nchBuffer = nchBuffer;
strcpy(pHitTest->szText, pChunk->szText);
pHitTest->cch = (int)strlen(pHitTest->szText);
}
break;
}
}
return false;
}
bool FindPositionCallback(int x, int y, int cx, int cy, int nchBuffer, int cyTotal, int nyBottom, Chunk *pChunk, void *pv)
{
FindPositionHelper *pFPH = (FindPositionHelper*)pv;
#if 0
if (pChunk != NULL && (pChunk->nType == knChunkRawText ||
pChunk->nType == knChunkLargeText)) {
pFPH->fLargeFont = (pChunk->nType == knChunkRawText) ? false : true;
}
#else
if (pChunk != NULL) {
if (pChunk->nType == knChunkLargeText) {
pFPH->fLargeFont = true;
} else {
pFPH->fLargeFont = false;
}
}
#endif
// we're counting the vertical (Y) distance traveled from a given point to
// pFPH->nIndex
if (pFPH->nCondition == knFindPosRunToIndex) {
if(nchBuffer >= pFPH->nIndex) {
pFPH->cySpan = cyTotal;
return true;
}
return false;
}
// keep track of the indexes where we found the first and last HR tags as
// well as what the font was at that HR tag. We do this check after the
// PosRunToIndex check because we may need to break prior to this point.
if (pChunk != NULL && pChunk->nType == knChunkHRTag && cyTotal < pFPH->cyControl) {
if (pFPH->nchFirstHR == -1) {
pFPH->nchFirstHR = nchBuffer;
pFPH->fLargeFontFirstHR = pChunk->fLargeFont;
}
pFPH->nchLastHR = nchBuffer;
pFPH->fLargeFontLastHR = pChunk->fLargeFont;
}
// in these cases we travel until we've traveled the height of the control
// so that we can be sure to find any HR tags we should break at within the
// page.
switch (pFPH->nCondition) {
case knFindPosAtMostY:
// Used when paging forward. Remember index at cySpan (7/8ths
// currently).
if (cy + cyTotal >= pFPH->cySpan && pFPH->nchSpanMet == -1) {
pFPH->nchSpanMet = nchBuffer;
pFPH->fLargeFontSpanMet = pFPH->fLargeFont;
}
// check to see if we've traveled the height of the control
if (cyTotal > pFPH->cyControl) {
pFPH->nIndex = nchBuffer;
return true;
}
break;
case knFindPosAtLeastY:
// Used when implementing page back by doing a forward Layout by
// a specific delta.
if (cyTotal >= pFPH->cySpan && pFPH->nchSpanMet == -1) {
pFPH->nchSpanMet = nchBuffer;
pFPH->fLargeFontSpanMet = pFPH->fLargeFont;
}
// check to see if we've traveled the height of the control
if (cyTotal > pFPH->cyControl && pFPH->nchSpanMet != -1)
return true;
break;
case knFindPosFingerScroll:
pFPH->nchSpanMet = nchBuffer;
pFPH->fLargeFontSpanMet = pFPH->fLargeFont;
return cyTotal >= pFPH->cySpan;
}
return false;
}
//
// Help implementation
//
class HelpForm : public ShellForm
{
public:
HelpForm() secHelpForm;
virtual bool DoModal(const char *pszLink, const char *pszFile) secHelpForm;
virtual void OnControlSelected(word idc) secHelpForm;
virtual bool EventProc(Event *pevt) secHelpForm;
};
void Help(const char *pszAnchor, bool fPauseSimulation, const char *pszFile)
{
word idf;
Size siz;
gpmfrmm->GetDib()->GetSize(&siz);
if (siz.cx >= 480) {
idf = kidfHelpWide;
} else {
idf = kidfHelp;
}
HelpForm *pfrm = (HelpForm *)gpmfrmm->LoadForm(gpiniForms, idf, new HelpForm());
if (pfrm != NULL) {
if (fPauseSimulation)
gsim.Pause(true);
pfrm->DoModal(pszAnchor, pszFile);
if (fPauseSimulation)
gsim.Pause(false);
delete pfrm;
}
}
HelpForm::HelpForm()
{
}
bool HelpForm::DoModal(const char *pszLink, const char *pszFile)
{
HelpControl *pctl = (HelpControl *)GetControlPtr(kidcHelp);
pctl->SetFile(pszFile == NULL ? "help.txt" : pszFile);
if (pszLink != NULL)
pctl->FollowLink(pszLink);
Control *pctlT = GetControlPtr(kidcNextPage);
pctlT->Show(true);
pctlT = GetControlPtr(kidcPrevPage);
pctlT->Show(true);
pctlT = GetControlPtr(kidcBack);
pctlT->Show(true);
int idc;
ShellForm::DoModal(&idc, false);
if (idc == kidcCancel)
return false;
return true;
}
void HelpForm::OnControlSelected(word idc)
{
HelpControl *pctl = (HelpControl *)GetControlPtr(kidcHelp);
switch (idc) {
case kidcPrevPage:
pctl->DoPrevPage();
break;
case kidcNextPage:
pctl->DoNextPage();
break;
case kidcBack:
pctl->DoBack();
break;
case kidcIndex:
pctl->DoIndex();
break;
case kidcOk:
EndForm(idc);
break;
}
}
bool HelpForm::EventProc(Event *pevt)
{
switch (pevt->eType) {
case keyDownEvent:
switch (pevt->chr) {
case chrUp:
OnControlSelected(kidcPrevPage);
return true;
case chrDown:
OnControlSelected(kidcNextPage);
return true;
case vchrBack:
OnControlSelected(kidcOk);
return true;
}
default:
return Form::EventProc(pevt);
}
return false;
}
} // namespace wi