#include "ht.h" namespace wi { // Supported tags: //

-- heading on a new line, large font //

-- heading on a new line, normal font //
-- line break //
-- horizontal rule // link text -- a hyperlink // -- a hyperlink target // -- 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, " 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 ", 4) == 0 || strncmp(m_szText, "

", 4) == 0) { // Call back to know if we're stopping since we need to stop before the . 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, "
", 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, "
", 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; } //
link text if (strncmp(m_szText, "') + 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 nchBuffer += pchLinkEnd - m_szText + 4; continue; } // if (strncmp(m_szText, "'); if (pchT != NULL) nchBuffer += (pchT - m_szText) + 1; continue; } // Image? // if (strncmp(m_szText, "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 '; 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
either directly above the current location // or there is a link target above the location with an
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], "
", 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

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], "

", 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
" and "
\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
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, "
"); *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