/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////////////// // // // pfJournalBook Class // // A generic, high-level, abstract method of creating various Myst-like // // books within the game with very little effort, while ensuring that they // // all remain consistent in appearance and operability. // // // ////////////////////////////////////////////////////////////////////////////// #include "pfJournalBook.h" #include "hsUtils.h" #include "hsStlUtils.h" #include "hsResMgr.h" #include "pcSmallRect.h" #include "plgDispatch.h" #include "../pfGameGUIMgr/pfGUIDialogMod.h" #include "../pfGameGUIMgr/pfGUIControlMod.h" #include "../pfGameGUIMgr/pfGUICheckBoxCtrl.h" #include "../pfGameGUIMgr/pfGUIDialogHandlers.h" #include "../pfGameGUIMgr/pfGUIDynDisplayCtrl.h" #include "../pfGameGUIMgr/pfGUIClickMapCtrl.h" #include "../pfGameGUIMgr/pfGUIButtonMod.h" #include "../pfGameGUIMgr/pfGUIProgressCtrl.h" #include "../pfGameGUIMgr/pfGUIMultiLineEditCtrl.h" #include "../pfMessage/pfGUINotifyMsg.h" #include "../plGImage/plMipmap.h" #include "../plGImage/plDynamicTextMap.h" #include "../plPipeline/hsGDeviceRef.h" #include "../plMessage/plAnimCmdMsg.h" #include "../pnKeyedObject/plFixedKey.h" #include "../pnMessage/plRefMsg.h" #include "../pnMessage/plTimeMsg.h" #include "../plMessage/plLayRefMsg.h" #include "../plMessage/plMatRefMsg.h" #include "../plSurface/plLayerInterface.h" #include "../plSurface/plLayer.h" #include "../plSurface/hsGMaterial.h" #include "../plAgeLoader/plAgeLoader.h" #include "../pfSurface/plLayerBink.h" // So we can do image searches in our local age #include "../plNetClient/plNetClientMgr.h" #include "../plResMgr/plKeyFinder.h" // For notify sends #include "../pnMessage/plNotifyMsg.h" #include "../pnTimer/plTimerCallbackManager.h" #include "../plMessage/plTimerCallbackMsg.h" // For custom cursors #include "../plInputCore/plInputInterface.h" // For measuring text #include "../plGImage/plFont.h" // For SFX #include "hsTimer.h" ////////////////////////////////////////////////////////////////////////////// //// pfEsHTMLChunk Class ///////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// class pfEsHTMLChunk { public: std::wstring fText; // Paragraph text, or face name plKey fImageKey; // Key of image UInt8 fFontSize; UInt32 fFlags; UInt8 fType; UInt32 fEventID; pcSmallRect fLinkRect; // Used only for image chunks, and only when stored in the fVisibleLinks array hsColorRGBA fColor; UInt16 fAbsoluteX, fAbsoluteY; hsScalar fCurrOpacity; // For SFX images hsScalar fSFXTime; // For SFX images hsScalar fMinOpacity, fMaxOpacity; hsColorRGBA fCurrColor; hsColorRGBA fOffColor, fOnColor; bool fNoResizeImg; Int16 fLineSpacing; bool fTintDecal; bool fLoopMovie; bool fOnCover; // if true, the movie is on the cover UInt8 fMovieIndex; // the index of the movie in the source code, used for identification enum Flags { kFontBold = 0x00000001, kFontItalic = 0x00000002, kFontRegular= 0x00000004, // 'cause 0 means "style not defined" kFontMask = kFontBold | kFontItalic | kFontRegular, kFontColor = 0x00000008, kFontSpacing= 0x00000010, kCenter = 0x00000001, kLeft = 0x00000002, kRight = 0x00000003, kAlignMask = 0x00000003, kBlendAlpha = 0x00000004, kCanLink = 0x00000008, kFloating = 0x00000010, kGlowing = 0x00000020, kActAsCB = 0x00000040, // Cause the image to act in a checkbox-like fashion. // Min opacity turns into "off opacity" and max opacity // is "on opacity" kChecked = 0x00000080, // Only for kActAsCB, set if it's currently "checked" kTranslucent= 0x00000100 // is the image translucent? if so, use fCurrOpacity }; enum Types { kEmpty = 0, kParagraph, kImage, kPageBreak, kFontChange, kMargin, kCover, // Just a placeholder, never actually used after compile time kBook, // another placeholder kDecal, kMovie, kEditable // placeholder, ver 3.0 }; // Paragraph constructor pfEsHTMLChunk( const wchar_t *text ) { fType = kParagraph; if (text) fText = text; else fText = L""; fFlags = kLeft; fFontSize = 0; fImageKey = nil; fEventID = 0; fColor.Set( 0.f, 0.f, 0.f, 1.f ); fAbsoluteX = fAbsoluteY = 0; fCurrOpacity = 1.f; fMinOpacity = 0.f; fMaxOpacity = 1.f; fNoResizeImg = false; fLineSpacing = 0; fTintDecal = false; fLoopMovie = true; fOnCover = false; fMovieIndex = -1; } // Image constructor (used for decals and movies too) pfEsHTMLChunk( plKey imageKey, UInt32 alignFlags ) { fType = kImage; fText = L""; fFlags = alignFlags; fFontSize = 0; fImageKey = imageKey; fEventID = 0; fColor.Set( 0.f, 0.f, 0.f, 1.f ); fAbsoluteX = fAbsoluteY = 0; fCurrOpacity = 1.f; fMinOpacity = 0.f; fMaxOpacity = 1.f; fNoResizeImg = false; fLineSpacing = 0; fTintDecal = false; fLoopMovie = true; fOnCover = false; fMovieIndex = -1; } // Page break constructor pfEsHTMLChunk() { fType = kPageBreak; fText = L""; fImageKey = nil; fFontSize = 0; fFlags = 0; fEventID = 0; fColor.Set( 0.f, 0.f, 0.f, 1.f ); fAbsoluteX = fAbsoluteY = 0; fCurrOpacity = 1.f; fMinOpacity = 0.f; fMaxOpacity = 1.f; fNoResizeImg = false; fLineSpacing = 0; fTintDecal = false; fLoopMovie = true; fOnCover = false; fMovieIndex = -1; } // Font change constructor pfEsHTMLChunk( const wchar_t *face, UInt8 size, UInt32 fontFlags ) { fType = kFontChange; if (face) fText = face; else fText = L""; fFontSize = size; fFlags = fontFlags; fImageKey = nil; fEventID = 0; fColor.Set( 0.f, 0.f, 0.f, 1.f ); fAbsoluteX = fAbsoluteY = 0; fCurrOpacity = 1.f; fMinOpacity = 0.f; fMaxOpacity = 1.f; fNoResizeImg = false; fLineSpacing = 0; fTintDecal = false; fLoopMovie = true; fOnCover = false; fMovieIndex = -1; } ~pfEsHTMLChunk() {} }; ////////////////////////////////////////////////////////////////////////////// //// Our Template Dialog Handler ///////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// class pfJournalDlgProc : public pfGUIDialogProc { protected: pfBookData *fBook; public: enum TagIDs { kTagBookCover = 100, kTagLeftDTMap = 101, kTagRightDTMap = 102, kTagTurnFrontDTMap = 103, kTagTurnBackDTMap = 104, kTagTurnPageCtrl = 105, kTagLeftPageBtn = 106, kTagRightPageBtn = 107, kTagOutsideBookBtn = 108, kTagCoverLayer = 109, kTagLeftCornerBtn = 110, kTagRightCornerBtn = 111, kTagWidthCBDummy = 112, kTagHeightCBDummy = 113, kTagLeftEditCtrl = 120, kTagRightEditCtrl = 121, kTagTurnFrontEditCtrl = 122, kTagTurnBackEditCtrl = 123 }; pfJournalDlgProc( pfBookData *book ) : fBook( book ) { } virtual ~pfJournalDlgProc() { } virtual void DoSomething( pfGUIControlMod *ctrl ) { if ( fBook && fBook->fCurrBook ) { if( ctrl->GetTagID() == kTagBookCover ) { fBook->fCurrBook->Open(); } else if( ctrl->GetTagID() == kTagLeftPageBtn ) { // only turn pages if the book is actually open if( fBook->fCurrentlyOpen ) fBook->fCurrBook->IHandleLeftSideClick(); } else if( ctrl->GetTagID() == kTagRightPageBtn ) { // only turn pages if the book is actually open if( fBook->fCurrentlyOpen ) fBook->fCurrBook->IHandleRightSideClick(); } else if( ctrl->GetTagID() == kTagOutsideBookBtn ) { if( fBook->fCurrentlyOpen ) fBook->fCurrBook->CloseAndHide(); else fBook->fCurrBook->Hide(); } } } // Called on dialog init (i.e. first showing, before OnShow() is called), only ever called once virtual void OnInit( void ) { } // Called before the dialog is shown, always after OnInit() virtual void OnShow( void ) { } // Called before the dialog is hidden virtual void OnHide( void ) { } // Called on the dialog's destructor, before it's unregistered with the game GUI manager virtual void OnDestroy( void ) { } // Called when the dialog's focused control changes virtual void OnCtrlFocusChange( pfGUIControlMod *oldCtrl, pfGUIControlMod *newCtrl ) { } // Called when the key bound to a GUI event is pressed. Only called on the top modal dialog virtual void OnControlEvent( ControlEvt event ) { if( event == kExitMode ) { if( fBook->fCurrentlyOpen ) fBook->fCurrBook->CloseAndHide(); else fBook->fCurrBook->Hide(); } } virtual void HandleExtendedEvent( pfGUIControlMod *ctrl, UInt32 event ) { if (fBook) { if( ctrl == fBook->fLeftPageMap ) { if( event == pfGUIClickMapCtrl::kMouseHovered ) { if (fBook->fCurrBook) { // Update our custom cursor on the map Int32 idx = fBook->fCurrBook->IFindCurrVisibleLink( false, true ); if( idx != -1 ) fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorPoised/*Hand*/ ); else if(( fBook->fCurrBook->fCurrentPage > 1 )&&( fBook->fCurrBook->fAllowTurning )) fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorLeft ); else if ((fBook->fCurrBook->fAreEditing) && !(fBook->fLeftEditCtrl->ShowingBeginningOfBuffer())) // if we have more buffer to show fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorLeft ); else fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorUp ); } } } else if( ctrl == fBook->fRightPageMap ) { if( event == pfGUIClickMapCtrl::kMouseHovered ) { if (fBook->fCurrBook) { // Update our custom cursor on the map Int32 idx = fBook->fCurrBook->IFindCurrVisibleLink( true, true ); if( idx != -1 ) fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorPoised/*Hand*/ ); else if((fBook->fCurrBook->fAreWeShowing) && ( fBook->fCurrBook->fCurrentPage + 2 <= fBook->fCurrBook->fLastPage )&&( fBook->fCurrBook->fAllowTurning )) fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorRight ); else if((fBook->fCurrBook->fAreEditing) && !(fBook->fRightEditCtrl->ShowingEndOfBuffer())) // if we have more buffer to show fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorRight ); else fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorUp ); } } } } } }; //// Multiline edit handler class //////////////////////////////////////////// class pfBookMultiLineEditProc : public pfGUIMultiLineEditProc { private: pfBookData *bookData; public: pfBookMultiLineEditProc(pfBookData *owner) { bookData = owner; } virtual ~pfBookMultiLineEditProc() {} virtual OnEndOfControlList(Int32 cursorPos) { bookData->HitEndOfControlList(cursorPos); } virtual OnBeginningOfControlList(Int32 cursorPos) { bookData->HitBeginningOfControlList(cursorPos); } }; //// Book data class ///////////////////////////////////////////////////////// pfBookData::pfBookData(const char *guiName /* = nil */) { fCurrBook = nil; fDialog = nil; fCoverButton = fTurnPageButton = nil; fLeftPageMap = fRightPageMap = nil; fCoverLayer = nil; fCoverMaterial = nil; UInt16 i; for (i=0; i<4; i++) fPageMaterials[i] = nil; fLeftCorner = fRightCorner = nil; fWidthCtrl = fHeightCtrl = nil; fCurrSFXPages = kNoSides; fBaseSFXTime = 0.f; fResetSFXFlag = false; fSFXUpdateFlip = false; fCurrentlyTurning = false; fRightEditCtrl = fLeftEditCtrl = nil; fTurnFrontEditCtrl = fTurnBackEditCtrl = nil; fEditable = false; fAdjustCursorTo = -1; if (guiName) fGUIName = guiName; else fGUIName = "BkBook"; } pfBookData::~pfBookData() { RegisterForSFX( kNoSides ); } void pfBookData::LoadGUI() { // has the dialog been loaded yet? if (!pfGameGUIMgr::GetInstance()->IsDialogLoaded(fGUIName.c_str())) // no then load and set handler pfGameGUIMgr::GetInstance()->LoadDialog(fGUIName.c_str(), GetKey(), "GUI"); else // yes then just set the handler pfGameGUIMgr::GetInstance()->SetDialogToNotify(fGUIName.c_str(), GetKey()); } hsBool pfBookData::MsgReceive(plMessage *pMsg) { plGenRefMsg *ref = plGenRefMsg::ConvertNoRef(pMsg); if(ref != nil) { if(ref->fType == kRefDialog) { if(ref->GetContext() & (plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace)) { pfGUIDialogMod *temp = pfGUIDialogMod::ConvertNoRef(ref->GetRef()); if (temp != nil) // sanity check fDialog = temp; } /*else { fDialog = nil; fCoverButton = nil; }*/ return true; } else if(ref->fType == kRefDefaultCover) { if(ref->GetContext() & (plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace)) fDefaultCover = plMipmap::ConvertNoRef(ref->GetRef()); else fDefaultCover = nil; return true; } } plEventCallbackMsg *callback = plEventCallbackMsg::ConvertNoRef( pMsg ); if( callback != nil ) { // Our callback message to tell us the page is done flipping if( callback->fUser & 0x08 ) { // make sure that we still have a current book if (fCurrBook) { // Or actually maybe it's that we're done closing and should hide now, // produced from a CloseAndHide() if( callback->fEvent == kStop ) fCurrBook->Hide(); else fCurrBook->IFinishShow( ( callback->fUser & 0x01 ) ? true : false ); } } else if( fCurrentlyTurning ) { if( callback->fUser & 0x04 ) IFillUncoveringPage( (hsBool)( callback->fUser & 0x01 ) ? true : false ); else if( callback->fUser & 0x02 ) StartTriggeredFlip( (hsBool)( callback->fUser & 0x01 ) ? true : false ); else IFinishTriggeredFlip( (hsBool)( callback->fUser & 0x01 ) ? true : false ); } return true; } pfGUINotifyMsg *notify = pfGUINotifyMsg::ConvertNoRef(pMsg); if(notify != nil) { // The only time we should get this is when the dialog loads; after that, we hijack // the dialog proc with our own IInitTemplate(pfGUIDialogMod::ConvertNoRef(notify->GetSender()->ObjectIsLoaded())); return true; } plTimeMsg *time = plTimeMsg::ConvertNoRef( pMsg ); if( time != nil && fCurrSFXPages != kNoSides && !fCurrentlyTurning && fCurrentlyOpen ) { IHandleSFX( (hsScalar)time->DSeconds() ); return true; } plTimerCallbackMsg* timerMsg = plTimerCallbackMsg::ConvertNoRef(pMsg); if (timerMsg) { if (timerMsg->fID == 99) // the flip animation is about to end, hide the page to prevent flickering { // the right side was uncovered, so the left side needs to be hidden fLeftEditCtrl->SetVisible(false); } else if (timerMsg->fID == 98) { // the left side was uncovered, so the right side needs to be hidden fRightEditCtrl->SetVisible(false); } } return hsKeyedObject::MsgReceive(pMsg); } void pfBookData::IInitTemplate(pfGUIDialogMod *templateDlg) { hsAssert(templateDlg != nil, "Nil template in pfBookData::IInitTemplate()!"); // Init and ref our fDialog pointer hsgResMgr::ResMgr()->SendRef(templateDlg->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefDialog), plRefFlags::kPassiveRef); // Hijack the dialog proc with our own templateDlg->SetHandlerForAll(TRACKED_NEW pfJournalDlgProc(this)); // Find our animation keys // And other interesting pointers fCoverButton = pfGUICheckBoxCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagBookCover)); fTurnPageButton = pfGUICheckBoxCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnPageCtrl)); fLeftPageMap = pfGUIClickMapCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftPageBtn)); fRightPageMap = pfGUIClickMapCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightPageBtn)); fLeftCorner = pfGUIButtonMod::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftCornerBtn)); fRightCorner = pfGUIButtonMod::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightCornerBtn)); fWidthCtrl = pfGUIProgressCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagWidthCBDummy)); fHeightCtrl = pfGUIProgressCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagHeightCBDummy)); fTurnPageButton->SetEnabled(false); fCoverButton->DontPlaySounds(); // dont let checkbox play sounds, journal will take care of that. fCoverLayer = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagCoverLayer))->GetLayer(0); fCoverMaterial = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagCoverLayer))->GetMaterial(0); fPageMaterials[kLeftPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftDTMap))->GetMaterial(0); fPageMaterials[kRightPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightDTMap))->GetMaterial(0); fPageMaterials[kTurnFrontPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnFrontDTMap))->GetMaterial(0); fPageMaterials[kTurnBackPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnBackDTMap))->GetMaterial(0); // Grab and ref the default cover mipmap plLayer *lay = plLayer::ConvertNoRef(fCoverLayer); if((lay != nil)&&(lay->GetTexture() != nil)) hsgResMgr::ResMgr()->AddViaNotify(lay->GetTexture()->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefDefaultCover), plRefFlags::kPassiveRef); fLeftPageMap->SetFlag(pfGUIClickMapCtrl::kReportHovering); fRightPageMap->SetFlag(pfGUIClickMapCtrl::kReportHovering); fLeftEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftEditCtrl)); if (fLeftEditCtrl) { fLeftEditCtrl->SetEnabled(false); // disable the edit controls initially, we can turn them on later fLeftEditCtrl->SetVisible(false); } fRightEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightEditCtrl)); if (fRightEditCtrl) { fRightEditCtrl->SetEnabled(false); fRightEditCtrl->SetVisible(false); } fTurnFrontEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnFrontEditCtrl)); if (fTurnFrontEditCtrl) { fTurnFrontEditCtrl->SetEnabled(false); fTurnFrontEditCtrl->SetVisible(false); } fTurnBackEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnBackEditCtrl)); if (fTurnBackEditCtrl) { fTurnBackEditCtrl->SetEnabled(false); fTurnBackEditCtrl->SetVisible(false); } // if all the edit controls are here, we are editable, so set up the initial link if (fLeftEditCtrl && fRightEditCtrl && fTurnFrontEditCtrl && fTurnBackEditCtrl) { fEditable = true; fLeftEditCtrl->SetNext(fRightEditCtrl); fLeftEditCtrl->SetEventProc(TRACKED_NEW pfBookMultiLineEditProc(this)); fTurnFrontEditCtrl->SetNext(fTurnBackEditCtrl); // these are always back to back } } //// IGetDTMap /////////////////////////////////////////////////////////////// // Just a quick helper plDynamicTextMap *pfBookData::GetDTMap(UInt32 which) { pfGUIDynDisplayCtrl *display = pfGUIDynDisplayCtrl::ConvertNoRef(fDialog->GetControlFromTag(which)); return display->GetMap(0); } //// GetEditCtrl ///////////////////////////////////////////////////////////// pfGUIMultiLineEditCtrl *pfBookData::GetEditCtrl(UInt32 which) { switch (which) { case pfJournalDlgProc::kTagLeftEditCtrl: return fLeftEditCtrl; case pfJournalDlgProc::kTagRightEditCtrl: return fRightEditCtrl; case pfJournalDlgProc::kTagTurnFrontEditCtrl: return fTurnFrontEditCtrl; case pfJournalDlgProc::kTagTurnBackEditCtrl: return fTurnBackEditCtrl; default: return nil; } } //// IRegisterForSFX ///////////////////////////////////////////////////////// // Registers (or unregisters) for time messages so we can process special FX // if we need to void pfBookData::RegisterForSFX(WhichSide whichPages) { if( whichPages == fCurrSFXPages) return; if(whichPages != kNoSides) { plgDispatch::Dispatch()->RegisterForExactType(plTimeMsg::Index(), GetKey()); } else plgDispatch::Dispatch()->UnRegisterForExactType(plTimeMsg::Index(), GetKey()); fCurrSFXPages = whichPages; } //// IHandleSFX ////////////////////////////////////////////////////////////// // Process SFX for this frame. Note: if ANYTHING is wrong (page starts not // calced, pointers bad, etc) just bail, since the SFX are just for visual // flair and not really needed. void pfBookData::IHandleSFX(hsScalar currTime, WhichSide whichSide /*= kNoSides*/) { if(fCurrBook == nil) return; if(whichSide == kNoSides) { if(fResetSFXFlag) { fBaseSFXTime=currTime; fResetSFXFlag=false; } fSFXUpdateFlip = !fSFXUpdateFlip; // Slightly recursive here to help us out a bit if(fSFXUpdateFlip&&(fCurrSFXPages & kLeftSide)) IHandleSFX(currTime, kLeftSide); else if(!fSFXUpdateFlip &&(fCurrSFXPages & kRightSide)) IHandleSFX(currTime, kRightSide); return; } // Update all SFX images for this page first hsScalar deltaT = currTime - fBaseSFXTime; UInt32 idx, inc = (whichSide == kLeftSide) ? 0 : 1; if(fCurrBook->fPageStarts.GetCount() <= fCurrBook->fCurrentPage + inc + 1) return; bool stillWant = false; for(idx = fCurrBook->fPageStarts[fCurrBook->fCurrentPage + inc]; idx < fCurrBook->fPageStarts[fCurrBook->fCurrentPage + inc + 1]; idx++) { pfEsHTMLChunk *chunk = fCurrBook->fHTMLSource[idx]; if(chunk->fFlags & pfEsHTMLChunk::kGlowing) { // Glow SFX: animate opacity based on time offset UInt8 isOdd = 0; hsScalar newDelta = deltaT; while(newDelta > chunk->fSFXTime) { isOdd = ~isOdd; newDelta -= chunk->fSFXTime; } // If we're not odd, then we're decreasing in opacity, else we're increasing if(isOdd) newDelta = chunk->fSFXTime - newDelta; chunk->fCurrOpacity = chunk->fMaxOpacity - ((chunk->fMaxOpacity - chunk->fMinOpacity)*(newDelta / chunk->fSFXTime)); stillWant = true; } else if(chunk->fFlags & pfEsHTMLChunk::kActAsCB) { // If our opacity doesn't match our checked state, slowly fade to it hsColorRGBA inc; inc.Set(0.1f, 0.1f, 0.1f, 0.1f); hsColorRGBA &want = (chunk->fFlags & pfEsHTMLChunk::kChecked) ? chunk->fOnColor : chunk->fOffColor; if(want != chunk->fCurrColor) { #define COMPARE_ME( wnt, curr ) \ if( wnt > curr + 0.1f ) \ { \ curr += 0.1f; stillWant = true; \ } \ else if( wnt < curr - 0.1f ) \ { \ curr -= 0.1f; stillWant = true; \ } \ else \ curr = wnt; COMPARE_ME( want.r, chunk->fCurrColor.r ) COMPARE_ME( want.g, chunk->fCurrColor.g ) COMPARE_ME( want.b, chunk->fCurrColor.b ) COMPARE_ME( want.a, chunk->fCurrColor.a ) } } } // All done updating that page. Now render it! fCurrBook->IRenderPage( fCurrBook->fCurrentPage + inc, ( whichSide == kLeftSide ) ? pfJournalDlgProc::kTagLeftDTMap : pfJournalDlgProc::kTagRightDTMap ); if( !stillWant ) { // Done with FX for this page, so unregister for FX on this page now RegisterForSFX((WhichSide)(fCurrSFXPages & ~whichSide)); } } //// IFillUncoveringPage ///////////////////////////////////////////////////// // Yet another step in the page flip, to make SURE we're already showing the // turning page before we fill in the page behind it void pfBookData::IFillUncoveringPage(hsBool rightSide) { // only show the turning page if the book is open if ( CurrentlyOpen() ) fTurnPageButton->SetVisible(true); // make sure there is a current book if (fCurrBook) { if (fCurrBook->fAreEditing) { int id; UpdatePageCorners(rightSide ? kRightSide : kLeftSide); if (rightSide) { fTurnBackEditCtrl->ForceUpdate(); // force everything that is changing to update fTurnBackEditCtrl->SetVisible(true); // and make sure everything is showing fTurnFrontEditCtrl->ForceUpdate(); fTurnFrontEditCtrl->SetVisible(true); fRightEditCtrl->ForceUpdate(); fRightEditCtrl->SetVisible(true); // The left edit ctrl doesn't update until the page flip animation is done id = 99; } else { fTurnFrontEditCtrl->ForceUpdate(); fTurnFrontEditCtrl->SetVisible(true); fTurnBackEditCtrl->ForceUpdate(); fTurnBackEditCtrl->SetVisible(true); fLeftEditCtrl->ForceUpdate(); fLeftEditCtrl->SetVisible(true); // The right edit ctrl doesn't update until the page flip animation is done id = 98; } // create a timer so we can hide the old left or right turn page right before the animation finishes to prevent flicker plTimerCallbackMsg* pTimerMsg = TRACKED_NEW plTimerCallbackMsg(GetKey(),id); plgTimerCallbackMgr::NewTimer( .5, pTimerMsg ); // .5 found by trial and error return; // the gui controls render everything for us, so ignoring this request } if(rightSide) fCurrBook->IRenderPage(fCurrBook->fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap); else fCurrBook->IRenderPage(fCurrBook->fCurrentPage, pfJournalDlgProc::kTagLeftDTMap); } // Update the page corner we're flipping away from UpdatePageCorners(rightSide ? kRightSide : kLeftSide); } //// ITriggerPageFlip //////////////////////////////////////////////////////// // Triggers the start of the page-flipping animation, as well as sets up the callback for when it's finished void pfBookData::ITriggerPageFlip(hsBool flipBackwards, hsBool immediate) { // Hack here: since we don't have an official interface to select these directly // in MAX, we just use a GUI check box to grab them for us, even though we never // actually use the functionality of the checkbox itself const hsTArray &keys = fTurnPageButton->GetAnimationKeys(); const char *animName = fTurnPageButton->GetAnimationName(); plAnimCmdMsg *msg = TRACKED_NEW plAnimCmdMsg(); if (immediate) { msg->SetCmd(plAnimCmdMsg::kGoToEnd); } else { msg->SetCmd(plAnimCmdMsg::kContinue); msg->SetCmd(plAnimCmdMsg::kSetForewards); } msg->SetAnimName(flipBackwards ? "backward" : "forward"); msg->AddReceivers(keys); // Here's the whole reason why we're not just checking the checkbox: so we can attach a callback // so we know when the animation completes. Pretty sad, huh? Poor checkbox. plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg; eventMsg->AddReceiver(GetKey()); eventMsg->fRepeats = 0; if (immediate) { eventMsg->fUser = ((!flipBackwards) ? 0x01 : 0x00) | 0x02; eventMsg->fEvent = kSingleFrameAdjust; } else { eventMsg->fUser = (flipBackwards ? 0x01 : 0x00); eventMsg->fEvent = kStop; } msg->SetCmd(plAnimCmdMsg::kAddCallbacks); msg->AddCallback(eventMsg); hsRefCnt_SafeUnRef(eventMsg); if (!immediate) { // We want a second callback to tell us when, indeed, the page has started turning // and is thus visible and thus we can actually, really, honestly, safely fill in the // page behind it eventMsg = TRACKED_NEW plEventCallbackMsg; eventMsg->AddReceiver(GetKey()); eventMsg->fRepeats = 0; eventMsg->fUser = !flipBackwards ? (0x04 | 0x01) : 0x04; eventMsg->fEvent = kBegin; // Should cause it to be triggered once it seeks at the start of the command msg->AddCallback(eventMsg); hsRefCnt_SafeUnRef(eventMsg); } fCurrentlyTurning = true; msg->Send(); } //// StartTriggeredFlip ///////////////////////////////////////////////////// // Finishes the start of the triggered page flip (once we're sure the // animation is at the new frame) void pfBookData::StartTriggeredFlip(hsBool flipBackwards) { if(flipBackwards) { ITriggerPageFlip(true, false); } else { ITriggerPageFlip(false, false); } } //// Kill the page flipping cause, we're closing the book void pfBookData::KillPageFlip() { if ( fCurrentlyTurning ) { //ITriggerPageFlip(false, true); fTurnPageButton->SetVisible(false); } } //// IFinishTriggeredFlip //////////////////////////////////////////////////// // Finishes the triggered page flip, on callback void pfBookData::IFinishTriggeredFlip(hsBool wasBackwards) { if (fCurrBook && fCurrBook->fAreEditing) // this is handled differently when we are editing { fLeftEditCtrl->SetNext(fRightEditCtrl); // relink the original path if (!wasBackwards) { // adjust the starting point of the control (not needed if we weren't backwards since that was done when we started turning Int32 newStart = fRightEditCtrl->GetLastVisibleLine(); fLeftEditCtrl->SetGlobalStartLine(newStart); } if (fAdjustCursorTo >= 0) { if (wasBackwards) { fRightEditCtrl->SetCursorToLoc(fAdjustCursorTo); fRightEditCtrl->GetOwnerDlg()->SetFocus(fRightEditCtrl); } else { fLeftEditCtrl->SetCursorToLoc(fAdjustCursorTo); fLeftEditCtrl->GetOwnerDlg()->SetFocus(fLeftEditCtrl); } fAdjustCursorTo = -1; } fTurnFrontEditCtrl->SetVisible(false); // hide the controls fTurnBackEditCtrl->SetVisible(false); fLeftEditCtrl->SetVisible(true); fRightEditCtrl->SetVisible(true); fLeftEditCtrl->ForceUpdate(); fRightEditCtrl->ForceUpdate(); } else if(wasBackwards) { // Grab the DTMaps for the front of the flip page and the right page, so we can // copy the front into the right page plDynamicTextMap *turnFront = GetDTMap(pfJournalDlgProc::kTagTurnFrontDTMap); plDynamicTextMap *right = GetDTMap(pfJournalDlgProc::kTagRightDTMap); // right->Swap( turnFront ); if ( turnFront->IsValid() && right->IsValid() ) { memcpy(right->GetImage(), turnFront->GetImage(), right->GetLevelSize(0)); if(right->GetDeviceRef() != nil) right->GetDeviceRef()->SetDirty(true); } // we are going to attempt to re-render the left-hand page // sometimes, the book stutters on a page flip and screws up the book if (fCurrBook) { fCurrBook->IRenderPage(fCurrBook->fCurrentPage, pfJournalDlgProc::kTagLeftDTMap); // move the videos over fCurrBook->IMoveMovies(PageMaterial(kTurnFrontPage),PageMaterial(kRightPage)); } } else { // Grab the DTMaps for the back of the flip page and the left page, so we can // copy the back into the left page plDynamicTextMap *turnBack = GetDTMap(pfJournalDlgProc::kTagTurnBackDTMap); plDynamicTextMap *left = GetDTMap(pfJournalDlgProc::kTagLeftDTMap); // right->Swap( turnFront ); if ( turnBack->IsValid() && left->IsValid() ) { memcpy(left->GetImage(), turnBack->GetImage(), left->GetLevelSize(0)); if(left->GetDeviceRef() != nil) left->GetDeviceRef()->SetDirty(true); } // we are going to attempt to re-render the right-hand page // sometimes, the book stutters on a page flip and screws up the book if (fCurrBook) { fCurrBook->IRenderPage(fCurrBook->fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap); // move the videos over fCurrBook->IMoveMovies(PageMaterial(kTurnBackPage),PageMaterial(kLeftPage)); } } // Hide our page flipping button/checkbox/whatever fTurnPageButton->SetVisible(false); // Update page corners UpdatePageCorners(kBothSides); fCurrentlyTurning = false; // Start our FX once the page is done turning fResetSFXFlag = true; } //// UpdatePageCorners ////////////////////////////////////////////////////// // Enables or disables the left and right page corners, to indicate current turnage state void pfBookData::UpdatePageCorners(WhichSide which) { // make sure there is a book to update! if (fCurrBook) { if (!fCurrBook->fAllowTurning || !fCurrBook->fAreWeShowing) { fLeftCorner->SetVisible(false); fLeftCorner->SetEnabled(false); fRightCorner->SetVisible(false); fRightCorner->SetEnabled(false); return; } if((which == kLeftSide)||(which == kBothSides)) { if (fCurrBook->fAreEditing) fLeftCorner->SetVisible(!fLeftEditCtrl->ShowingBeginningOfBuffer()); // only show if the control is not viewing the beginning of the buffer else fLeftCorner->SetVisible((fCurrBook->fCurrentPage >= 2) ? true : false); // Note: always disabled (we just go on the page click itself) fLeftCorner->SetEnabled(false); } if((which == kRightSide)||(which == kBothSides)) { if (fCurrBook->fAreEditing) fRightCorner->SetVisible(!fRightEditCtrl->ShowingEndOfBuffer()); // only show if the control is not viewing the end of the buffer else fRightCorner->SetVisible((fCurrBook->fCurrentPage + 2 <= fCurrBook->fLastPage) ? true : false); fRightCorner->SetEnabled(false); } } } //// SetCurrSize //////////////////////////////////////////////////////////// // Seeks the width and height animations to set the desired book size. Sizes are in % across the animation void pfBookData::SetCurrSize(hsScalar w, hsScalar h) { fWidthCtrl->SetCurrValue(w); fHeightCtrl->SetCurrValue(h); } //// PlayBookCloseAnim ////////////////////////////////////////////////////// // Triggers our animation for closing or opening the book. void pfBookData::PlayBookCloseAnim(hsBool closeIt /*= true*/, hsBool immediate /*= false*/) { // Disable the book cover button if we're opening, enable otherwise fCoverButton->SetEnabled(closeIt); // Tell our cover button to check or uncheck fCoverButton->SetChecked(!closeIt, immediate); // Trigger the open (or close) sound if(!immediate) fCoverButton->PlaySound(closeIt ? pfGUICheckBoxCtrl::kMouseUp : pfGUICheckBoxCtrl::kMouseDown); fCurrentlyOpen = !closeIt; } //// Event routines from a linked multi-line edit control //////////////////// void pfBookData::HitEndOfControlList(Int32 cursorPos) { fAdjustCursorTo = cursorPos; if (fCurrBook) fCurrBook->NextPage(); } void pfBookData::HitBeginningOfControlList(Int32 cursorPos) { fAdjustCursorTo = cursorPos; if (fCurrBook) fCurrBook->PreviousPage(); } void pfBookData::EnableEditGUI(hsBool enable/* =true */) { if (fEditable) { fLeftEditCtrl->SetEnabled(enable); fLeftEditCtrl->SetVisible(enable); fRightEditCtrl->SetEnabled(enable); fRightEditCtrl->SetVisible(enable); // we don't make these editable because they are temps used for the turning page fTurnFrontEditCtrl->SetVisible(false); // we don't want these visible initially either fTurnBackEditCtrl->SetVisible(false); } } //// Our Singleton Stuff ///////////////////////////////////////////////////// //pfJournalBook *pfJournalBook::fInstance = nil; std::map pfJournalBook::fBookGUIs; void pfJournalBook::SingletonInit( void ) { fBookGUIs["BkBook"] = TRACKED_NEW pfBookData(); // load the default book data object hsgResMgr::ResMgr()->NewKey("BkBook",fBookGUIs["BkBook"],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation()); fBookGUIs["BkBook"]->LoadGUI(); } void pfJournalBook::SingletonShutdown( void ) { std::map::iterator i = fBookGUIs.begin(); while (i != fBookGUIs.end()) { pfBookData *bookData = i->second; bookData->GetKey()->UnRefObject(); i->second = nil; i++; } fBookGUIs.clear(); } void pfJournalBook::LoadGUI( const char *guiName ) { if (fBookGUIs.find(guiName) == fBookGUIs.end()) // is it already loaded? { // nope, load it fBookGUIs[guiName] = TRACKED_NEW pfBookData(guiName); hsgResMgr::ResMgr()->NewKey(guiName,fBookGUIs[guiName],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation()); fBookGUIs[guiName]->LoadGUI(); } } void pfJournalBook::UnloadGUI( const char *guiName ) { if (strcmp(guiName,"BkBook")==0) return; // do not allow people to unload the default book gui std::map::iterator loc = fBookGUIs.find(guiName); if (loc != fBookGUIs.end()) // make sure it's loaded { fBookGUIs[guiName]->GetKey()->UnRefObject(); fBookGUIs[guiName] = nil; fBookGUIs.erase(loc); } } void pfJournalBook::UnloadAllGUIs() { std::map::iterator i = fBookGUIs.begin(); std::vector names; while (i != fBookGUIs.end()) { std::string name = i->first; names.push_back(name); // store a list of keys i++; } int idx; for (idx = 0; idx < names.size(); idx++) UnloadGUI(names[idx].c_str()); // UnloadGUI won't unload BkBook } //// Constructor ///////////////////////////////////////////////////////////// // The constructor takes in the esHTML source for the journal, along with // the name of the mipmap to use as the cover of the book. The callback // key is the keyed object to send event messages to (see tag). pfJournalBook::pfJournalBook( const char *esHTMLSource, plKey coverImageKey, plKey callbackKey /*= nil*/, const plLocation &hintLoc /* = plLocation::kGlobalFixedLoc */, const char *guiName /* = nil */ ) { if (guiName && (strcmp(guiName,"") != 0)) fCurBookGUI = guiName; else fCurBookGUI = "BkBook"; if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end()) { fBookGUIs[fCurBookGUI] = TRACKED_NEW pfBookData(fCurBookGUI.c_str()); hsgResMgr::ResMgr()->NewKey(fCurBookGUI.c_str(),fBookGUIs[fCurBookGUI],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation()); fBookGUIs[fCurBookGUI]->LoadGUI(); } fCurrentPage = 0; fLastPage = -1; fCoverMipKey = coverImageKey; fCoverFromHTML = false; fCallbackKey = callbackKey; fWidthScale = fHeightScale = 0.f; fPageTMargin = fPageLMargin = fPageBMargin = fPageRMargin = 16; fAllowTurning = true; fAreWeShowing = false; fCoverTint.Set( 0.f, 0.f, 0.f, 1.f ); fTintFirst = true; fTintCover = false; fAreEditing = false; fWantEditing = false; fDefLoc = hintLoc; wchar_t *wESHTMLSource = hsStringToWString(esHTMLSource); fUncompiledSource = wESHTMLSource; ICompileSource( wESHTMLSource, hintLoc ); delete [] wESHTMLSource; } pfJournalBook::pfJournalBook( const wchar_t *esHTMLSource, plKey coverImageKey, plKey callbackKey /*= nil*/, const plLocation &hintLoc /* = plLocation::kGlobalFixedLoc */, const char *guiName /* = nil */ ) { if (guiName && (strcmp(guiName,"") != 0)) fCurBookGUI = guiName; else fCurBookGUI = "BkBook"; if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end()) { fBookGUIs[fCurBookGUI] = TRACKED_NEW pfBookData(fCurBookGUI.c_str()); hsgResMgr::ResMgr()->NewKey(fCurBookGUI.c_str(),fBookGUIs[fCurBookGUI],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation()); fBookGUIs[fCurBookGUI]->LoadGUI(); } fCurrentPage = 0; fLastPage = -1; fCoverMipKey = coverImageKey; fCoverFromHTML = false; fCallbackKey = callbackKey; fWidthScale = fHeightScale = 0.f; fPageTMargin = fPageLMargin = fPageBMargin = fPageRMargin = 16; fAllowTurning = true; fAreWeShowing = false; fCoverTint.Set( 1.f, 1.f, 1.f, 1.f ); fTintFirst = true; fTintCover = false; fAreEditing = false; fWantEditing = false; fDefLoc = hintLoc; fUncompiledSource = esHTMLSource; ICompileSource( esHTMLSource, hintLoc ); } pfJournalBook::~pfJournalBook() { if (fBookGUIs.find(fCurBookGUI) != fBookGUIs.end()) // it might have been deleted before we got here if( fBookGUIs[fCurBookGUI] && fBookGUIs[fCurBookGUI]->CurBook() == this ) Hide(); IFreeSource(); } //// MsgReceive ////////////////////////////////////////////////////////////// hsBool pfJournalBook::MsgReceive( plMessage *pMsg ) { return hsKeyedObject::MsgReceive( pMsg ); } void pfJournalBook::SetGUI( const char *guiName ) { if (guiName && (strcmp(guiName,"") != 0)) fCurBookGUI = guiName; if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end()) fCurBookGUI = "BkBook"; // requested GUI isn't loaded, so use default GUI SetEditable(fWantEditing); // make sure that if we want editing, to set it ICompileSource(fUncompiledSource.c_str(), fDefLoc); // recompile the source to be safe } //// Show //////////////////////////////////////////////////////////////////// // Shows the book, optionally starting open or closed void pfJournalBook::Show( hsBool startOpened /*= false */) { fBookGUIs[fCurBookGUI]->StartedOpen(startOpened); fBookGUIs[fCurBookGUI]->CurBook(this); fBookGUIs[fCurBookGUI]->SetCurrSize(fWidthScale, fHeightScale); ILoadAllImages( false ); hsGMaterial *cover = fBookGUIs[fCurBookGUI]->CoverMaterial(); if( cover != nil ) { hsTArray layers; plMipmap *mip = fCoverMipKey ? plMipmap::ConvertNoRef( fCoverMipKey->ObjectIsLoaded() ) : nil; if( mip != nil ) { layers.Append(IMakeBaseLayer(mip)); int i; for (i=0; ifType == pfEsHTMLChunk::kDecal) { plMipmap *decal = plMipmap::ConvertNoRef( fCoverDecals[i]->fImageKey != nil ? fCoverDecals[i]->fImageKey->ObjectIsLoaded() : nil ); if (decal != nil) layers.Append(IMakeDecalLayer(fCoverDecals[i],decal,mip)); } else { // it's a cover movie, not a decal, so we make a layer, thinking it's at 0,0 and a left map (which gives us the results we want) plLayerBink *movieLayer = IMakeMovieLayer(fCoverDecals[i],0,0,mip,pfJournalDlgProc::kTagLeftDTMap,false); loadedMovie *movie = TRACKED_NEW loadedMovie; movie->movieLayer = movieLayer; movie->movieChunk = fCoverDecals[i]; fLoadedMovies.Append(movie); layers.Append(plLayerInterface::ConvertNoRef(movieLayer)); fVisibleLinks.Reset(); // remove any links that the make movie layer might have added, since a cover movie can't link } } ISetDecalLayers(cover,layers); } else { layers.Append(IMakeBaseLayer(fBookGUIs[fCurBookGUI]->DefaultCover())); ISetDecalLayers(cover,layers); } // release our ref on the cover layers since the material will take care of them now int i; for (i=0; iRelease(layers[i]->GetKey()); } // fInstance->IPlayBookCloseAnim( !startOpened, true ); fBookGUIs[fCurBookGUI]->TurnPageButton()->SetVisible( false ); ITriggerCloseWithNotify( !startOpened, true ); } //// IFinishShow ///////////////////////////////////////////////////////////// // Finish showing the book, due to the animation being done seeking void pfJournalBook::IFinishShow( hsBool startOpened ) { fBookGUIs[fCurBookGUI]->Dialog()->Show(); fAreWeShowing = true; if( startOpened ) { // Render initial pages fCurrentPage = 0; fVisibleLinks.Reset(); IRenderPage( 0, pfJournalDlgProc::kTagLeftDTMap ); IRenderPage( 1, pfJournalDlgProc::kTagRightDTMap ); fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides ); } ISendNotify( kNotifyShow ); } //// Hide //////////////////////////////////////////////////////////////////// void pfJournalBook::Hide( void ) { if (fBookGUIs[fCurBookGUI]) { // we can only hide our own dialog if (fBookGUIs[fCurBookGUI]->CurBook() == this ) { if (fBookGUIs[fCurBookGUI]->Dialog()) fBookGUIs[fCurBookGUI]->Dialog()->Hide(); fBookGUIs[fCurBookGUI]->CurBook(nil); ISendNotify( kNotifyHide ); ILoadAllImages( true ); // purge the dynaTextMaps, we're done with them for now IPurgeDynaTextMaps(); // nuke the movies so they don't stay in memory (they're big!) int i; for( i = 0; i < fLoadedMovies.GetCount(); i++ ) { plLayerBink *movie = fLoadedMovies[ i ]->movieLayer; movie->GetKey()->UnRefObject(); delete fLoadedMovies[ i ]; } fLoadedMovies.Reset(); } } } //// Open //////////////////////////////////////////////////////////////////// // Opens the book, optionally to the given page void pfJournalBook::Open( UInt32 startingPage /*= 0 */) { if( !fBookGUIs[fCurBookGUI]->CurrentlyOpen() ) { fBookGUIs[fCurBookGUI]->PlayBookCloseAnim( false ); // Render initial pages fCurrentPage = startingPage; fVisibleLinks.Reset(); IRenderPage( startingPage, pfJournalDlgProc::kTagLeftDTMap ); IRenderPage( startingPage + 1, pfJournalDlgProc::kTagRightDTMap ); fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides ); } } //// Close /////////////////////////////////////////////////////////////////// // Closes the book. void pfJournalBook::Close( void ) { // don't allow them to close the book if the book started open if( !fBookGUIs[fCurBookGUI]->StartedOpen() && fBookGUIs[fCurBookGUI]->CurrentlyOpen() ) { ISendNotify( kNotifyClose ); fBookGUIs[fCurBookGUI]->PlayBookCloseAnim( true ); } } //// CloseAndHide //////////////////////////////////////////////////////////// // Closes the book, then calls Hide() once it's done closing void pfJournalBook::CloseAndHide( void ) { // if they start with the book open, then don't allow them to close it if( !fBookGUIs[fCurBookGUI]->StartedOpen() && fBookGUIs[fCurBookGUI]->CurrentlyOpen() ) { // if we are flipping a book then kill the page flipping animation if ( fBookGUIs[fCurBookGUI]->CurrentlyTurning() ) fBookGUIs[fCurBookGUI]->KillPageFlip(); ISendNotify( kNotifyClose ); ITriggerCloseWithNotify( true, false ); // Don't hide until we get the callback! } else // Already closed, just hide Hide(); } //// ITriggerCloseWithNotify ///////////////////////////////////////////////// // Close with a notify void pfJournalBook::ITriggerCloseWithNotify( hsBool closeNotOpen, hsBool immediate ) { // Disable the book cover button if we're opening, enable otherwise fBookGUIs[fCurBookGUI]->CoverButton()->SetEnabled( closeNotOpen ); // Do the animation manually so we can get a callback fBookGUIs[fCurBookGUI]->CurrentlyOpen(!closeNotOpen); const hsTArray &keys = fBookGUIs[fCurBookGUI]->CoverButton()->GetAnimationKeys(); const char *animName = fBookGUIs[fCurBookGUI]->CoverButton()->GetAnimationName(); plAnimCmdMsg *msg = TRACKED_NEW plAnimCmdMsg(); if( !immediate ) { msg->SetCmd( plAnimCmdMsg::kContinue ); msg->SetCmd( closeNotOpen ? plAnimCmdMsg::kSetBackwards : plAnimCmdMsg::kSetForewards ); } else { msg->SetCmd( plAnimCmdMsg::kStop ); msg->SetCmd( closeNotOpen ? plAnimCmdMsg::kGoToBegin : plAnimCmdMsg::kGoToEnd ); } msg->SetAnimName( animName ); msg->AddReceivers( keys ); plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg; eventMsg->AddReceiver( fBookGUIs[fCurBookGUI]->GetKey() ); eventMsg->fRepeats = 0; eventMsg->fUser = 0x08 | ( closeNotOpen ? 0x00 : 0x01 ); // So we know which this is for eventMsg->fEvent = immediate ? ( kSingleFrameEval ) : kStop; msg->SetCmd( plAnimCmdMsg::kAddCallbacks ); msg->AddCallback( eventMsg ); hsRefCnt_SafeUnRef( eventMsg ); msg->Send(); // Trigger the open (or close) sound if( !immediate ) fBookGUIs[fCurBookGUI]->CoverButton()->PlaySound(closeNotOpen ? pfGUICheckBoxCtrl::kMouseUp : pfGUICheckBoxCtrl::kMouseDown); } //// NextPage //////////////////////////////////////////////////////////////// // Advances forward one page void pfJournalBook::NextPage( void ) { if( (fBookGUIs[fCurBookGUI]->CurrentlyTurning()) || (!fAllowTurning) || (!fAreWeShowing) ) return; if ((fAreEditing)&&!(fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->ShowingEndOfBuffer())) // we're editing the book, so page turning is different here { fCurrentPage += 2; // we just go to the next page, an editable book has "infinite" pages anyway pfGUIMultiLineEditCtrl *right = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl); pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl); pfGUIMultiLineEditCtrl *turnFront = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl); pfGUIMultiLineEditCtrl *turnBack = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl); // re-link the controls with the turn page in the middle left->SetNext(turnFront); turnBack->SetNext(right); // At this point, only the left and right pages are visible, we don't want to actually update anything until the animation // starts so that nothing flashes or overdraws. fBookGUIs[fCurBookGUI]->StartTriggeredFlip( false ); fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp ); ISendNotify(kNotifyNextPage); } else if( fCurrentPage + 2 <= fLastPage ) { fCurrentPage += 2; fVisibleLinks.Reset(); // Swap the right DT map into the turn page front DTMap, then render // the new current page into turn page back and currPage+1 into // the right DTMap plDynamicTextMap *turnFront = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnFrontDTMap ); plDynamicTextMap *right = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagRightDTMap ); if ( turnFront->IsValid() && right->IsValid() ) { memcpy( turnFront->GetImage(), right->GetImage(), right->GetLevelSize( 0 ) ); if( turnFront->GetDeviceRef() != nil ) turnFront->GetDeviceRef()->SetDirty( true ); } // copy the videos over IMoveMovies( fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kRightPage), fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnFrontPage) ); IRenderPage( fCurrentPage, pfJournalDlgProc::kTagTurnBackDTMap ); // This will fire a callback when it's done that'll let us continue the setup fBookGUIs[fCurBookGUI]->StartTriggeredFlip( false ); // Play us a sound too, if defined on our button fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp ); ISendNotify( kNotifyNextPage ); } } //// PreviousPage //////////////////////////////////////////////////////////// // Same, only back void pfJournalBook::PreviousPage( void ) { if(( fBookGUIs[fCurBookGUI]->CurrentlyTurning() )||( !fAllowTurning )) return; if (fAreEditing) // we're editing the book, so page turning is different here { if (!(fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->ShowingBeginningOfBuffer())) // make sure we don't flip past the beginning { if (fCurrentPage >= 2) // this variable can get out of whack if we open the book to a page in the middle fCurrentPage -= 2; // just making sure that this doesn't go below zero (and therefore wrap around) pfGUIMultiLineEditCtrl *right = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl); pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl); pfGUIMultiLineEditCtrl *turnFront = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl); pfGUIMultiLineEditCtrl *turnBack = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl); // adjust the starting position of the left control to the new start Int32 newStartLine = left->GetFirstVisibleLine() - ((left->GetNumVisibleLines()-1)*2); left->SetGlobalStartLine(newStartLine); // re-link the controls with the turn page in the middle left->SetNext(turnFront); turnBack->SetNext(right); // At this point, only the left and right pages are visible, we don't want to actually update anything until the animation // starts so that nothing flashes or overdraws. fBookGUIs[fCurBookGUI]->StartTriggeredFlip( true ); fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp ); ISendNotify(kNotifyPreviousPage); } else { Close(); } } else if( fCurrentPage > 1 ) { fCurrentPage -= 2; fVisibleLinks.Reset(); // Swap the left DT map into the turn page back DTMap, then render // the new current page into the left and currPage+1 into // the turn page front DTMap plDynamicTextMap *turnBack = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnBackDTMap ); plDynamicTextMap *left = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagLeftDTMap ); if ( turnBack->IsValid() && left->IsValid() ) { memcpy( turnBack->GetImage(), left->GetImage(), left->GetLevelSize( 0 ) ); if( turnBack->GetDeviceRef() != nil ) turnBack->GetDeviceRef()->SetDirty( true ); } // copy the videos over IMoveMovies( fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kLeftPage), fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnBackPage) ); IRenderPage( fCurrentPage + 1, pfJournalDlgProc::kTagTurnFrontDTMap ); // This will fire a callback when it's done that'll let us continue the setup fBookGUIs[fCurBookGUI]->StartTriggeredFlip( true ); // Play us a sound too, if defined on our button fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp ); ISendNotify( kNotifyPreviousPage ); } else { Close(); } } //// IFindCurrVisibleLink //////////////////////////////////////////////////// // Find the current moused link, if any Int32 pfJournalBook::IFindCurrVisibleLink( hsBool rightNotLeft, hsBool hoverNotUp ) { pfGUIClickMapCtrl *ctrl = ( rightNotLeft ) ? fBookGUIs[fCurBookGUI]->RightPageMap() : fBookGUIs[fCurBookGUI]->LeftPageMap(); hsPoint3 pt = hoverNotUp ? ctrl->GetLastMousePt() : ctrl->GetLastMouseUpPt(); // This should be 0-1 in the context of the control, so scale to our DTMap size plDynamicTextMap *dtMap = fBookGUIs[fCurBookGUI]->GetDTMap( rightNotLeft ? pfJournalDlgProc::kTagRightDTMap : pfJournalDlgProc::kTagLeftDTMap ); pt.fX *= (hsScalar)dtMap->GetWidth(); pt.fY *= (hsScalar)dtMap->GetHeight(); if( rightNotLeft ) { // Clicks on the right side are offsetted in x by the left side's width dtMap = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagLeftDTMap ); pt.fX += dtMap->GetWidth(); } // Search through the list of visible hotspots UInt32 i; for( i = 0; i < fVisibleLinks.GetCount(); i++ ) { if( fVisibleLinks[ i ]->fLinkRect.Contains( (Int16)pt.fX, (Int16)pt.fY ) ) { // Found a visible link return (Int32)i; } } return -1; } //// IHandleLeftSideClick //////////////////////////////////////////////////// void pfJournalBook::IHandleLeftSideClick( void ) { if( fBookGUIs[fCurBookGUI]->CurrentlyTurning() ) return; Int32 idx = IFindCurrVisibleLink( false, false ); if( idx != -1 ) { if( fVisibleLinks[ idx ]->fFlags & pfEsHTMLChunk::kActAsCB ) IHandleCheckClick( idx, pfBookData::kLeftSide ); else ISendNotify( kNotifyImageLink, fVisibleLinks[ idx ]->fEventID ); return; } // No link found that we're inside of, so just do the default behavior of turning the page PreviousPage(); } void pfJournalBook::IHandleRightSideClick( void ) { if( fBookGUIs[fCurBookGUI]->CurrentlyTurning() ) return; Int32 idx = IFindCurrVisibleLink( true, false ); if( idx != -1 ) { if( fVisibleLinks[ idx ]->fFlags & pfEsHTMLChunk::kActAsCB ) IHandleCheckClick( idx, pfBookData::kRightSide ); else ISendNotify( kNotifyImageLink, fVisibleLinks[ idx ]->fEventID ); return; } // No link found that we're inside of, so just do the default behavior of turning the page NextPage(); } //// IHandleCheckClick /////////////////////////////////////////////////////// // Process a click on the given "check box" image void pfJournalBook::IHandleCheckClick( UInt32 idx, pfBookData::WhichSide which ) { // Special processing for checkboxes--toggle our state, switch our opacity // and then send a notify about our new state hsBool check = ( fVisibleLinks[ idx ]->fFlags & pfEsHTMLChunk::kChecked ) ? false : true; if( check ) { fVisibleLinks[ idx ]->fFlags |= pfEsHTMLChunk::kChecked; // fVisibleLinks[ idx ]->fCurrOpacity = fVisibleLinks[ idx ]->fMaxOpacity; } else { fVisibleLinks[ idx ]->fFlags &= ~pfEsHTMLChunk::kChecked; // fVisibleLinks[ idx ]->fCurrOpacity = fVisibleLinks[ idx ]->fMinOpacity; } // Re-render the page we're on, to show the change in state IRenderPage( fCurrentPage + ( ( which == pfBookData::kLeftSide ) ? 0 : 1 ), ( which == pfBookData::kLeftSide ) ? pfJournalDlgProc::kTagLeftDTMap: pfJournalDlgProc::kTagRightDTMap ); ISendNotify( check ? kNotifyImageLink : kNotifyCheckUnchecked, fVisibleLinks[ idx ]->fEventID ); // Register for FX processing, so we can fade the checkbox in fBookGUIs[fCurBookGUI]->RegisterForSFX( (pfBookData::WhichSide)( fBookGUIs[fCurBookGUI]->CurSFXPages() | which ) ); } //// GoToPage //////////////////////////////////////////////////////////////// // For completeness... void pfJournalBook::GoToPage( UInt32 pageNumber ) { // Put us here, but only on an even page (odd pages go on the right, y'know) if (pageNumber < fPageStarts.Count()) fCurrentPage = pageNumber & ~0x00000001; else fCurrentPage = 0; fVisibleLinks.Reset(); IRenderPage( fCurrentPage, pfJournalDlgProc::kTagLeftDTMap ); IRenderPage( fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap ); fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides ); } //// SetEditable ///////////////////////////////////////////////////////////// void pfJournalBook::SetEditable(hsBool editable) { if (fBookGUIs[fCurBookGUI]->IsEditable()) // make sure this GUI supports editing { fBookGUIs[fCurBookGUI]->EnableEditGUI(editable); fAreEditing = editable; // we may be editing the book, so change rendering/page flipping methods if (editable) fLastPage = 0; // setting this to 0 since editable books don't know what the last page is (it always changes) } else fWantEditing = editable; // we want to edit, but the gui doesn't support it, check again if the GUI changes }; //// ForceCacheCalculations ////////////////////////////////////////////////// // Just forces a full calc of the cached info void pfJournalBook::ForceCacheCalculations( void ) { // Make sure our page starts are up-to-snuff, at least to this point IRecalcPageStarts( -1 ); } // Tiny helper to convert hex values the *right* way static UInt32 IConvertHex( const wchar_t *str ) { UInt32 value = 0; while( *str != 0 ) { value <<= 4; switch( *str ) { case L'0': value |= 0x0; break; case L'1': value |= 0x1; break; case L'2': value |= 0x2; break; case L'3': value |= 0x3; break; case L'4': value |= 0x4; break; case L'5': value |= 0x5; break; case L'6': value |= 0x6; break; case L'7': value |= 0x7; break; case L'8': value |= 0x8; break; case L'9': value |= 0x9; break; case L'a': case L'A': value |= 0xa; break; case L'b': case L'B': value |= 0xb; break; case L'c': case L'C': value |= 0xc; break; case L'd': case L'D': value |= 0xd; break; case L'e': case L'E': value |= 0xe; break; case L'f': case L'F': value |= 0xf; break; } str++; } return value; } //// ICompileSource ////////////////////////////////////////////////////////// // Compiles the given string of esHTML source into our compiled chunk list hsBool pfJournalBook::ICompileSource( const wchar_t *source, const plLocation &hintLoc ) { IFreeSource(); pfEsHTMLChunk *chunk, *lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); const wchar_t *c, *start; wchar_t name[ 128 ], option[ 256 ]; float bookWidth=1.0, bookHeight=1.0; UInt8 movieIndex = 0; // the index of a movie in the source (used for id purposes) plKey anotherKey; // Parse our source! for( start = c = source; *c != 0; ) { // Are we on a tag? UInt8 type = IGetTagType( c ); if( type != pfEsHTMLChunk::kEmpty ) { // First, end the current paragraph chunk, which is a special case 'cause its // text is defined outside the tag if( start == c ) { // No actual text, just delete delete lastParChunk; lastParChunk = nil; } else if( lastParChunk != nil ) { UInt32 count = ((UInt32)c - (UInt32)start)/2; // wchar_t is 2 bytes wchar_t *temp = TRACKED_NEW wchar_t[ count + 1 ]; wcsncpy( temp, start, count ); temp[count] = L'\0'; lastParChunk->fText = temp; delete [] temp; // Special case to remove any last trailing carriage return // if( count > 1 && lastParChunk->fText[ count - 1 ] == '\n' ) // lastParChunk->fText[ count - 1 ] = 0; fHTMLSource.Append( lastParChunk ); } // What chunk are we making now? switch( type ) { case pfEsHTMLChunk::kParagraph: c += 2; chunk = TRACKED_NEW pfEsHTMLChunk( nil ); chunk->fFlags = IFindLastAlignment(); while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"align" ) == 0 ) { if( wcsicmp( option, L"left" ) == 0 ) chunk->fFlags = pfEsHTMLChunk::kLeft; else if( wcsicmp( option, L"center" ) == 0 ) chunk->fFlags = pfEsHTMLChunk::kCenter; else if( wcsicmp( option, L"right" ) == 0 ) chunk->fFlags = pfEsHTMLChunk::kRight; } } // Append text to this one (don't add to source just yet) lastParChunk = chunk; break; case pfEsHTMLChunk::kImage: c += 4; chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0 ); while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"align" ) == 0 ) { chunk->fFlags &= ~pfEsHTMLChunk::kAlignMask; if( wcsicmp( option, L"left" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kLeft; else if( wcsicmp( option, L"center" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kCenter; else if( wcsicmp( option, L"right" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kRight; } else if( wcsicmp( name, L"src" ) == 0 ) { // Name of mipmap source chunk->fImageKey = IGetMipmapKey( option, hintLoc ); } else if( wcsicmp( name, L"link" ) == 0 ) { chunk->fEventID = _wtoi( option ); chunk->fFlags |= pfEsHTMLChunk::kCanLink; } else if( wcsicmp( name, L"blend" ) == 0 ) { if( wcsicmp( option, L"alpha" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kBlendAlpha; } else if( wcsicmp( name, L"pos" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kFloating; wchar_t *comma = wcschr( option, L',' ); if( comma != nil ) { chunk->fAbsoluteY = _wtoi( comma + 1 ); *comma = 0; } chunk->fAbsoluteX = _wtoi( option ); } else if( wcsicmp( name, L"glow" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kGlowing; chunk->fFlags &= ~pfEsHTMLChunk::kActAsCB; char *cOption = hsWStringToString(option); char *comma = strchr( cOption, ',' ); if( comma != nil ) { char *comma2 = strchr( comma + 1, ',' ); if( comma2 != nil ) { chunk->fMaxOpacity = (hsScalar)atof( comma2 + 1 ); *comma2 = 0; } chunk->fMinOpacity = (hsScalar)atof( comma + 1 ); *comma = 0; } chunk->fSFXTime = (hsScalar)atof( cOption ); delete [] cOption; } else if( wcsicmp( name, L"opacity" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kTranslucent; char *cOption = hsWStringToString(option); chunk->fCurrOpacity = (hsScalar)atof( cOption ); delete [] cOption; } else if( wcsicmp( name, L"check" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kActAsCB; chunk->fFlags &= ~pfEsHTMLChunk::kGlowing; wchar_t *comma = wcschr( option, L',' ); if( comma != nil ) { wchar_t *comma2 = wcschr( comma + 1, L',' ); if( comma2 != nil ) { if( _wtoi( comma2 + 1 ) != 0 ) chunk->fFlags |= pfEsHTMLChunk::kChecked; *comma2 = 0; } UInt32 c = IConvertHex( comma + 1 ); if( wcslen( comma + 1 ) <= 6 ) c |= 0xff000000; // Add in full alpha if none specified chunk->fOffColor.FromARGB32( c ); *comma = 0; } UInt32 c = IConvertHex( option ); if( wcslen( option ) <= 6 ) c |= 0xff000000; // Add in full alpha if none specified chunk->fOnColor.FromARGB32( c ); if( chunk->fFlags & pfEsHTMLChunk::kChecked ) chunk->fCurrColor = chunk->fOnColor; else chunk->fCurrColor = chunk->fOffColor; } else if (wcsicmp(name,L"resize")==0) { if (wcsicmp(option,L"no")==0) chunk->fNoResizeImg = true; } } if( chunk->fImageKey != nil ) fHTMLSource.Append( chunk ); else delete chunk; // Start new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kCover: // Don't create an actual chunk for this one, just use the "src" and // grab the mipmap key for our cover c += 6; while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"src" ) == 0 ) { // Name of mipmap source anotherKey = IGetMipmapKey( option, hintLoc ); if( anotherKey != nil ) { fCoverMipKey = anotherKey; fCoverFromHTML = true; } } if( wcsicmp( name, L"tint" ) == 0 ) { fTintCover = true; fCoverTint.FromARGB32( wcstol( option, nil, 16 ) | 0xff000000 ); } if( wcsicmp( name, L"tintfirst" ) == 0 ) { if (wcsicmp(option,L"no")==0) fTintFirst = false; } } // Still gotta create a new par chunk lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kPageBreak: c += 3; chunk = TRACKED_NEW pfEsHTMLChunk(); while( IGetNextOption( c, name, option ) ) { } fHTMLSource.Append( chunk ); // Start new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kFontChange: c += 5; chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0, 0 ); while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"style" ) == 0 ) { UInt8 guiFlags = 0; if( wcsicmp( option, L"b" ) == 0 ) { chunk->fFlags = pfEsHTMLChunk::kFontBold; guiFlags = plDynamicTextMap::kFontBold; } else if( wcsicmp( option, L"i" ) == 0 ) { chunk->fFlags = pfEsHTMLChunk::kFontItalic; guiFlags = plDynamicTextMap::kFontItalic; } else if( wcsicmp( option, L"bi" ) == 0 ) { chunk->fFlags = pfEsHTMLChunk::kFontBold | pfEsHTMLChunk::kFontItalic; guiFlags = plDynamicTextMap::kFontBold | plDynamicTextMap::kFontItalic; } else chunk->fFlags = pfEsHTMLChunk::kFontRegular; if (fBookGUIs[fCurBookGUI]->IsEditable()) { fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontStyle(guiFlags); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontStyle(guiFlags); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontStyle(guiFlags); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontStyle(guiFlags); } } else if( wcsicmp( name, L"face" ) == 0 ) { // Name of mipmap source chunk->fText = option; if (fBookGUIs[fCurBookGUI]->IsEditable()) { char *fontFace = hsWStringToString(option); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontFace(fontFace); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontFace(fontFace); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontFace(fontFace); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontFace(fontFace); delete [] fontFace; } } else if( wcsicmp( name, L"size" ) == 0 ) { chunk->fFontSize = _wtoi( option ); if (fBookGUIs[fCurBookGUI]->IsEditable()) { fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontSize(chunk->fFontSize); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontSize(chunk->fFontSize); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontSize(chunk->fFontSize); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontSize(chunk->fFontSize); } } else if( wcsicmp( name, L"color" ) == 0 ) { chunk->fColor.FromARGB32( wcstol( option, nil, 16 ) | 0xff000000 ); chunk->fFlags |= pfEsHTMLChunk::kFontColor; if (fBookGUIs[fCurBookGUI]->IsEditable()) { fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontColor(chunk->fColor); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontColor(chunk->fColor); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontColor(chunk->fColor); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontColor(chunk->fColor); } } else if( wcsicmp( name, L"spacing" ) == 0 ) { chunk->fLineSpacing = _wtoi(option); chunk->fFlags |= pfEsHTMLChunk::kFontSpacing; } } fHTMLSource.Append( chunk ); // Start new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kMargin: c += 7; while(IGetNextOption(c,name,option)) { if (wcsicmp(name,L"top") == 0) fPageTMargin = _wtoi(option); else if (wcsicmp(name,L"left") == 0) fPageLMargin = _wtoi(option); else if (wcsicmp(name,L"bottom") == 0) fPageBMargin = _wtoi(option); else if (wcsicmp(name,L"right") == 0) fPageRMargin = _wtoi(option); } // set the edit controls to the margins we just set if (fBookGUIs[fCurBookGUI]->IsEditable()) { fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin); fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin); } // Start a new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk(nil); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kBook: c += 5; // don't actually create a chunk, just set the book size while (IGetNextOption(c,name,option)) { if (wcsicmp(name,L"height") == 0) { char *temp = hsWStringToString(option); bookHeight = (float)atof(temp); delete [] temp; } else if (wcsicmp(name,L"width") == 0) { char *temp = hsWStringToString(option); bookWidth = (float)atof(temp); delete [] temp; } } fHeightScale = 1.f - bookHeight; fWidthScale = 1.f - bookWidth; // Still gotta create a new par chunk lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kDecal: c += 6; chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0 ); chunk->fType = pfEsHTMLChunk::kDecal; while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"align" ) == 0 ) { chunk->fFlags &= ~pfEsHTMLChunk::kAlignMask; if( wcsicmp( option, L"left" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kLeft; else if( wcsicmp( option, L"center" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kCenter; else if( wcsicmp( option, L"right" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kRight; } else if( wcsicmp( name, L"src" ) == 0 ) { // Name of mipmap source chunk->fImageKey = IGetMipmapKey( option, hintLoc ); } else if( wcsicmp( name, L"pos" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kFloating; wchar_t *comma = wcschr( option, L',' ); if( comma != nil ) { chunk->fAbsoluteY = _wtoi( comma + 1 ); *comma = 0; } chunk->fAbsoluteX = _wtoi( option ); } else if (wcsicmp(name,L"resize")==0) { if (wcsicmp(option,L"no")==0) chunk->fNoResizeImg = true; } else if (wcsicmp(name,L"tint")==0) { if (wcsicmp(option,L"yes")==0) chunk->fTintDecal = true; } } // add it to our cover decals list (this is tag is essentially thrown away as far as the parser cares) if( chunk->fImageKey != nil ) fCoverDecals.Append( chunk ); else delete chunk; // Start new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kMovie: c += 6; chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0 ); chunk->fType = pfEsHTMLChunk::kMovie; while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"align" ) == 0 ) { chunk->fFlags &= ~pfEsHTMLChunk::kAlignMask; if( wcsicmp( option, L"left" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kLeft; else if( wcsicmp( option, L"center" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kCenter; else if( wcsicmp( option, L"right" ) == 0 ) chunk->fFlags |= pfEsHTMLChunk::kRight; } else if( wcsicmp( name, L"src" ) == 0 ) { chunk->fText = option; } else if( wcsicmp( name, L"link" ) == 0 ) { chunk->fEventID = _wtoi( option ); chunk->fFlags |= pfEsHTMLChunk::kCanLink; } else if( wcsicmp( name, L"pos" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kFloating; wchar_t *comma = wcschr( option, L',' ); if( comma != nil ) { chunk->fAbsoluteY = _wtoi( comma + 1 ); *comma = 0; } chunk->fAbsoluteX = _wtoi( option ); } else if (wcsicmp(name,L"resize")==0) { if (wcsicmp(option,L"no")==0) chunk->fNoResizeImg = true; } else if (wcsicmp(name,L"oncover")==0) { if (wcsicmp(option,L"yes")==0) chunk->fOnCover = true; } else if (wcsicmp(name,L"loop")==0) { if (wcsicmp(option,L"no")==0) chunk->fLoopMovie = false; } } chunk->fMovieIndex = movieIndex; movieIndex++; if (chunk->fOnCover) { if( chunk->fText != L"" ) fCoverDecals.Append( chunk ); else delete chunk; } else { if( chunk->fText != L"" ) fHTMLSource.Append( chunk ); else delete chunk; } // Start new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kEditable: c += 9; SetEditable(true); chunk = TRACKED_NEW pfEsHTMLChunk(); while( IGetNextOption( c, name, option ) ) { } fHTMLSource.Append( chunk ); // Start new paragraph chunk after this one lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; } start = c; } else { // Keep looking c++; } } // Final bit goes into the last paragraph chunk we had if( start == c ) { // No actual text, just delete delete lastParChunk; lastParChunk = nil; } else if( lastParChunk != nil ) { UInt32 count = (UInt32)c - (UInt32)start; wchar_t *temp = TRACKED_NEW wchar_t[ count + 1 ]; wcsncpy( temp, start, count + 1 ); lastParChunk->fText = temp; delete [] temp; // Special case to remove any last trailing carriage return // if( count > 1 && lastParChunk->fText[ count - 1 ] == '\n' ) // lastParChunk->fText[ count - 1 ] = 0; fHTMLSource.Append( lastParChunk ); } // Reset a few fPageStarts.Reset(); fPageStarts.Append( 0 ); if (fAreEditing) fLastPage = 0; else fLastPage = -1; return true; } UInt8 pfJournalBook::IGetTagType( const wchar_t *string ) { if( string[ 0 ] != '<' ) return pfEsHTMLChunk::kEmpty; struct TagRec { const wchar_t *fTag; UInt8 fType; } tags[] = { { L"p", pfEsHTMLChunk::kParagraph }, { L"img", pfEsHTMLChunk::kImage }, { L"pb", pfEsHTMLChunk::kPageBreak }, { L"font", pfEsHTMLChunk::kFontChange }, { L"margin", pfEsHTMLChunk::kMargin }, { L"cover", pfEsHTMLChunk::kCover }, { L"book", pfEsHTMLChunk::kBook }, { L"decal", pfEsHTMLChunk::kDecal }, { L"movie", pfEsHTMLChunk::kMovie }, { L"editable", pfEsHTMLChunk::kEditable }, { nil, pfEsHTMLChunk::kEmpty } }; UInt32 i; for( i = 0; tags[ i ].fTag != nil; i++ ) { if( wcsnicmp( string + 1, tags[ i ].fTag, wcslen( tags[ i ].fTag ) ) == 0 ) { // Found tag--but only space or end tag marker allowed afterwards char end = (char)string[ wcslen( tags[ i ].fTag ) + 1 ]; if( end == '>' || end == ' ' ) return tags[ i ].fType; } } return pfEsHTMLChunk::kEmpty; } hsBool pfJournalBook::IGetNextOption( const wchar_t *&string, wchar_t *name, wchar_t *option ) { const wchar_t *c; // Advance past any white space while( *string == L' ' ) string++; if( *string == L'>' ) { string++; return false; } // Advance to = c = string; while( *string != L'>' && *string != L' ' && *string != L'=' && *string != L'\0' ) string++; if( *string != L'=' ) return false; // Copy name UInt32 len = ((UInt32)string - (UInt32)c)/2; // divide length by 2 because each character is two bytes wcsncpy( name, c, len ); name[len] = L'\0'; // Find start of option value string++; while( *string == L' ' ) string++; if( *string == L'\0' || *string == L'>' ) return false; if( *string == L'\"' ) { // Search for other quote string++; c = string; while( *string != L'>' && *string != L'\"' && *string != L'\0' ) string++; len = ((UInt32)string - (UInt32)c)/2; // divide length by 2 because each character is two bytes wcsncpy( option, c, len ); option[len] = L'\0'; if( *string == L'\"' ) string++; return true; } // Non-quoted token c = string; while( *string != L' ' && *string != L'>' && *string != L'\0' ) string++; len = ((UInt32)string - (UInt32)c)/2; // divide length by 2 because each character is two bytes wcsncpy( option, c, len ); option[len] = L'\0'; return true; } void pfJournalBook::IFreeSource( void ) { UInt32 i; for( i = 0; i < fHTMLSource.GetCount(); i++ ) delete fHTMLSource[ i ]; fHTMLSource.Reset(); for( i = 0; i < fCoverDecals.GetCount(); i++ ) delete fCoverDecals[ i ]; fCoverDecals.Reset(); for( i = 0; i < fLoadedMovies.GetCount(); i++ ) { plLayerBink *movie = fLoadedMovies[ i ]->movieLayer; movie->GetKey()->UnRefObject(); delete fLoadedMovies[ i ]; } fLoadedMovies.Reset(); } //// IGetMipmapKey /////////////////////////////////////////////////////////// // Looks up the key for a mipmap given the image name. Note that the given // location is treated as a hint; if the image isn't found in that location, // the code will attempt to look in the currently loaded age for a matching // image name. #ifndef PLASMA_EXTERNAL_RELEASE #include "../plJPEG/plJPEG.h" #endif plKey pfJournalBook::IGetMipmapKey( const wchar_t *name, const plLocation &loc ) { char *cName = hsWStringToString(name); #ifndef PLASMA_EXTERNAL_RELEASE if( strchr( cName, '/' ) != nil || strchr( cName, '\\' ) != nil ) { // For internal use only--we allow local path names of JPEG images, to // facilitate fast prototyping plMipmap *mip = plJPEG::Instance().ReadFromFile( cName ); hsgResMgr::ResMgr()->NewKey( cName, mip, loc ); delete [] cName; return mip->GetKey(); } #endif // Try first to find in the given location plUoid myUoid( loc, plMipmap::Index(), cName ); plKey key = hsgResMgr::ResMgr()->FindKey( myUoid ); if( key != nil ) { delete [] cName; return key; } // Next, try our "global" pre-defined age const plLocation &globLoc = plKeyFinder::Instance().FindLocation( "GUI", "BkBookImages" ); myUoid = plUoid( globLoc, plMipmap::Index(), cName ); key = hsgResMgr::ResMgr()->FindKey( myUoid ); if( key != nil ) { delete [] cName; return key; } // Do a search through our current age with just the name given if( plNetClientMgr::GetInstance() != nil ) { const char *thisAge = plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeName(); if( thisAge != nil ) { key = plKeyFinder::Instance().StupidSearch( thisAge, nil, plMipmap::Index(), cName, true ); if( key != nil ) { delete [] cName; return key; } } } delete [] cName; return nil; } //// IRenderPage ///////////////////////////////////////////////////////////// // Takes the given page out of the source and renders it into the specified // DTMap (by GUI tag ID). If no page is cached after this one, also updates // the various cached info about page endings, etc. void pfJournalBook::IRenderPage( UInt32 page, UInt32 whichDTMap, hsBool suppressRendering /*= false*/ ) { if (fAreEditing) return; // we don't render if we are editing the book // Grab the DTMap via the GUI system plDynamicTextMap *dtMap = fBookGUIs[fCurBookGUI]->GetDTMap( whichDTMap ); hsAssert( dtMap != nil, "Invalid DT map in IRenderPage()" ); loadedMovie *movie = nil; bool movieAlreadyLoaded = false; // Make sure our page starts are up-to-snuff, at least to this point IRecalcPageStarts( page ); // Render! hsColorRGBA color; color.Set( 0, 0, 0, 0 ); if( !suppressRendering ) dtMap->ClearToColor( color ); hsGMaterial *material = nil; if (whichDTMap == pfJournalDlgProc::kTagLeftDTMap) material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kLeftPage); else if (whichDTMap == pfJournalDlgProc::kTagRightDTMap) material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kRightPage); else if (whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap) material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnFrontPage); else if (whichDTMap == pfJournalDlgProc::kTagTurnBackDTMap) material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnBackPage); if (material) { // clear any exiting layers (movies) from the material int i; for( i = 0; i < material->GetNumLayers(); i++ ) // remove all plLayerBink layers { plLayerInterface *matLayer = material->GetLayer(i); plLayerBink *bink = plLayerBink::ConvertNoRef(matLayer); if (bink) // if it was a bink layer { plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(material->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it hsgResMgr::ResMgr()->SendRef(material->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef); } } } hsAssert(page < fPageStarts.GetCount(), "UnInitialized page start!"); if( page <= fLastPage && page < fPageStarts.GetCount()) // Added this as a crash-prevention bandaid - MT { UInt32 idx; UInt16 width, height, y, x, ascent, lastX, lastY; UInt8 fontFlags, fontSize; const wchar_t *fontFace; hsColorRGBA fontColor; Int16 fontSpacing; hsBool needSFX = false; // Find and set initial font properties IFindFontProps( fPageStarts[ page ], fontFace, fontSize, fontFlags, fontColor, fontSpacing ); dtMap->SetFont( fontFace, fontSize, fontFlags, false ); dtMap->SetTextColor( fontColor, true ); dtMap->SetLineSpacing(fontSpacing); for( idx = fPageStarts[ page ], x = (UInt16)fPageLMargin, y = (UInt16)fPageTMargin; y < (UInt16)(512 - fPageTMargin - fPageBMargin) && idx < fHTMLSource.GetCount(); idx++ ) { if( fPageStarts.GetCount() > page + 1 && idx == fPageStarts[ page + 1 ] ) break; // Just go ahead and break at the start of the next page, since we already found it pfEsHTMLChunk *chunk = fHTMLSource[ idx ]; switch( chunk->fType ) { case pfEsHTMLChunk::kParagraph: if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kLeft ) { dtMap->SetJustify( plDynamicTextMap::kLeftJustify ); x = (UInt16)fPageLMargin; // reset X if our justification changes } else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight ) { dtMap->SetJustify( plDynamicTextMap::kRightJustify ); x = (UInt16)fPageLMargin; // reset X if our justification changes } else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter ) { dtMap->SetJustify( plDynamicTextMap::kCenter ); x = (UInt16)fPageLMargin; // reset X if our justification changes } dtMap->SetFirstLineIndent( (Int16)(x - fPageLMargin) ); width = (UInt16)(512 - fPageLMargin - fPageRMargin); height = (UInt16)(512 - fPageBMargin - y); UInt32 lastChar; dtMap->CalcWrappedStringSize( chunk->fText.c_str(), &width, &height, &lastChar, &ascent, &lastX, &lastY ); width = (UInt16)(512 - fPageLMargin - fPageRMargin); if( !suppressRendering ) dtMap->DrawWrappedString( (UInt16)fPageLMargin, y, chunk->fText.c_str(), width, (UInt16)(512 - fPageBMargin - y), &lastX, &lastY ); if( lastChar == 0 ) { // This paragraph didn't fit on this page at *all*, so just bump it to the next // one artificially (the -- is to account for the for loop; see image handling below) y += 512; if( idx > fPageStarts[ page ] ) idx--; break; } if( chunk->fText[ lastChar ] != 0 ) { // Didn't get to render the whole paragraph in this go, so we're going to cheat // and split the paragraph up into two so that we can handle it properly. Note: // this changes the chunk array beyond this point, so we need to invalidate the // cache, but that's ok 'cause if we're doing this, it's probably invalid (or empty) // anyway int fTextLen = chunk->fText.length(); wchar_t *s = TRACKED_NEW wchar_t[fTextLen+1]; wcscpy(s,chunk->fText.c_str()); s[fTextLen] = L'\0'; // Note: Makes a copy of the string pfEsHTMLChunk *c2 = TRACKED_NEW pfEsHTMLChunk( &s[ lastChar ] ); c2->fFlags = chunk->fFlags; fHTMLSource.Insert( idx + 1, c2 ); // Clip and reallocate so we don't have two copies laying around s[ lastChar ] = L'\0'; chunk->fText = s; delete [] s; // Invalidate our cache starting with the next page if( fPageStarts.GetCount() > page + 1 ) fPageStarts.SetCount( page + 1 ); y += 512; break; } x = lastX; y = (UInt16)(lastY - dtMap->GetCurrFont()->GetAscent()); // Since our text is top-justified if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) != pfEsHTMLChunk::kLeft ) { // Ending X is not guaranteed to be anything useful if we're not left justified x = (UInt16)fPageLMargin; } break; case pfEsHTMLChunk::kImage: { plMipmap *mip = plMipmap::ConvertNoRef( chunk->fImageKey != nil ? chunk->fImageKey->ObjectIsLoaded() : nil ); if( mip != nil ) { // First, determine if we need to be processing FX messages if( chunk->fFlags & pfEsHTMLChunk::kGlowing ) needSFX = true; else if( chunk->fFlags & pfEsHTMLChunk::kActAsCB ) { // If our color doesn't match our checked state, we want to be fading it in hsColorRGBA &want = ( chunk->fFlags & pfEsHTMLChunk::kChecked ) ? chunk->fOnColor : chunk->fOffColor; if( want != chunk->fCurrColor ) needSFX = true; } if( chunk->fFlags & pfEsHTMLChunk::kFloating ) { // Floating image, ignore the text flow completely and just splat the image on! IDrawMipmap( chunk, chunk->fAbsoluteX, chunk->fAbsoluteY, mip, dtMap, whichDTMap, suppressRendering ); } else { if( y + mip->GetHeight() >= 512 - fPageBMargin ) { // Mipmap overlaps the bottom of this page, so forcibly break so we'll // end up marking the page break here (note that, unlike paragraphs, we // can't really break the mipmap into two...well, OK, we could, but it // wouldn't make much sense :) y += (UInt16)(mip->GetHeight()); // Wonderful, the break breaks us from the switch(), which means the for() // loops runs once more and increments idx. So this is to counter that. // (We better check tho, just to make sure nobody feeds us an extra-large // image and sends us on an infinite loop) if( idx > fPageStarts[ page ] ) idx--; break; } if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kLeft ) x = (UInt16)fPageLMargin; else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight ) x = (UInt16)(512 - fPageRMargin - mip->GetWidth()); else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter ) x = (UInt16)(256 - ( mip->GetWidth() >> 1 )); IDrawMipmap( chunk, x, y, mip, dtMap, whichDTMap, suppressRendering ); y += (UInt16)(mip->GetHeight()); x = (UInt16)fPageLMargin; } } } break; case pfEsHTMLChunk::kPageBreak: // Time for some creative guesswork. See, if the user put a in at the end of a page, // but coincidentally we already broke to a new page, then the explicit is redundant, // so we don't want it. However, if we're at a new page *because* of a , then a new // is intentional, so we DO want to process it. if( idx == fPageStarts[ page ] ) { // Did we get here b/c the last chunk was a pb? if( idx > 0 && fHTMLSource[ idx - 1 ]->fType != pfEsHTMLChunk::kPageBreak ) { // Nope, so we DO want to ignore it! continue; } } y = (UInt16)(512 - fPageTMargin - fPageBMargin); x = (UInt16)fPageLMargin; break; case pfEsHTMLChunk::kFontChange: IFindFontProps( idx, fontFace, fontSize, fontFlags, fontColor, fontSpacing ); dtMap->SetFont( fontFace, fontSize, fontFlags, false ); dtMap->SetTextColor( fontColor, true ); dtMap->SetLineSpacing(fontSpacing); break; case pfEsHTMLChunk::kMovie: movieAlreadyLoaded = (IMovieAlreadyLoaded(chunk) != nil); // have we already cached it? plLayerBink *movieLayer = IMakeMovieLayer(chunk, x, y, (plMipmap*)dtMap, whichDTMap, suppressRendering); if (movieLayer) { // adjust the starting height of the movie if we are keeping it inline with the text UInt32 movieHeight = 0, movieWidth = 0; movieHeight = movieLayer->GetHeight(); movieWidth = movieLayer->GetWidth(); if(!(chunk->fFlags & pfEsHTMLChunk::kFloating )) { if( y + movieHeight >= 512 - fPageBMargin ) { // Movie overlaps the bottom of this page, so forcibly break so we'll // end up marking the page break here (note that, unlike paragraphs, we // can't really break the Movie into two) y += (UInt16)movieHeight; // Wonderful, the break breaks us from the switch(), which means the for() // loops runs once more and increments idx. So this is to counter that. // (We better check tho, just to make sure nobody feeds us an extra-large // image and sends us on an infinite loop) if( idx > fPageStarts[ page ] ) idx--; break; } y += (UInt16)movieHeight; x = (UInt16)fPageLMargin; } if (!movieAlreadyLoaded) // if the movie wasn't already cached, cache it { movie = TRACKED_NEW loadedMovie; movie->movieLayer = movieLayer; // save the layer and chunk data movie->movieChunk = chunk; fLoadedMovies.Append(movie); movie = nil; movieAlreadyLoaded = false; } if (material && !suppressRendering) material->AddLayerViaNotify(movieLayer); } break; } } if( fPageStarts.GetCount() <= page + 1 ) fPageStarts.ExpandAndZero( page + 2 ); fPageStarts[ page + 1 ] = idx; if( idx == fHTMLSource.GetCount() ) fLastPage = page; pfBookData::WhichSide thisWhich = ( whichDTMap == pfJournalDlgProc::kTagRightDTMap ) ? pfBookData::kRightSide : ( whichDTMap == pfJournalDlgProc::kTagLeftDTMap ) ? pfBookData::kLeftSide : pfBookData::kNoSides; if( needSFX ) fBookGUIs[fCurBookGUI]->RegisterForSFX( (pfBookData::WhichSide)( fBookGUIs[fCurBookGUI]->CurSFXPages() | thisWhich ) ); else fBookGUIs[fCurBookGUI]->RegisterForSFX( (pfBookData::WhichSide)( fBookGUIs[fCurBookGUI]->CurSFXPages() & ~thisWhich ) ); } if( !suppressRendering ) dtMap->FlushToHost(); } //// IMoveMovies ///////////////////////////////////////////////////////////// void pfJournalBook::IMoveMovies( hsGMaterial *source, hsGMaterial *dest ) { hsTArray moviesOnPage; if (source && dest) { // clear any exiting layers (movies) from the material and save them to our local array int i; for( i = 0; i < source->GetNumLayers(); i++ ) // remove all plLayerBink layers { plLayerInterface *matLayer = source->GetLayer(i); plLayerBink *bink = plLayerBink::ConvertNoRef(matLayer); if (bink) // if it was a bink layer { plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(source->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it hsgResMgr::ResMgr()->SendRef(source->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef); moviesOnPage.Append(bink); } } // clear the destination's movies (if it has any) for( i = 0; i < dest->GetNumLayers(); i++ ) // remove all plLayerBink layers { plLayerInterface *matLayer = dest->GetLayer(i); plLayerBink *bink = plLayerBink::ConvertNoRef(matLayer); if (bink) // if it was a bink layer { plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(dest->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it hsgResMgr::ResMgr()->SendRef(dest->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef); } } // put the movies we ripped off the old page onto the new one for( i = 0; i < moviesOnPage.GetCount(); i++ ) { dest->AddLayerViaNotify(moviesOnPage[i]); } } } //// IDrawMipmap ///////////////////////////////////////////////////////////// void pfJournalBook::IDrawMipmap( pfEsHTMLChunk *chunk, UInt16 x, UInt16 y, plMipmap *mip, plDynamicTextMap *dtMap, UInt32 whichDTMap, hsBool dontRender ) { plMipmap *copy = TRACKED_NEW plMipmap(); copy->CopyFrom(mip); if (chunk->fNoResizeImg) { // book is NOT square, there is a h/w ratio of 1/0.7 // calc new size based on how the book has been skewed float xScale = (fWidthScale == 0) ? 1 : 1/(1-fWidthScale); float yScale = (fHeightScale == 0) ? 1 : 1/(1-fHeightScale); yScale *= 0.7; // adjust because the book isn't square UInt32 width = (UInt32)(mip->GetWidth()*xScale); UInt32 height = (UInt32)(mip->GetHeight()*yScale); UInt16 xShift; UInt16 yShift; if (dtMap->GetWidth() < width) width = dtMap->GetWidth(); if (dtMap->GetHeight() < height) height = dtMap->GetHeight(); if (height < mip->GetHeight()) { yShift = (UInt16)((mip->GetHeight()-height)/2); if (y+yShift+height > dtMap->GetHeight()) y = (UInt16)(dtMap->GetHeight()-height); else y += yShift; } else { yShift = (UInt16)((height-mip->GetHeight())/2); if (yShift > y) y = 0; else y -= yShift; } if (width < mip->GetWidth()) { xShift = (UInt16)((mip->GetWidth()-width)/2); if (x+xShift+width > dtMap->GetWidth()) x = (UInt16)(dtMap->GetWidth()-width); else x += xShift; } else { xShift = (UInt16)((width-mip->GetWidth())/2); if (xShift > x) x = 0; else x -= xShift; } copy->SetCurrLevel(0); // resize the image so it will look unchanged when rendered on the altered book copy->ResizeNicely((UInt16)width,(UInt16)height,plMipmap::kDefaultFilter); } if( !dontRender ) { plMipmap::CompositeOptions opts; if( chunk->fFlags & pfEsHTMLChunk::kActAsCB ) { opts.fFlags = ( chunk->fFlags & pfEsHTMLChunk::kBlendAlpha ) ? 0 : plMipmap::kBlendWriteAlpha; opts.fRedTint = chunk->fCurrColor.r; opts.fGreenTint = chunk->fCurrColor.g; opts.fBlueTint = chunk->fCurrColor.b; opts.fOpacity = (UInt8)(chunk->fCurrColor.a * 255.f); } else { if( chunk->fFlags & pfEsHTMLChunk::kGlowing ) opts.fFlags = ( chunk->fFlags & pfEsHTMLChunk::kBlendAlpha ) ? 0 : plMipmap::kMaskSrcAlpha; else if (chunk->fFlags & pfEsHTMLChunk::kTranslucent) opts.fFlags = plMipmap::kMaskSrcAlpha; else opts.fFlags = ( chunk->fFlags & pfEsHTMLChunk::kBlendAlpha ) ? plMipmap::kCopySrcAlpha : plMipmap::kForceOpaque; opts.fOpacity = (UInt8)(chunk->fCurrOpacity * 255.f); } dtMap->Composite( copy, x, y, &opts ); } if( chunk->fFlags & pfEsHTMLChunk::kCanLink ) { if( whichDTMap == pfJournalDlgProc::kTagRightDTMap || whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap ) x += (UInt16)(dtMap->GetWidth()); // Right page rects are offsetted to differentiate if (dontRender) // if we aren't rendering then this link isn't visible, but the index still needs to be valid, so give it a rect of 0,0,0,0 chunk->fLinkRect.Set(0,0,0,0); else chunk->fLinkRect.Set( x, y, (Int16)(copy->GetWidth()), (Int16)(copy->GetHeight()) ); fVisibleLinks.Append( chunk ); } delete copy; } pfJournalBook::loadedMovie *pfJournalBook::IMovieAlreadyLoaded(pfEsHTMLChunk *chunk) { int i; for (i=0; ifText == fLoadedMovies[i]->movieChunk->fText)&&(chunk->fMovieIndex == fLoadedMovies[i]->movieChunk->fMovieIndex)) return fLoadedMovies[i]; } return nil; } plKey pfJournalBook::GetMovie(UInt8 index) { loadedMovie *movie = IGetMovieByIndex(index); if (movie) return movie->movieLayer->GetKey(); return plKey(nil); } pfJournalBook::loadedMovie *pfJournalBook::IGetMovieByIndex(UInt8 index) { int i; for (i=0; imovieChunk->fMovieIndex == index) return fLoadedMovies[i]; } return nil; } plLayerBink *pfJournalBook::IMakeMovieLayer(pfEsHTMLChunk *chunk, UInt16 x, UInt16 y, plMipmap *baseMipmap, UInt32 whichDTMap, hsBool dontRender) { // see if it's already loaded loadedMovie *movie = IMovieAlreadyLoaded(chunk); plLayer* layer = nil; plLayerBink* movieLayer = nil; UInt16 movieWidth=0,movieHeight=0; if (movie) { movieLayer = movie->movieLayer; layer = plLayer::ConvertNoRef(movieLayer->BottomOfStack()); movieWidth = movieLayer->GetWidth(); movieHeight = movieLayer->GetHeight(); } else { // Create the layer and register it. // We'll need a unique name. This is a hack, but an effective hack. static int uniqueSuffix = 0; char buff[256]; sprintf(buff, "%s_%d_ml", GetKey()->GetName(), uniqueSuffix); layer = TRACKED_NEW plLayer; hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation()); sprintf(buff, "%s_%d_m", GetKey()->GetName(), uniqueSuffix++); movieLayer = TRACKED_NEW plLayerBink; hsgResMgr::ResMgr()->NewKey(buff, movieLayer, GetKey()->GetUoid().GetLocation()); movieLayer->GetKey()->RefObject(); // we want to own a ref so we can nuke it at will movieLayer->AttachViaNotify(layer); // Initialize it. char *name = hsWStringToString(chunk->fText.c_str()); movieLayer->SetMovieName(name); delete [] name; movieLayer->Eval(0,0,0); // set up the movie movieWidth = movieLayer->GetWidth(); movieHeight = movieLayer->GetHeight(); if (movieHeight == 0 || movieWidth == 0) // problem loading the file { movieLayer->GetKey()->UnRefObject(); return nil; } } if (layer) { layer->InitToDefault(); layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.0f)); layer->SetOpacity(1.0f); // Blend flags, movies are opaque, but they don't take the whole page, so alphamapping them layer->SetBlendFlags(hsGMatState::kBlendAlpha); // Movie shouldn't have to ZWrite layer->SetZFlags(hsGMatState::kZNoZWrite); // No special shading. layer->SetShadeFlags(0); // Clamp all textures. layer->SetClampFlags(hsGMatState::kClampTexture); // Draw passes individually. layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere); // Shared UV coordinates. layer->SetUVWSrc(0); if( chunk->fFlags & pfEsHTMLChunk::kFloating ) { // Floating movie, ignore the text flow completely and just splat the movie on! x = chunk->fAbsoluteX; y = chunk->fAbsoluteY; } if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kLeft ) x = (UInt16)fPageLMargin; else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight ) x = (UInt16)(baseMipmap->GetWidth() - fPageRMargin - movieWidth); else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter ) x = (UInt16)((baseMipmap->GetWidth() >> 1) - (movieWidth >> 1)); // x and y are in pixels, need to convert to a range of 0 to 1 float xRel = (float)x/(float)baseMipmap->GetWidth(); float yRel = (float)y/(float)baseMipmap->GetHeight(); // Need to convert the scaling to texture space float xScale = (float)baseMipmap->GetWidth()/(float)movieWidth; float yScale = (float)baseMipmap->GetHeight()/(float)movieHeight; if (chunk->fNoResizeImg) { // book is NOT square, there is a h/w ratio of 1/0.7 // calc new size based on how the book has been skewed xScale *= (fWidthScale == 0) ? 1 : (1-fWidthScale); yScale *= (fHeightScale == 0) ? 1 : (1-fHeightScale); yScale *= 1/0.7; // adjust because the book isn't square } hsVector3 scaleVec(xScale,yScale,1), translateVec(-(xRel*xScale),-(yRel*yScale),0); hsMatrix44 scaleMat, translateMat; scaleMat.MakeScaleMat(&scaleVec); translateMat.MakeTranslateMat(&translateVec); hsMatrix44 flipMat; if (chunk->fOnCover) // cover movies need to be y flipped { hsVector3 yTransVec(0,-1,0), invertYVec(1,-1,1); hsMatrix44 invertY, transY; invertY.MakeScaleMat(&invertYVec); transY.MakeTranslateMat(&yTransVec); flipMat = invertY * transY; } else // left page movies need to be x flipped { if ((whichDTMap == pfJournalDlgProc::kTagLeftDTMap) || (whichDTMap == pfJournalDlgProc::kTagTurnBackDTMap)) { hsVector3 xTransVec(-1,0,0), invertXVec(-1,1,1); hsMatrix44 invertX, transX; invertX.MakeScaleMat(&invertXVec); transX.MakeTranslateMat(&xTransVec); flipMat = invertX * transX; } else flipMat = hsMatrix44::IdentityMatrix(); } hsMatrix44 xfm; xfm = translateMat * scaleMat * flipMat; layer->SetTransform(xfm); } if( chunk->fFlags & pfEsHTMLChunk::kCanLink ) { if( whichDTMap == pfJournalDlgProc::kTagRightDTMap || whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap ) x += (UInt16)(baseMipmap->GetWidth()); // Right page rects are offsetted to differentiate if (dontRender) // if we aren't rendering then this link isn't visible, but the index still needs to be valid, so give it a rect of 0,0,0,0 chunk->fLinkRect.Set(0,0,0,0); else chunk->fLinkRect.Set( x, y, movieWidth, movieHeight ); fVisibleLinks.Append( chunk ); } plAnimTimeConvert &timeConvert = movieLayer->GetTimeConvert(); timeConvert.SetBegin(0); timeConvert.SetEnd(movieLayer->GetLength()); if (chunk->fLoopMovie) { timeConvert.SetLoopPoints(0,movieLayer->GetLength()); timeConvert.Loop(); } timeConvert.Start(); // start the show! return movieLayer; } plLayerInterface *pfJournalBook::IMakeBaseLayer(plMipmap *image) { // Create the layer and register it. // We'll need a unique name. This is a hack, but an effective hack. static int uniqueSuffix = 0; char buff[256]; sprintf(buff, "%s_%d", GetKey()->GetName(), uniqueSuffix++); plLayer* layer = TRACKED_NEW plLayer; hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation()); // Initialize it. layer->InitToDefault(); layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f)); if (fTintCover) layer->SetRuntimeColor(hsColorRGBA().Set(fCoverTint.r, fCoverTint.g, fCoverTint.b, 1.f)); layer->SetOpacity(1.f); // Blend flags, opaque for the bottom layer. layer->SetBlendFlags(0); // Only the bottom layer writes it's Z value. layer->SetZFlags(0); // No special shading. layer->SetShadeFlags(hsGMatState::kShadeReallyNoFog); // Clamp all textures. layer->SetClampFlags(hsGMatState::kClampTexture); // Draw passes individually. layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere); // Shared UV coordinates. layer->SetUVWSrc(0); // Set up the transform. hsVector3 yTransVec(0,-1,0), invertYVec(1,-1,1); hsMatrix44 xfm, invertY, transY, flipY; invertY.MakeScaleMat(&invertYVec); transY.MakeTranslateMat(&yTransVec); flipY = invertY * transY; xfm = flipY; layer->SetTransform(xfm); // Set the texture (assumes mipmap is non-nil). plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture); hsgResMgr::ResMgr()->SendRef(image->GetKey(), refMsg, plRefFlags::kActiveRef); return plLayerInterface::ConvertNoRef(layer); } plLayerInterface *pfJournalBook::IMakeDecalLayer(pfEsHTMLChunk *decalChunk, plMipmap *decal, plMipmap *baseMipmap) { // Create the layer and register it. // We'll need a unique name. This is a hack, but an effective hack. static int uniqueSuffix = 0; char buff[256]; sprintf(buff, "%s_%d_d", GetKey()->GetName(), uniqueSuffix++); plLayer* layer = TRACKED_NEW plLayer; hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation()); // Initialize it. layer->InitToDefault(); // tint the layer only if the decal wants to be tinted layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f)); if (decalChunk->fTintDecal) layer->SetRuntimeColor(hsColorRGBA().Set(fCoverTint.r, fCoverTint.g, fCoverTint.b, 1.f)); layer->SetOpacity(1.f); // Blend flags, decals are alpha blended. layer->SetBlendFlags(hsGMatState::kBlendAlpha); // Only the bottom layer writes it's Z value. layer->SetZFlags(hsGMatState::kZNoZWrite); // No special shading. layer->SetShadeFlags(hsGMatState::kShadeReallyNoFog); // Clamp all textures. layer->SetClampFlags(hsGMatState::kClampTexture); // Draw passes individually. layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere); // Shared UV coordinates. layer->SetUVWSrc(0); // Set up the transform. UInt16 x,y; x = decalChunk->fAbsoluteX; y = decalChunk->fAbsoluteY; if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kLeft) x = (UInt16)fPageLMargin; else if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kRight) x = (UInt16)(baseMipmap->GetWidth() - decal->GetWidth()); else if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kCenter) x = (UInt16)((baseMipmap->GetWidth() >> 1) - (decal->GetWidth() >> 1)); // x and y are in pixels, need to convert to a range of 0 to 1 float xRel = (float)x/(float)baseMipmap->GetWidth(); float yRel = (float)y/(float)baseMipmap->GetHeight(); // Need to convert the scaling to texture space float xScale = (float)baseMipmap->GetWidth()/(float)decal->GetWidth(); float yScale = (float)baseMipmap->GetHeight()/(float)decal->GetHeight(); if (decalChunk->fNoResizeImg) { // book is NOT square, there is a h/w ratio of 1/0.7 // calc new size based on how the book has been skewed xScale *= (fWidthScale == 0) ? 1 : (1-fWidthScale); yScale *= (fHeightScale == 0) ? 1 : (1-fHeightScale); yScale *= 1/0.7; // adjust because the book isn't square } hsVector3 scaleVec(xScale,yScale,1), translateVec(-(xRel*xScale),-(yRel*yScale),0), yTransVec(0,-1,0), invertYVec(1,-1,1); hsMatrix44 scaleMat, translateMat, invertY, transY; scaleMat.MakeScaleMat(&scaleVec); translateMat.MakeTranslateMat(&translateVec); invertY.MakeScaleMat(&invertYVec); transY.MakeTranslateMat(&yTransVec); hsMatrix44 xfm, flipY; flipY = invertY * transY; xfm = translateMat * scaleMat * flipY; layer->SetTransform(xfm); // Set the texture (assumes mipmap is non-nil). plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture); hsgResMgr::ResMgr()->SendRef(decal->GetKey(), refMsg, plRefFlags::kActiveRef); return plLayerInterface::ConvertNoRef(layer); } void pfJournalBook::ISetDecalLayers(hsGMaterial *material,hsTArray layers) { // First, clear out the existing layers. int i; for( i = material->GetNumLayers()-1; i >= 0; i-- ) { plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(material->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); hsgResMgr::ResMgr()->SendRef(material->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef); } // Now append our new layers in order. for( i = 0; i < layers.GetCount(); i++ ) { material->AddLayerViaNotify(layers[i]); } } //// IFindFontProps ////////////////////////////////////////////////////////// // Starting at the given chunk, works backwards to determine the full set of current // font properties at that point, or assigns defaults if none were specified void pfJournalBook::IFindFontProps( UInt32 chunkIdx, const wchar_t *&face, UInt8 &size, UInt8 &flags, hsColorRGBA &color, Int16 &spacing ) { enum Which { kFace = 0x01, kSize = 0x02, kFlags = 0x04, kColor = 0x08, kSpacing= 0x10, kAllFound = kFace | kSize | kFlags | kColor | kSpacing }; // Start empty UInt8 found = 0; // Work backwards and fill in our properties chunkIdx++; do { chunkIdx--; if (fHTMLSource.Count() <= chunkIdx) break; // apparently it's sometimes possible for fHTMLSource to be empty (parse errors?) pfEsHTMLChunk *chunk = fHTMLSource[ chunkIdx ]; if( chunk->fType == pfEsHTMLChunk::kFontChange ) { // What do we (still) need? if( !( found & kFace ) && chunk->fText != L"" ) { face = chunk->fText.c_str(); found |= kFace; } if( !( found & kSize ) && chunk->fFontSize > 0 ) { size = chunk->fFontSize; found |= kSize; } if( !( found & kFlags ) && ( chunk->fFlags & pfEsHTMLChunk::kFontMask ) != 0 ) { flags = 0; if( chunk->fFlags & pfEsHTMLChunk::kFontBold ) flags |= plDynamicTextMap::kFontBold; if( chunk->fFlags & pfEsHTMLChunk::kFontItalic ) flags |= plDynamicTextMap::kFontItalic; found |= kFlags; } if( !( found & kColor ) && ( chunk->fFlags & pfEsHTMLChunk::kFontColor ) ) { color = chunk->fColor; found |= kColor; } if( !( found & kSpacing ) && ( chunk->fFlags & pfEsHTMLChunk::kFontSpacing ) ) { spacing = chunk->fLineSpacing; found |= kSpacing; } } } while( chunkIdx != 0 && found != kAllFound ); // Set any un-found defaults if( !( found & kFace ) ) face = L"Arial"; if( !( found & kSize ) ) size = 24; if( !( found & kFlags ) ) flags = 0; if( !( found & kColor ) ) color.Set( 0.f, 0.f, 0.f, 1.f ); if( !( found & kSpacing ) ) spacing = 0; } //// IFindLastAlignment ////////////////////////////////////////////////////// // Find the last paragraph chunk and thus the last par alignment settings UInt8 pfJournalBook::IFindLastAlignment( void ) const { Int32 idx; for( idx = fHTMLSource.GetCount() - 1; idx >= 0; idx-- ) { if( fHTMLSource[ idx ]->fType == pfEsHTMLChunk::kParagraph && fHTMLSource[ idx ]->fFlags != 0 ) return (UInt8)(fHTMLSource[ idx ]->fFlags); } return pfEsHTMLChunk::kLeft; } //// IRecalcPageStarts /////////////////////////////////////////////////////// // Ensures that all the page starts are calced up to the given page (but not including it) void pfJournalBook::IRecalcPageStarts( UInt32 upToPage ) { UInt32 page; // Well, sadly, we can't really calc the page starts without at least a DTMap // we can change things on...so we just pick one and render. Note: this WILL // trash the font settings on the given DTMap! // We assume that the stored page starts we already have are accurate, so // just start from there and calc onward for( page = fPageStarts.GetCount()-1; page < upToPage && page <= fLastPage; page++ ) { // normally we would surpress rendering the pages, but that seems to have a bug in it // that causes lost text that the rendering doesn't have. Since it isn't very costly to // actually draw them all (even in large journals), we're just going to do it IRenderPage( page, pfJournalDlgProc::kTagTurnBackDTMap, false ); // Reset any "visible" links since they aren't really visible UInt16 i; for (i=0; ifLinkRect.Set(0,0,0,0); } } } //// ISendNotify ///////////////////////////////////////////////////////////// // Just sends out a notify to our currently set receiver key void pfJournalBook::ISendNotify( UInt32 type, UInt32 linkID ) { if( fCallbackKey != nil ) { plNotifyMsg *pMsg = TRACKED_NEW plNotifyMsg; pMsg->AddBookEvent( type, linkID ); pMsg->SetBCastFlag( plMessage::kNetPropagate, false ); // don't deliver networked! pMsg->Send( fCallbackKey ); } } //// SetBookSize ///////////////////////////////////////////////////////////// // Sets the book size scaling. 1,1 would be full size, 0,0 is the smallest size possible // Note: internally we store these as the seek positions on our animations, // so the incoming parameters are actually inverse of what we finally want void pfJournalBook::SetBookSize( hsScalar width, hsScalar height ) { fWidthScale = 1.f - width; fHeightScale = 1.f - height; if( fBookGUIs[fCurBookGUI]->CurBook() == this ) fBookGUIs[fCurBookGUI]->SetCurrSize( fWidthScale, fHeightScale ); } //// ILoadAllImages ////////////////////////////////////////////////////////// // Load (or unload) all the images for the book void pfJournalBook::ILoadAllImages( hsBool unload ) { UInt32 i; // load the cover if( fCoverFromHTML && fCoverMipKey != nil ) { if( unload ) fBookGUIs[fCurBookGUI]->GetKey()->Release( fCoverMipKey ); else { plGenRefMsg *ref = TRACKED_NEW plGenRefMsg( fBookGUIs[fCurBookGUI]->GetKey(), plRefMsg::kOnCreate, -1, kRefImage ); hsgResMgr::ResMgr()->AddViaNotify( fCoverMipKey, ref, plRefFlags::kActiveRef ); } } for( i = 0; i < fHTMLSource.GetCount(); i++ ) { if( fHTMLSource[ i ]->fType == pfEsHTMLChunk::kImage && fHTMLSource[ i ]->fImageKey != nil ) { if( unload ) fBookGUIs[fCurBookGUI]->GetKey()->Release( fHTMLSource[ i ]->fImageKey ); else { plGenRefMsg *ref = TRACKED_NEW plGenRefMsg( fBookGUIs[fCurBookGUI]->GetKey(), plRefMsg::kOnCreate, -1, kRefImage ); hsgResMgr::ResMgr()->AddViaNotify( fHTMLSource[ i ]->fImageKey, ref, plRefFlags::kActiveRef ); } } } } void pfJournalBook::IPurgeDynaTextMaps( ) { plDynamicTextMap* turnFront = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnFrontDTMap ); if (turnFront) turnFront->PurgeImage(); plDynamicTextMap* right = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagRightDTMap ); if (right) right->PurgeImage(); plDynamicTextMap* turnBack = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnBackDTMap ); if (turnBack) turnBack->PurgeImage(); plDynamicTextMap* left = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagLeftDTMap ); if (left) left->PurgeImage(); pfGUIMultiLineEditCtrl *leftEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagLeftEditCtrl ); if (leftEdit) leftEdit->PurgeDynaTextMapImage(); pfGUIMultiLineEditCtrl *rightEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagRightEditCtrl ); if (rightEdit) rightEdit->PurgeDynaTextMapImage(); pfGUIMultiLineEditCtrl *turnFrontEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagTurnFrontEditCtrl ); if (turnFrontEdit) turnFrontEdit->PurgeDynaTextMapImage(); pfGUIMultiLineEditCtrl *turnBackEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagTurnBackEditCtrl ); if (turnBackEdit) turnBackEdit->PurgeDynaTextMapImage(); } std::string pfJournalBook::GetEditableText() { pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagLeftEditCtrl ); if (left) { char *temp = left->GetNonCodedBuffer(); std::string retVal = temp; delete [] temp; return retVal; } return ""; } void pfJournalBook::SetEditableText(std::string text) { pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagLeftEditCtrl ); if (left) { left->SetBuffer(text.c_str()); left->ForceUpdate(); } }