mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-23 06:57:23 +00:00
1118 lines
28 KiB
C++
1118 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;
|
|
}
|
|
default:
|
|
return Form::EventProc(pevt);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace wi
|