home *** CD-ROM | disk | FTP | other *** search
- #include "ocheaders.h"
- #include "CBaseControl.h"
- #include "CErrorControl.h"
- #include "CTickerControl.h"
- #include "CTick.h"
- #include "Colors.h"
- #include "BDAssert.h"
- #include "FnAssert.h"
- #include "dispatch.h"
- #include "CTickerError.h"
- #include "BDUtils.h"
- #include <iterator.h>
- #include <ctype.h>
- #include <math.h>
- #include <stdio.h>
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::CTickerControl
- //
-
- CTickerControl::CTickerControl(void) : CBaseControl()
- {
- const Rect nullRect = { 0, 0, 0, 0 };
-
- // user definable parameters
- strcpy(mDataURL, "");
- mDataActive = true;
- mScrollWidth = TICK_WIDTH;
- mScrollInterval = SCROLL_INTERVAL;
- mReloadInterval = RELOAD_INTERVAL;
- mForeColor = RGB_BLACK;
- mBackColor = RGB_LTGRAY;
- mOffsetValues = 0;
- mUserOffsetValues = false;
-
- // protected:
- mBirthTime = ::TickCount(); // should be safe inside a consructor
- mNextScrollTime = mBirthTime + mScrollInterval;
- mNextReloadTime = mBirthTime + mReloadInterval;
- mData = NULL;
- mDataTemp = NULL;
- mDataSize = 0;
- mBound = false;
- mBindSite = NULL;
- mFeedDown = false;
- mOffscreenGWorld = NULL;
- mGWorldPixelDepth = 8;
- mRectOffscreenBounds = nullRect;
- mGWorldCTable = 0;
- mGWorldAGDevice = NULL;
- mGWorldFlags = 0;
- mPixOffscreenPixMap = NULL;
- mQNew = NULL;
- mQCache = NULL;
- mOnstage = NULL;
- mMaxCacheSize = QUEUE_SIZE;
- mReadTillNow = 0;
- mOffscreenWorldReady = false;
- mOffscreenImageReady = false;
- mPurgeOnstage = false;
- mDataValidation = dataUnconfirmed;
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::~CTickerControl
- //
-
- CTickerControl::~CTickerControl(void)
- {
- // if we're idling, get rid of our idling
- if ( mIsIdling )
- mContainerSiteP->SetIdleTime(RemoveAllIdlers, 0);
-
- // clean up data
- if ( mData != NULL )
- {
- ::CoTaskMemFree(mData);
- mData = NULL;
- }
-
- // clean up offscreen world
- if ( mOffscreenGWorld != NULL )
- {
- ::DisposeGWorld( mOffscreenGWorld );
- mOffscreenGWorld = NULL;
- mPixOffscreenPixMap = NULL;
- }
-
- // Clear out both queues
- ClearQueues();
-
- if ( mCurrentLeftOvers != NULL )
- {
- ::CoTaskMemFree(mCurrentLeftOvers);
- mCurrentLeftOvers = NULL;
- }
- }
-
- #pragma mark === CTickerControl::IUnknown ===
-
- //
- // CPictControl::IUnknown::QueryInterface
- //
- // Returns a pointer to the specified interface on a component to which a
- // client currently holds an interface pointer.
- //
- STDMETHODIMP
- CTickerControl::QueryInterface(REFIID inRefID, void** outObj)
- {
- if ( inRefID == IID_IBindStatusCallback )
- return CBaseBindStatusCallback::QueryInterface(inRefID, outObj);
- else
- return CBaseControl::QueryInterface(inRefID, outObj);
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IControl::Draw
- //
-
- STDMETHODIMP
- CTickerControl::Draw( THIS_ DrawContext* Context)
- {
- if (Context->DrawAspect != DVASPECT_CONTENT)
- return ResultFromScode(DV_E_DVASPECT);
-
- // we've been told to draw in an aspect which requires idling
- // if we weren't already idling then start
- if ( !mIsIdling && mContainerSiteP )
- mIsIdling = mContainerSiteP->SetIdleTime(0, 0) == S_OK;
-
- if ( mOffscreenImageReady )
- MoveOnScreen(Context);
-
- return ResultFromScode(S_OK);
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IControl::DoMouse
- //
-
- STDMETHODIMP
- CTickerControl::DoMouse(MouseEventType inMouseET, PlatformEvent* inEvent)
- {
- long currentTime = ::TickCount();
-
- if ( inMouseET == MouseDown )
- {
- mNextReloadTime = currentTime + mReloadInterval;
- ReloadTicker();
- }
-
- return ResultFromScode(S_OK);
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IControl::DoIdle
- //
-
- STDMETHODIMP
- CTickerControl::DoIdle(Uint32 IdleRefCon)
- {
- long currentTime = ::TickCount();
-
- if (currentTime >= mNextScrollTime)
- {
- mNextScrollTime = currentTime + mScrollInterval;
- MoveTicker(mScrollWidth);
- }
-
- if (currentTime >= mNextReloadTime)
- {
- mNextReloadTime = currentTime + mReloadInterval;
- ReloadTicker();
- }
-
-
- return ResultFromScode(S_OK);
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IPersistPropertyBag::Load
- //
-
- STDMETHODIMP
- CTickerControl::Load(IPropertyBag* PropBag, IErrorLog* ErrorLog)
- {
- LoadTextState(PropBag, ErrorLog);
-
- BindToData();
-
- return ResultFromScode(S_OK);
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IPersistPropertyBag::LoadTextState
- //
-
- STDMETHODIMP CTickerControl::LoadTextState(IPropertyBag *pPropertyBag,IErrorLog *pErrorLog)
- {
- const long maxLength = 256;
- char propertyString[maxLength];
-
- // try to load in each property. if we can't get it, then leave
- // things at the default.
-
- // The URL containing the stock ticker data
- if ( ::LoadPropertyString(pPropertyBag, "dataobjectname", propertyString, maxLength, pErrorLog) )
- strcpy(mDataURL, propertyString);
-
- // Is the data object active? -- 1, y, or Y means "YES" (anything else, i.e. 0, n, or N means "NO")
- if ( ::LoadPropertyString(pPropertyBag, "dataobjectactive", propertyString, maxLength, pErrorLog) )
- mDataActive = ((propertyString[0] == '1') || (propertyString[0] == 'y')|| (propertyString[0] == 'Y'));
-
- // The scroll width
- if ( ::LoadPropertyString(pPropertyBag, "scrollwidth", propertyString, maxLength, pErrorLog) )
- mScrollWidth = atoi(propertyString);
-
- // The scroll interval
- if ( ::LoadPropertyString(pPropertyBag, "scrollinterval", propertyString, maxLength, pErrorLog) )
- {
- mScrollInterval = atoi(propertyString);
- mNextScrollTime = mBirthTime + mScrollInterval;
- }
-
- // The foreground color
- if ( ::LoadPropertyString(pPropertyBag, "forecolor", propertyString, maxLength, pErrorLog) )
- LoadColor(&mForeColor, propertyString);
-
- // The background color
- if ( ::LoadPropertyString(pPropertyBag, "backcolor", propertyString, maxLength, pErrorLog) )
- LoadColor(&mBackColor, propertyString);
-
- // The Reload Interval
- if ( ::LoadPropertyString(pPropertyBag, "reloadinterval", propertyString, maxLength, pErrorLog) )
- {
- mReloadInterval = atoi(propertyString);
- mNextReloadTime = mBirthTime + mReloadInterval;
- }
-
- // Do we want the values offset in the display? -- 1, y, or Y means "YES" (anything else, i.e. 0, n, or N means "NO")
- if ( ::LoadPropertyString(pPropertyBag, "offsetvalues", propertyString, maxLength, pErrorLog) )
- {
- mOffsetValues = atoi(propertyString);
- mUserOffsetValues = true;
- }
-
- return S_OK;
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IBindStatusCallback::OnStopBinding
- //
-
- STDMETHODIMP
- CTickerControl::OnStopBinding(HRESULT Result, const char* Error)
- {
- mBound = true;
-
- if (mBindSite)
- {
- mBindSite->Release();
- mBindSite = 0;
- }
-
- return ResultFromScode(S_OK);
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IBindStatusCallback::OnDataAvailable
- //
-
- STDMETHODIMP
- CTickerControl::OnDataAvailable(DWORD BSCF, DWORD Size, FORMATETC* FormatEtc, STGMEDIUM* StgMedium)
- {
- CBaseBindStatusCallback::OnDataAvailable(BSCF, Size, FormatEtc, StgMedium);
-
- // Check for first data notification.
- HRESULT hr = NOERROR;
- if (BSCF & BSCF_FIRSTDATANOTIFICATION)
- {
- if (mData)
- ::CoTaskMemFree(mData);
- mData = NULL;
- mReadTillNow = 0;
- }
-
- // Begin normal data processing
- char * current;
- if ( mDataSize ) // mSize is 0 if BSCF_LASTDATANOTIFICATION
- {
- try
- {
- // if we've read previously && there's valid data
- if ( mReadTillNow > 0 )
- {
- if ( ContinueRead() )
- {
- // reallocate mData to the size of m_TotalStreamLen(+1)
- // to contain the accumulated total data 'till now
- ASSERT((mData != NULL), "Unexpected NULL data pointer");
- mDataTemp = mData;
- mData = (char *)::CoTaskMemAlloc(mTotalStreamLen+1); // +1 for eventual null term
- if ( mData == NULL )
- throw CTickerError(DATA_REALLOCATION_ERROR);
- memcpy ( mData, mDataTemp, mReadTillNow );
- current = mData + mReadTillNow;
- ::CoTaskMemFree(mDataTemp);
- mDataTemp = NULL;
- }
- }
- else // first read
- {
- mData = (char *)::CoTaskMemAlloc(Size+1); // +1 for eventual null term
- if ( mData == NULL )
- throw CTickerError(DATA_ALLOCATION_ERROR);
- current = mData;
- }
-
- if ( ContinueRead() )
- {
- // read the stream
- DWORD dwRead = 0;
- DWORD dwStillToRead = mDataSize;
- DWORD dwReadTillNow = 0; // used for sanity check only
- do
- {
- // note: reading dwStillToRead each time, which gets decremented!
- hr = StgMedium->pstm->Read(current,dwStillToRead,&dwRead);
-
- if( SUCCEEDED(hr) || hr == E_PENDING )
- {
- current += dwRead; // points to the end of current data in the buffer
- dwStillToRead -= dwRead; // decrement by the amount we just read
- dwReadTillNow += dwRead; // for sanity check below only
- }
- }
- while ( (hr == S_OK) && dwStillToRead ); // read until there's nothing left
-
- mReadTillNow += Size;
-
- ASSERT((dwReadTillNow == Size), "Unexpected data length"); // sanity check
- }
- }
-
- // error handling
- catch ( CTickerError &tickerError )
- {
- tickerError.HandleError();
- }
- }
-
- // last data notification
- if ((BSCF & BSCF_LASTDATANOTIFICATION) && mReadTillNow && ContinueRead())
- {
- mData[mReadTillNow] = '\0'; // null termination
- OnNewTextData();
- if (mData)
- CoTaskMemFree(mData);
- mData = 0;
- // m_fCanDownLoadNow = TRUE;
- }
-
- return (hr);
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::LoadColor
- //
-
- Boolean CTickerControl::LoadColor(RGBColor * theColor, char * colorString)
- {
- Boolean didSet = true;
-
- for ( short i = 0; i < strlen(colorString); i++ )
- tolower(colorString[i]);
-
- if ( streq(colorString, "black") )
- *theColor = RGB_BLACK;
- else if ( streq(colorString, "white") )
- *theColor = RGB_WHITE;
- else if ( streq(colorString, "red") )
- *theColor = RGB_RED;
- else if ( streq(colorString, "green") )
- *theColor = RGB_GREEN;
- else if ( streq(colorString, "blue") )
- *theColor = RGB_BLUE;
- else if ( streq(colorString, "cyan") )
- *theColor = RGB_CYAN;
- else if ( streq(colorString, "magenta") )
- *theColor = RGB_MAGENTA;
- else if ( streq(colorString, "yellow") )
- *theColor = RGB_YELLOW;
- else
- {
- didSet = false; // reverse Boolean logic -- false unless we succeed
-
- // We're expecting a 32-bit hex RGB value in the format #00bbggrr.
- //
- // NOTE: To be compatible with Windows, if the high-order bit is set,
- // the low-order byte is supposed to be treated as a system color index.
- // We'll punt on this for now.
-
- Boolean punt = false;
-
- // sanity check on string format. #bbggrr, #0bbggrr, or #00bbggrr.
- int len = strlen(colorString);
- if ( (colorString[0] == '#') && (len >= 7) && (len <= 9) )
- {
- // if len == 9, the input may be trying to set the high-order
- // bit. Check for this and punt if so.
- if ( len == 9 )
- {
- char * sHi = "0x??";
- short iHi = 0;
-
- strncat(sHi, colorString, 2);
- sscanf(sHi, "%hx", &iHi);
-
- punt = iHi & 0x40;
- }
-
- if ( ! punt ) // if we're NOT setting hi-order bit...
- {
- unsigned char desiredColorArray[3]; // a COLORREF, as an array
- COLORREF * desiredColorRef = (COLORREF *) desiredColorArray; // the COLORREF
- ASSERT((sizeof(RGBColor) == (3 * sizeof(unsigned short))), "Unexpected size for RGBColor!");
-
- for ( short i = 0, pos = len-2; i < 3; i++, pos -= 2 )
- {
- char * sColor = "0x??";
- short aShort = 0;
- strncpy(&sColor[2], &colorString[pos], 2);
- sscanf(sColor, "%hx", &aShort);
- ASSERT((aShort <= 0xff), "Range check error!");
- desiredColorArray[i] = aShort;
- }
-
- RGBColor desiredRGBColor;
- MapColorRefToRGBColor(desiredColorRef, &desiredRGBColor);
-
- *theColor = desiredRGBColor;
-
- didSet = true;
- }
- }
- }
-
- return didSet;
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::OnNewTextData
- //
-
- void CTickerControl::OnNewTextData(void)
- {
- CTick* pTick; // declared here so as not to confuse the debugger
-
- mFeedDown = FALSE;
-
- // Pull apart the text string into separate ticks.
- // Each tick is separated by a CR(from Mac) or CRLF(from PC).
-
- // Start parsing
- long dataID = 0;
- unsigned long dataLength = strlen(mData) + 1;
- char * pszNamesNValues = (char *) ::CoTaskMemAlloc(dataLength);
- if ( pszNamesNValues == NULL )
- throw CTickerError(NAME_TOKENIZE_ALLOCATION_ERROR);
- strcpy( pszNamesNValues, mData ) ;
- VOLATILE char * p = strtok( pszNamesNValues, TICKTOKENS ) ;
-
- // continue unless the very first token is incomplete (strtok returned NULL)
- if ( p == NULL )
- SaveLeftOvers(p);
- else
- {
- VOLATILE char * reassembledTickData = NULL;
- try
- {
- // If the last data fetch ended in incomplete Tick data, then the first
- // tick we find now is a completion of that data.
- if ( mCurrentLeftOvers != NULL )
- {
- reassembledTickData = (char *) ::CoTaskMemAlloc
- (strlen(mCurrentLeftOvers) + strlen(p) +1);
- ASSERT((reassembledTickData != NULL), "Data allocation failed");
- strcpy(reassembledTickData, mCurrentLeftOvers);
- strcat(reassembledTickData, p);
- p = reassembledTickData;
-
- ::CoTaskMemFree(mCurrentLeftOvers);
- mCurrentLeftOvers = NULL;
- }
-
- // the main tick data parsing loop
- while (p && (mQNew.size() < mMaxCacheSize))
- {
- if ( !(mQNew.size() == 0 && !strncmp(p, "XRT", 3)) ) // ignore a leading "XRT"
- {
- pTick = new CTick(this, dataID++) ;
- pTick->SetName(p);
-
- mQNew.push_back( pTick ) ;
- }
- p = strtok( NULL, TICKTOKENS ) ;
- }
- }
-
- // error handling
- catch ( CTickerError &tickError )
- {
- tickError.HandleError();
- }
-
- // general clean up
- if ( pszNamesNValues )
- ::CoTaskMemFree(pszNamesNValues);
- if ( reassembledTickData != NULL )
- ::CoTaskMemFree(reassembledTickData);
- }
-
- // check for leftover data now that we're done parsing. Note that the leftover
- // data doesn't get built into a CTick because strtok won't find the final
- // TICKTOKENS and will return NULL.
- SaveLeftOvers(mData);
-
- // Now go through each tick and pull apart the
- // name. The name, value, and subsequent
- // values are separated by TAB characters
- deque<CTick*>::iterator it = mQNew.begin();
- while (it != mQNew.end())
- {
- VOLATILE char * pszValues;
- try
- {
- pTick = *it++;
- pszValues = (char *) ::CoTaskMemAlloc(strlen(pTick->GetName())+1);
-
- if ( pszValues == NULL )
- throw CTickerError(VALUE_TOKENIZE_ALLOCATION_ERROR);
- ASSERT((pszValues != NULL), "Data allocation failed");
- strcpy(pszValues, pTick->GetName());
- if ( pszValues )
- {
- p = strtok( pszValues, NAMEVALUETOKENS ) ;
- if (p)
- pTick->SetName(p);
- p = strtok( NULL, NAMEVALUETOKENS ) ;
- while (p)
- {
- pTick->SetValue(p);
- p = strtok( NULL, NAMEVALUETOKENS ) ;
- if (!p)
- pTick->TruncateValue(); // lose the trailing space
- }
- }
- }
-
- // error handling
- catch ( CTickerError &tickerError )
- {
- tickerError.HandleError();
- }
-
- // general cleanup
- if ( pszValues )
- ::CoTaskMemFree(pszValues);
- }
-
- // sanity check. since we set the cache size really big, we're depending
- // on this never happening. The Windows intrinsic fires a cache overflow
- // event here, although I'm no convinced what it does is useful.
- ASSERT((mQNew.size() < mMaxCacheSize), "Cache overflow!");
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::PrepareOffscreenWorld
- //
- // Synopsis: Creates the offscreen gworld that is the current display.
- //
-
- void CTickerControl::PrepareOffscreenWorld(void)
- {
- try
- {
- // get the current drawing environment
- DrawContext Context = {(PortType) 0};
- mContainerSiteP->AcquireContext(mActiveContext->GetContextID(), &Context);
-
- // Define the size of the GWorld's bounding boxes -- same size as onscreen
- short rightBounds = Context.Location.right - Context.Location.left;
- short bottomBounds = Context.Location.bottom - Context.Location.top;
- ::SetRect(&mRectOffscreenBounds, 0, 0, rightBounds, bottomBounds);
-
- // we can reset the drawing environment -- we're done with it
- mContainerSiteP->ReleaseContext(&Context);
-
- // If there's already an old offscreen GWorld, get rid of it.
- if ( mOffscreenGWorld != NULL )
- {
- ::DisposeGWorld( mOffscreenGWorld );
- mOffscreenGWorld = NULL;
- mPixOffscreenPixMap = NULL;
- }
-
- // Allocate a new GWorld for the offscreen drawing and store its PixMap.
- QDErr err = ::NewGWorld( &mOffscreenGWorld, mGWorldPixelDepth, &mRectOffscreenBounds, mGWorldCTable, mGWorldAGDevice, mGWorldFlags );
- if ( !err ) // paramErr because Context.Location == {0,0,0,0}, perhaps?
- {
- mPixOffscreenPixMap = ::GetGWorldPixMap( mOffscreenGWorld );
-
- // we should now have a gworld
- if ( mOffscreenGWorld == NULL || mPixOffscreenPixMap == NULL )
- throw CTickerError(CONTROL_GWORLD_ALLOCATION_ERROR, this);
-
- // Clear the image
- ClearDisplay();
-
- // Note completion
- mOffscreenWorldReady = true;
- }
- }
-
- // error handling
- catch ( CTickerError &tickerError )
- {
- tickerError.HandleError();
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::MoveTicker
- //
- // Synopsis: Draws a portion of the offstage bitmap into the rhs of the
- // display bitmap, scrolling the display bitmap to the left
- //
- // Arguments:
- //
-
- void CTickerControl::MoveTicker( short nMove )
- {
- // don't do anything if nMove == 0;
- if ( nMove == 0 )
- return;
-
- // update the offscreen world if it hasn't been done already, or if something's
- // changed that means it's now invalid.
- if ( ! mOffscreenWorldReady )
- PrepareOffscreenWorld();
-
- // don't do anything more if PrepareOffscreenWorld didn't take
- if ( ! mOffscreenWorldReady )
- return;
-
- // Grab the next tick, if we don't already have one
- if ( mOnstage == NULL )
- mOnstage = GetNextTick();
-
- // if there is no tick ready, don't do anything
- if (mOnstage == NULL)
- return ;
-
- // tell the new tick to Render itself, if it hasn't already
- mOnstage->Render(renderON);
-
- // Scroll display bitmap to left...
-
- // How much are we scrolling by? -- up to nMove.
- short scrollAmount = min(nMove, (short)(mOnstage->HSize() - mOnstage->GetX()));
-
- // sizes, for convenience
- short offscreenHeight = mRectOffscreenBounds.bottom - mRectOffscreenBounds.top;
- short offscreenWidth = mRectOffscreenBounds.right - mRectOffscreenBounds.left;
-
- // the x-coordinate of the chunk that got shifted left
- short cutPoint = mRectOffscreenBounds.left + (offscreenWidth - scrollAmount);
-
- Rect srcRect, destRect; // used for CopyBits operations
-
- // Store the current port and device before switching to the offscreen world.
- // We're switching to the offscreen GWorld here because we need to make sure
- // the foreground and background colors are set to black and white, respectively,
- // before doing any CopyBits operations.
- CGrafPtr currentPort;
- GDHandle currentDevice;
- ::GetGWorld( ¤tPort, ¤tDevice );
-
- // Switch to the offscreen GWorld and lock the offscreen buffer in memory.
- ::SetGWorld( mOffscreenGWorld, nil );
-
- // start by "left-shifting" the offscreen bitmap.
- RgnHandle dummyRegion = ::NewRgn();
- assert(dummyRegion != NULL);
- ::ScrollRect(&mRectOffscreenBounds, -scrollAmount, 0, dummyRegion);
- ::DisposeRgn(dummyRegion);
-
- // Copy the appropriate section of the onstage tick into the
- // right-hand side of the offscreen bitmap
-
- // grow the visible chunk of the onstage tick, remembering the old x coordinate
- short oldx = mOnstage->GetX();
- mOnstage->BumpX(scrollAmount);
-
- // grab the next onstage tick chunk, left of mXLocation
- ::SetRect(&srcRect, oldx, 0, mOnstage->GetX(), mOnstage->VSize());
-
- // erase the rect left vacant by the scrolling
- Rect vacantRect;
- ::SetRect(&vacantRect, (mRectOffscreenBounds.left + cutPoint), mRectOffscreenBounds.top, mRectOffscreenBounds.right, mRectOffscreenBounds.bottom);
- ::RGBBackColor(&mBackColor);
- ::EraseRect(&vacantRect);
-
- // Set the colors so that CopyBits should be happy
- ::RGBForeColor(&RGB_BLACK);
- ::RGBBackColor(&RGB_WHITE);
-
- // center this vertically inside the offscreen bitmap,
- // starting horizontally at the cutPoint
- short topMargin = (offscreenHeight - mOnstage->VSize()) / 2; // half the extra
- short bottomMargin = offscreenHeight - mOnstage->VSize() - topMargin; // what's left over
- ::SetRect(&destRect, (mRectOffscreenBounds.left + cutPoint), (mRectOffscreenBounds.top + topMargin), mRectOffscreenBounds.right, (mRectOffscreenBounds.bottom - bottomMargin));
-
- // lock down the bitmaps and blit
- BitMap * offscreenBitMap = GetOffscreenBitMap(); // lock and load
- BitMap * tickBitMap = mOnstage->GetBitMap(); // lock and load
- ::CopyBits(tickBitMap, offscreenBitMap, &srcRect, &destRect, srcCopy, NULL); // BLIT
- mOnstage->ReleaseBitMap(); // unlock and unload
- ReleaseOffscreenBitMap(offscreenBitMap); // unlock and unload
-
- // sanity check: we should be at or before the end of the tick
- ASSERT((mOnstage->GetX() <= mOnstage->HSize()), "Onstage tick error!");
-
- // Set the port and device back to the original
- ::SetGWorld( currentPort, currentDevice );
-
- // If we've grown the visible chunk of the onstage tick to the end of
- // the tick, then we need the next tick.
- if (mOnstage->GetX() == mOnstage->HSize()) // are we at the end of the tick?
- {
- // sanity check: Windows intrinsic does something in this case,
- // but it shouldn't happen here, since mQCache is the
- // same size as mQNew.
- ASSERT((mQCache.size() < mMaxCacheSize), "Cache Overflow!");
-
- // Put the current onstage tick at end of cache queue, unless we just
- // reloaded and we want to get rid of this one.
- if ( mPurgeOnstage )
- {
- delete mOnstage;
- mOnstage = NULL;
- mPurgeOnstage = false;
- }
- else
- {
- mOnstage->SetX(0);
- mOnstage->SetIsCached(true);
- mOnstage->Render(renderOFF);
- mQCache.push_back(mOnstage);
- mOnstage = NULL;
- }
-
- // sanity check. This shouldn't happen any more, since we only scroll
- // at most the size of the onstage tick.
- ASSERT (!((scrollAmount > 0) && (cutPoint + scrollAmount < offscreenWidth)), "Unexpected scroll state");
- }
-
- // make a note that the offscreen Image is now ready to be moved onscreen.
- mOffscreenImageReady = true;
-
- // Actually move the offscreen world into the onscreen world.
- // Normally we always check mOffscreenImageReady before calling MoveOnScreen,
- // but we just set it above...
- MoveOnScreen();
-
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::GetOffscreenBitMap
- //
- BitMap * CTickerControl::GetOffscreenBitMap(void)
- {
- BitMap * offscreenBitMap = NULL;
-
- // sanity check
- ASSERT((mPixOffscreenPixMap != NULL), "NULL offscreen pixmap!");
-
- // Lock n load the pixMap handle 'till we're done with it
- Boolean lockNLoad = false;
- if ( ::LockPixels(mPixOffscreenPixMap) )
- {
- ::HLock((Handle)mPixOffscreenPixMap);
- lockNLoad = true;
- }
- else // pixmap has been purged? Update the GWorld and try again
- {
- OSErr err = noErr;
- err = ::UpdateGWorld(&mOffscreenGWorld, mGWorldPixelDepth, &mRectOffscreenBounds, mGWorldCTable, mGWorldAGDevice, mGWorldFlags);
- if ( ! err )
- lockNLoad = true;
- }
-
- if ( lockNLoad )
- {
- // Note the bitmap, now that we've locked n loaded
- offscreenBitMap = (BitMap *)(*mPixOffscreenPixMap);
- ASSERT((offscreenBitMap != NULL), "NULL offscreen bitmap!");
- }
-
- return offscreenBitMap;
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::ReleaseOffscreenBitMap
- //
- void CTickerControl::ReleaseOffscreenBitMap(BitMap * offscreenBitMap)
- {
- // Release the pixMap handle
- ::HUnlock((Handle)mPixOffscreenPixMap);
- ::UnlockPixels(mPixOffscreenPixMap);
-
- // Set this to NULL just as a safety factor
- offscreenBitMap = NULL;
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::GetNextTick
- //
- CTick * CTickerControl::GetNextTick(void)
- {
- CTick * nextTick = NULL;
-
- // Grab the next tick off the new que or the cache que, whichever
- // is available
- if (!mQNew.empty())
- {
- nextTick = mQNew.front();
- mQNew.pop_front();
- }
- if (nextTick == NULL && !mQCache.empty())
- {
- nextTick = mQCache.front();
- mQCache.pop_front();
- }
-
- return nextTick;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::ClearDisplay
- //
-
- void CTickerControl::ClearDisplay(void)
- {
- // Store the current port and device before switching to the offscreen world.
- CGrafPtr currentPort;
- GDHandle currentDevice;
- ::GetGWorld( ¤tPort, ¤tDevice );
-
- // Switch to the offscreen GWorld and lock the offscreen buffer in memory.
- ::SetGWorld( mOffscreenGWorld, nil );
-
- // Set the background color before we erase
- ::RGBBackColor(&mBackColor);
-
- // Erase the PixMap's bounding box in the GWorld.
- ::EraseRect( &mRectOffscreenBounds );
-
- // Set the port and device back to the original
- ::SetGWorld( currentPort, currentDevice );
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::MoveOnScreen
- //
- void CTickerControl::MoveOnScreen(DrawContext* Context)
- {
- // lock and load
- BitMap * offscreenBitMap = GetOffscreenBitMap();
- ASSERT(offscreenBitMap != NULL, "NULL ofscreen Bitmap!");
-
- Boolean getContext = (Context == NULL);
- if ( getContext )
- {
- // get the current drawing environment
- DrawContext currentContext = {(PortType) 0};
- Context = ¤tContext;
- mContainerSiteP->AcquireContext(mActiveContext->GetContextID(), Context);
- }
-
- // Set the colors
- ::RGBForeColor(&RGB_BLACK);
- ::RGBBackColor(&RGB_WHITE);
-
- // actually move the bits
- GrafPtr grafPtr = Context->Port;
- BitMap * onscreenBitMap = &(grafPtr->portBits);
- ::CopyBits(offscreenBitMap, onscreenBitMap, &mRectOffscreenBounds, &(Context->Location), srcCopy, NULL);
-
- // unlock and unload
- ReleaseOffscreenBitMap(offscreenBitMap);
-
- if ( getContext )
- {
- // reset the drawing environment
- mContainerSiteP->ReleaseContext(Context);
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::IsXRT
- //
-
- Boolean CTickerControl::IsXRT(FORMATETC* FormatEtc)
- {
- Boolean isXRT = false;
-
- // MMF, TBD: In dr0, it didn't look like the FormatEtc parameter was set to anything but
- // gNullFormatEtc inside the plugin. So I determined the data type by checking the
- // filename extension (if any). Get back to this later...
- const short POSTFIXLENGTH = 4; // .xyz is four characters
- char postFix[POSTFIXLENGTH+1];
- strcpy(postFix, "NONE");
- long urlLength = strlen(mDataURL);
- if ( urlLength > POSTFIXLENGTH )
- {
- strcpy(postFix, &mDataURL[urlLength-POSTFIXLENGTH]);
- for ( short i = 0; i < strlen(postFix); i++ )
- tolower(postFix[i]);
- if ( streq(postFix, ".xrt") ) // it's WOSA/XRT data format
- isXRT = true;
- }
-
- return isXRT;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::SaveLeftOvers (a.k.a. OnTupperWare)
- //
-
- Boolean CTickerControl::SaveLeftOvers(char * theData)
- {
- // this function saves any "leftover" data from theData into
- // the instance variable mCurrentLeftOvers. Leftover data is defined
- // as anything at the tail end of theData that does NOT end in one of
- // TICKTOKENS. The complexity in this function is to handle the case
- // where there's already existing leftover data, in which case the
- // new leftover data is tacked on to what's already there.
-
- Boolean savedSomething = false;
-
- // are there any leftOvers in theData?
- char lastChar = theData[strlen(theData)-1];
- if ( lastChar != CRCHAR && lastChar != CRCHAR ) // assumes that TICKTOKENS == CRCHAR + CRCHAR
- {
- // find the leftovers
- char * pLastCR = strrchr(theData, CRCHAR);
- char * pLastLF = strrchr(theData, LFCHAR);
- char * latestLeftOvers = pLastCR > pLastLF ? pLastCR : pLastLF;
-
- // determine the size of the entire leftovers, including any existing stuff
- // (isn't "stuff" a great word?)
- unsigned long leftOverSize = strlen(latestLeftOvers);
- if ( mCurrentLeftOvers != NULL )
- leftOverSize += strlen(mCurrentLeftOvers);
- leftOverSize += 1; // for NULL termination
-
- // Allocate the (new) leftovers buffer. Move old (if any) and
- // new leftovers in to it.
- char * newLeftOvers = (char *) ::CoTaskMemAlloc(leftOverSize);
- ASSERT((newLeftOvers != NULL), "Data allocation failed");
- if ( mCurrentLeftOvers == NULL )
- strcpy(newLeftOvers, latestLeftOvers);
- else
- {
- strcpy(newLeftOvers, mCurrentLeftOvers);
- strcat(newLeftOvers, latestLeftOvers);
-
- ::CoTaskMemFree(mCurrentLeftOvers);
- mCurrentLeftOvers = NULL;
- }
- mCurrentLeftOvers = newLeftOvers;
-
- // be sure to mark that we've done something
- savedSomething = true;
- }
- else // no leftovers in theData
- {
- if ( mCurrentLeftOvers != NULL )
- {
- // as I understand it, we should never get here, since if there isn't any
- // new leftover data, we should have already processed and cleared any old stuff.
- // ::CoTaskMemFree(mCurrentLeftOvers);
- // mCurrentLeftOvers = NULL;
- ASSERT(NULL, "Unexpected non-null leftovers");
- }
- }
-
- return savedSomething;
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::ReloadTicker
- //
-
- void CTickerControl::ReloadTicker(void)
- {
- // don't do anything if we've never loaded in the first place
- if ( mBound )
- {
- // clear out the old data
- if ( mData != NULL )
- {
- ::CoTaskMemFree(mData);
- mData = NULL;
- }
- if ( mCurrentLeftOvers != NULL )
- {
- ::CoTaskMemFree(mCurrentLeftOvers);
- mCurrentLeftOvers = NULL;
- }
-
- // reset the data counter, and the data validation flag
- mReadTillNow = 0;
- mDataValidation = dataUnconfirmed;
-
- // Clear out both queues
- ClearQueues();
-
- // NOTE: we do NOT clear out the offscreen world. We just let it scroll
- // through and start filling up with new data.
- // get new data.
- BindToData();
-
- // set a flag to get rid of the currently onstage tick when the
- // next one is reloaded. This keeps it from re-entering onto the
- // cache que.
- mPurgeOnstage = true;
- }
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::BindToData
- //
-
- void CTickerControl::BindToData(void)
- {
- LPMONIKER URLMoniker = NULL;
- LPVOID Dummy;
- HRESULT hr;
- VARIANT Var;
-
- mUnkOuterP->QueryInterface(IID_IBindHost, (LPVOID *) &mBindSite);
-
- if(mBindSite)
- {
- mBindSite->CreateMoniker((LPOLESTR)mDataURL, NULL, &URLMoniker, 0);
- if(URLMoniker)
- {
- mBindSite->MonikerBindToStorage(URLMoniker, NULL, this, IID_IStream, &Dummy);
- URLMoniker->Release();
- }
- }
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::ClearQueues
- //
-
- void CTickerControl::ClearQueues(void)
- {
- CTick * front;
- while (mQNew.empty() == false)
- {
- front = mQNew.front();
- mQNew.pop_front();
- delete front;
- }
- while (mQCache.empty() == false)
- {
- front = mQCache.front();
- mQCache.pop_front();
- delete front;
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- // CTickerControl::ContinueRead
- //
-
- Boolean CTickerControl::ContinueRead(void)
- {
- // it's valid data until we've confirmed that the first three characters
- // are NOT "xrt"
-
- if ( mReadTillNow < 3 )
- mDataValidation = dataUnconfirmed; // not enough data to confirm
- else // mReadTillNow >= 3
- {
- if ( mDataValidation == dataUnconfirmed ) // we have not yet confirmed whether or not it's valid data
- {
- char temp[4];
- strncpy(temp, mData, 3);
- temp[3] = 0; // null terminate
- #if 0 // This doesn't work. tolower doesn't change the case. For now, require all uppercase!?
- for (short i = 0; i < 3; i++) // compare case-insensitive
- tolower(temp[i]);
- if ( !strcmp(temp, "xrt") ) // if it's xrt
- #endif
- if ( !strcmp(temp, "XRT") ) // if it's XRT
- mDataValidation = dataValid; // confirmation of valid data
- else
- mDataValidation = dataInvalid; // confirmation of INvalid data
- }
- }
-
- return (mDataValidation != dataInvalid);
- }