/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. 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 #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 "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/plLayerAVI.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_t fFontSize; uint32_t fFlags; uint8_t fType; uint32_t fEventID; pcSmallRect fLinkRect; // Used only for image chunks, and only when stored in the fVisibleLinks array hsColorRGBA fColor; uint16_t fAbsoluteX, fAbsoluteY; float fCurrOpacity; // For SFX images float fSFXTime; // For SFX images float fMinOpacity, fMaxOpacity; hsColorRGBA fCurrColor; hsColorRGBA fOffColor, fOnColor; bool fNoResizeImg; int16_t fLineSpacing; bool fTintDecal; bool fLoopMovie; bool fOnCover; // if true, the movie is on the cover uint8_t 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_t 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_t size, uint32_t 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_t event ) { if (fBook) { if( ctrl == fBook->fLeftPageMap ) { if( event == pfGUIClickMapCtrl::kMouseHovered ) { if (fBook->fCurrBook) { // Update our custom cursor on the map int32_t 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_t 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 void OnEndOfControlList(int32_t cursorPos) { bookData->HitEndOfControlList(cursorPos); } virtual void OnBeginningOfControlList(int32_t cursorPos) { bookData->HitBeginningOfControlList(cursorPos); } }; //// Book data class ///////////////////////////////////////////////////////// pfBookData::pfBookData(const plString &guiName /* = nil */) { fCurrBook = nil; fDialog = nil; fCoverButton = fTurnPageButton = nil; fLeftPageMap = fRightPageMap = nil; fCoverLayer = nil; fCoverMaterial = nil; uint16_t 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.IsEmpty()) 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()); } bool 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( (bool)( callback->fUser & 0x01 ) ? true : false ); else if( callback->fUser & 0x02 ) StartTriggeredFlip( (bool)( callback->fUser & 0x01 ) ? true : false ); else IFinishTriggeredFlip( (bool)( 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( (float)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(), new plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefDialog), plRefFlags::kPassiveRef); // Hijack the dialog proc with our own templateDlg->SetHandlerForAll(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(), 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(new pfBookMultiLineEditProc(this)); fTurnFrontEditCtrl->SetNext(fTurnBackEditCtrl); // these are always back to back } } //// IGetDTMap /////////////////////////////////////////////////////////////// // Just a quick helper plDynamicTextMap *pfBookData::GetDTMap(uint32_t which) { pfGUIDynDisplayCtrl *display = pfGUIDynDisplayCtrl::ConvertNoRef(fDialog->GetControlFromTag(which)); return display->GetMap(0); } //// GetEditCtrl ///////////////////////////////////////////////////////////// pfGUIMultiLineEditCtrl *pfBookData::GetEditCtrl(uint32_t 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(float 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 float deltaT = currTime - fBaseSFXTime; uint32_t 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_t isOdd = 0; float 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(bool 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 = 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(bool flipBackwards, bool 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(); plString animName = fTurnPageButton->GetAnimationName(); plAnimCmdMsg *msg = 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 = 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 = 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(bool 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(bool 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_t 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(float w, float h) { fWidthCtrl->SetCurrValue(w); fHeightCtrl->SetCurrValue(h); } //// PlayBookCloseAnim ////////////////////////////////////////////////////// // Triggers our animation for closing or opening the book. void pfBookData::PlayBookCloseAnim(bool closeIt /*= true*/, bool 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_t cursorPos) { fAdjustCursorTo = cursorPos; if (fCurrBook) fCurrBook->NextPage(); } void pfBookData::HitBeginningOfControlList(int32_t cursorPos) { fAdjustCursorTo = cursorPos; if (fCurrBook) fCurrBook->PreviousPage(); } void pfBookData::EnableEditGUI(bool 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"] = 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 plString &guiName ) { if (fBookGUIs.find(guiName) == fBookGUIs.end()) // is it already loaded? { // nope, load it fBookGUIs[guiName] = new pfBookData(guiName); hsgResMgr::ResMgr()->NewKey(guiName,fBookGUIs[guiName],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation()); fBookGUIs[guiName]->LoadGUI(); } } void pfJournalBook::UnloadGUI( const plString &guiName ) { if (guiName.Compare("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()) { plString 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]); // 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 plString &guiName /* = nil */ ) { if (!guiName.IsEmpty()) fCurBookGUI = guiName; else fCurBookGUI = "BkBook"; if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end()) { fBookGUIs[fCurBookGUI] = new pfBookData(fCurBookGUI); hsgResMgr::ResMgr()->NewKey(fCurBookGUI,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 plString &guiName /* = nil */ ) { if (!guiName.IsEmpty()) fCurBookGUI = guiName; else fCurBookGUI = "BkBook"; if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end()) { fBookGUIs[fCurBookGUI] = new pfBookData(fCurBookGUI); hsgResMgr::ResMgr()->NewKey(fCurBookGUI,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 ////////////////////////////////////////////////////////////// bool pfJournalBook::MsgReceive( plMessage *pMsg ) { return hsKeyedObject::MsgReceive( pMsg ); } void pfJournalBook::SetGUI( const plString &guiName ) { if (!guiName.IsEmpty()) 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( bool 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) plLayerAVI *movieLayer = IMakeMovieLayer(fCoverDecals[i],0,0,mip,pfJournalDlgProc::kTagLeftDTMap,false); loadedMovie *movie = 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( bool 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++ ) { plLayerAVI *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_t 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( bool closeNotOpen, bool 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(); plString animName = fBookGUIs[fCurBookGUI]->CoverButton()->GetAnimationName(); plAnimCmdMsg *msg = 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 = 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_t 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_t pfJournalBook::IFindCurrVisibleLink( bool rightNotLeft, bool 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 *= (float)dtMap->GetWidth(); pt.fY *= (float)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_t i; for( i = 0; i < fVisibleLinks.GetCount(); i++ ) { if( fVisibleLinks[ i ]->fLinkRect.Contains( (int16_t)pt.fX, (int16_t)pt.fY ) ) { // Found a visible link return (int32_t)i; } } return -1; } //// IHandleLeftSideClick //////////////////////////////////////////////////// void pfJournalBook::IHandleLeftSideClick( void ) { if( fBookGUIs[fCurBookGUI]->CurrentlyTurning() ) return; int32_t 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_t 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_t idx, pfBookData::WhichSide which ) { // Special processing for checkboxes--toggle our state, switch our opacity // and then send a notify about our new state bool 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_t pageNumber ) { // Put us here, but only on an even page (odd pages go on the right, y'know) // (no need for a range check, going past the end simply puts you on a blank page, able to go backward but not forward) fCurrentPage = pageNumber & ~0x00000001; fVisibleLinks.Reset(); IRenderPage( fCurrentPage, pfJournalDlgProc::kTagLeftDTMap ); IRenderPage( fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap ); fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides ); } //// SetEditable ///////////////////////////////////////////////////////////// void pfJournalBook::SetEditable(bool 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_t IConvertHex( const wchar_t *str ) { uint32_t 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 bool pfJournalBook::ICompileSource( const wchar_t *source, const plLocation &hintLoc ) { IFreeSource(); pfEsHTMLChunk *chunk, *lastParChunk = new pfEsHTMLChunk( nil ); const wchar_t *c, *start; wchar_t name[ 128 ], option[ 256 ]; float bookWidth=1.0, bookHeight=1.0; uint8_t 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_t 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_t count = ((uintptr_t)c - (uintptr_t)start)/2; // wchar_t is 2 bytes wchar_t *temp = 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 = 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 = 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 = wcstol(option, NULL, 0); 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 = wcstol(comma + 1, NULL, 0); *comma = 0; } chunk->fAbsoluteX = wcstol(option, NULL, 0); } 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 = (float)atof( comma2 + 1 ); *comma2 = 0; } chunk->fMinOpacity = (float)atof( comma + 1 ); *comma = 0; } chunk->fSFXTime = (float)atof( cOption ); delete [] cOption; } else if( wcsicmp( name, L"opacity" ) == 0 ) { chunk->fFlags |= pfEsHTMLChunk::kTranslucent; char *cOption = hsWStringToString(option); chunk->fCurrOpacity = (float)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( wcstol(comma2 + 1, NULL, 0) != 0 ) chunk->fFlags |= pfEsHTMLChunk::kChecked; *comma2 = 0; } uint32_t 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_t 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 = 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 = new pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kPageBreak: c += 3; chunk = new pfEsHTMLChunk(); while( IGetNextOption( c, name, option ) ) { } fHTMLSource.Append( chunk ); // Start new paragraph chunk after this one lastParChunk = new pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kFontChange: c += 5; chunk = new pfEsHTMLChunk( nil, 0, 0 ); while( IGetNextOption( c, name, option ) ) { if( wcsicmp( name, L"style" ) == 0 ) { uint8_t 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 = wcstol(option, NULL, 0); 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 = wcstol(option, NULL, 0); chunk->fFlags |= pfEsHTMLChunk::kFontSpacing; } } fHTMLSource.Append( chunk ); // Start new paragraph chunk after this one lastParChunk = new pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kMargin: c += 7; while(IGetNextOption(c,name,option)) { if (wcsicmp(name,L"top") == 0) fPageTMargin = wcstol(option, NULL, 0); else if (wcsicmp(name,L"left") == 0) fPageLMargin = wcstol(option, NULL, 0); else if (wcsicmp(name,L"bottom") == 0) fPageBMargin = wcstol(option, NULL, 0); else if (wcsicmp(name,L"right") == 0) fPageRMargin = wcstol(option, NULL, 0); } // 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 = 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 = new pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kDecal: c += 6; chunk = 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 = wcstol(comma + 1, NULL, 0); *comma = 0; } chunk->fAbsoluteX = wcstol(option, NULL, 0); } 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 = new pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kMovie: c += 6; chunk = 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 = wcstol(option, NULL, 0); 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 = wcstol(comma + 1, NULL, 0); *comma = 0; } chunk->fAbsoluteX = wcstol(option, NULL, 0); } 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 = new pfEsHTMLChunk( nil ); lastParChunk->fFlags = IFindLastAlignment(); break; case pfEsHTMLChunk::kEditable: c += 9; SetEditable(true); chunk = new pfEsHTMLChunk(); while( IGetNextOption( c, name, option ) ) { } fHTMLSource.Append( chunk ); // Start new paragraph chunk after this one lastParChunk = 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_t count = (uintptr_t)c - (uintptr_t)start; wchar_t *temp = 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_t pfJournalBook::IGetTagType( const wchar_t *string ) { if( string[ 0 ] != '<' ) return pfEsHTMLChunk::kEmpty; struct TagRec { const wchar_t *fTag; uint8_t 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_t 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; } bool 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_t len = ((uintptr_t)string - (uintptr_t)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 = ((uintptr_t)string - (uintptr_t)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 = ((uintptr_t)string - (uintptr_t)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_t 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++ ) { plLayerAVI *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 "plGImage/plJPEG.h" #include "plGImage/plPNG.h" #endif plKey pfJournalBook::IGetMipmapKey( const wchar_t *name, const plLocation &loc ) { plString cName = plString::FromWchar(name); #ifndef PLASMA_EXTERNAL_RELEASE if( cName.Find( '/' ) >= 0 || cName.Find( '\\' ) >= 0 ) { // For internal use only--allow local path names of PNG and JPEG images, to // facilitate fast prototyping plMipmap *mip; if( cName.Find( ".png" ) >= 0 ) mip = plPNG::Instance().ReadFromFile( cName.c_str() ); else mip = plJPEG::Instance().ReadFromFile( cName.c_str() ); hsgResMgr::ResMgr()->NewKey( cName, mip, loc ); 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 ) { 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 ) { return key; } // Do a search through our current age with just the name given if( plNetClientMgr::GetInstance() != nil ) { plString thisAge = plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeName(); if (!thisAge.IsNull()) { key = plKeyFinder::Instance().StupidSearch( thisAge, "", plMipmap::Index(), cName, true ); if( key != nil ) { return key; } } } 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_t page, uint32_t whichDTMap, bool 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 plLayerMovie layers { plLayerInterface *matLayer = material->GetLayer(i); plLayerAVI *movie = plLayerAVI::ConvertNoRef(matLayer); if (movie) // if it was a movie layer { plMatRefMsg* refMsg = 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() || page > fLastPage, "UnInitialized page start!"); if( page <= fLastPage && page < fPageStarts.GetCount()) // Added this as a crash-prevention bandaid - MT { uint32_t idx; uint16_t width, height, y, x, ascent, lastX, lastY; uint8_t fontFlags, fontSize; plString fontFace; hsColorRGBA fontColor; int16_t fontSpacing; bool 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_t)fPageLMargin, y = (uint16_t)fPageTMargin; y < (uint16_t)(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_t)fPageLMargin; // reset X if our justification changes } else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight ) { dtMap->SetJustify( plDynamicTextMap::kRightJustify ); x = (uint16_t)fPageLMargin; // reset X if our justification changes } else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter ) { dtMap->SetJustify( plDynamicTextMap::kCenter ); x = (uint16_t)fPageLMargin; // reset X if our justification changes } dtMap->SetFirstLineIndent( (int16_t)(x - fPageLMargin) ); width = (uint16_t)(512 - fPageLMargin - fPageRMargin); height = (uint16_t)(512 - fPageBMargin - y); uint32_t lastChar; dtMap->CalcWrappedStringSize( chunk->fText.c_str(), &width, &height, &lastChar, &ascent, &lastX, &lastY ); width = (uint16_t)(512 - fPageLMargin - fPageRMargin); if( !suppressRendering ) dtMap->DrawWrappedString( (uint16_t)fPageLMargin, y, chunk->fText.c_str(), width, (uint16_t)(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 (lastChar < chunk->fText.size() && 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 = new wchar_t[fTextLen+1]; wcscpy(s,chunk->fText.c_str()); s[fTextLen] = L'\0'; // Note: Makes a copy of the string pfEsHTMLChunk *c2 = 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_t)(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_t)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_t)(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_t)fPageLMargin; else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight ) x = (uint16_t)(512 - fPageRMargin - mip->GetWidth()); else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter ) x = (uint16_t)(256 - ( mip->GetWidth() >> 1 )); IDrawMipmap( chunk, x, y, mip, dtMap, whichDTMap, suppressRendering ); y += (uint16_t)(mip->GetHeight()); x = (uint16_t)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_t)(512 - fPageTMargin - fPageBMargin); x = (uint16_t)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? plLayerAVI *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_t 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_t)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_t)movieHeight; x = (uint16_t)fPageLMargin; } if (!movieAlreadyLoaded) // if the movie wasn't already cached, cache it { movie = 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 plLayerMovie layers { plLayerInterface *matLayer = source->GetLayer(i); plLayerAVI *movie = plLayerAVI::ConvertNoRef(matLayer); if (movie) // if it was a movie layer { plMatRefMsg* refMsg = new plMatRefMsg(source->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it hsgResMgr::ResMgr()->SendRef(source->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef); moviesOnPage.Append(movie); } } // clear the destination's movies (if it has any) for( i = 0; i < dest->GetNumLayers(); i++ ) // remove all plLayerMovie layers { plLayerInterface *matLayer = dest->GetLayer(i); plLayerAVI *movie = plLayerAVI::ConvertNoRef(matLayer); if (movie) // if it was a movie layer { plMatRefMsg* refMsg = 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_t x, uint16_t y, plMipmap *mip, plDynamicTextMap *dtMap, uint32_t whichDTMap, bool dontRender ) { plMipmap *copy = 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_t width = (uint32_t)(mip->GetWidth()*xScale); uint32_t height = (uint32_t)(mip->GetHeight()*yScale); uint16_t xShift; uint16_t yShift; if (dtMap->GetWidth() < width) width = dtMap->GetWidth(); if (dtMap->GetHeight() < height) height = dtMap->GetHeight(); if (height < mip->GetHeight()) { yShift = (uint16_t)((mip->GetHeight()-height)/2); if (y+yShift+height > dtMap->GetHeight()) y = (uint16_t)(dtMap->GetHeight()-height); else y += yShift; } else { yShift = (uint16_t)((height-mip->GetHeight())/2); if (yShift > y) y = 0; else y -= yShift; } if (width < mip->GetWidth()) { xShift = (uint16_t)((mip->GetWidth()-width)/2); if (x+xShift+width > dtMap->GetWidth()) x = (uint16_t)(dtMap->GetWidth()-width); else x += xShift; } else { xShift = (uint16_t)((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_t)width,(uint16_t)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_t)(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_t)(chunk->fCurrOpacity * 255.f); } dtMap->Composite( copy, x, y, &opts ); } if( chunk->fFlags & pfEsHTMLChunk::kCanLink ) { if( whichDTMap == pfJournalDlgProc::kTagRightDTMap || whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap ) x += (uint16_t)(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_t)(copy->GetWidth()), (int16_t)(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_t index) { loadedMovie *movie = IGetMovieByIndex(index); if (movie) return movie->movieLayer->GetKey(); return plKey(nil); } pfJournalBook::loadedMovie *pfJournalBook::IGetMovieByIndex(uint8_t index) { int i; for (i=0; imovieChunk->fMovieIndex == index) return fLoadedMovies[i]; } return nil; } plLayerAVI *pfJournalBook::IMakeMovieLayer(pfEsHTMLChunk *chunk, uint16_t x, uint16_t y, plMipmap *baseMipmap, uint32_t whichDTMap, bool dontRender) { // see if it's already loaded loadedMovie *movie = IMovieAlreadyLoaded(chunk); plLayer* layer = nil; plLayerAVI* movieLayer = nil; uint16_t 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; plString buff; buff = plFormat("{}_{}_ml", GetKey()->GetName(), uniqueSuffix); layer = new plLayer; hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation()); buff = plFormat("{}_{}_m", GetKey()->GetName(), uniqueSuffix++); movieLayer = new plLayerAVI; 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_t)fPageLMargin; else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight ) x = (uint16_t)(baseMipmap->GetWidth() - fPageRMargin - movieWidth); else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter ) x = (uint16_t)((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_t)(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; plString buff = plFormat("{}_{}", GetKey()->GetName(), uniqueSuffix++); plLayer* layer = 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 = 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; plString buff = plFormat("{}_{}_d", GetKey()->GetName(), uniqueSuffix++); plLayer* layer = 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_t x,y; x = decalChunk->fAbsoluteX; y = decalChunk->fAbsoluteY; if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kLeft) x = (uint16_t)fPageLMargin; else if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kRight) x = (uint16_t)(baseMipmap->GetWidth() - decal->GetWidth()); else if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kCenter) x = (uint16_t)((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 = 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 = 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_t chunkIdx, plString &face, uint8_t &size, uint8_t &flags, hsColorRGBA &color, int16_t &spacing ) { enum Which { kFace = 0x01, kSize = 0x02, kFlags = 0x04, kColor = 0x08, kSpacing= 0x10, kAllFound = kFace | kSize | kFlags | kColor | kSpacing }; // Start empty uint8_t 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 = plString::FromWchar(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 = "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_t pfJournalBook::IFindLastAlignment( void ) const { int32_t idx; for( idx = fHTMLSource.GetCount() - 1; idx >= 0; idx-- ) { if( fHTMLSource[ idx ]->fType == pfEsHTMLChunk::kParagraph && fHTMLSource[ idx ]->fFlags != 0 ) return (uint8_t)(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_t upToPage ) { uint32_t 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_t 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_t type, uint32_t linkID ) { if( fCallbackKey != nil ) { plNotifyMsg *pMsg = 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( float width, float 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( bool unload ) { uint32_t i; // load the cover if( fCoverFromHTML && fCoverMipKey != nil ) { if( unload ) fBookGUIs[fCurBookGUI]->GetKey()->Release( fCoverMipKey ); else { plGenRefMsg *ref = 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 = 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(); } }