gecko-dev/cmd/macfe/central/CIconCache.cp

332 lines
9.3 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
//
// CIconCache.cp
// Mike Pinkerton, Netscape Communications
//
// Contains the implementation of the CImageCache class which is a small cache of
// images which the FE can pull from with a low overhead. It's good for things
// such as using images for icons in the chrome. Note that this is very
// different from the XP image cache.
//
// The filename is old, owing back to the original name for this class, CIconCache
//
// To Do:
// ¥ Flush cache when it gets too full?
//
#include "CIconCache.h"
#include "net.h"
#include "mimages.h"
#include "proto.h"
#include "libevent.h"
//
// gImageCache
//
// Wrapper around our image cache global so we know it gets initialized correctly before anyone
// can call it.
//
CImageCache& gImageCache ( )
{
static CImageCache _gImageCache;
return _gImageCache;
} // gImageCache
#pragma mark -
ImageCacheData :: ImageCacheData ( const string & inURL )
: mImage(NULL), mMask(NULL)
{
mContext = new CIconContext ( inURL );
if ( !mContext )
throw bad_alloc();
}
ImageCacheData :: ~ImageCacheData ( )
{
DestroyFEPixmap ( mImage );
DestroyFEPixmap ( mMask );
delete mContext;
}
//
// CacheThese
//
// Copy whatever FE data (pixmaps, color tables, etc) we need to ensure we can still draw when
// imageLib cleans out the memory cache. Before that, however, be sure to tear down anything
// that already exists.
//
void
ImageCacheData :: CacheThese ( IL_Pixmap* inImage, IL_Pixmap* inMask )
{
// clean up what we allocate beforehand.
DestroyFEPixmap ( mImage );
DestroyFEPixmap ( mMask );
DrawingState state;
StColorPenState::Normalize();
PreparePixmapForDrawing ( inImage, inMask, true, &state );
// we use some trickery here to create the copy FE data structures for the image/mask pixmaps.
// Save the ones that are currently in use then call the callback which initially created them
// to go ahead and create new ones. Store the |client_data| fields internally (they are our
// new copies), and then put the old ones back leaving the image/mask untouched.
NS_PixMap* oldPixmap = mImage = NULL;
NS_PixMap* oldMask = mMask = NULL;
size_t imageWidth = 0, imageHeight = 0;
if ( inImage ) {
oldPixmap = static_cast<NS_PixMap*>(inImage->client_data);
imageWidth = inImage->header.width;
imageHeight = inImage->header.height;
}
if ( inMask )
oldMask = static_cast<NS_PixMap*>(inMask->client_data);
_IMGCB_NewPixmap ( NULL, 0, *IconContext(), imageWidth, imageHeight, inImage, inMask );
if ( inImage ) {
mImage = static_cast<NS_PixMap*>(inImage->client_data);
size_t imageSize = inImage->header.widthBytes * inImage->header.height;
::BlockMoveData ( oldPixmap->image_buffer, mImage->image_buffer, imageSize );
inImage->client_data = oldPixmap;
}
if ( inMask ) {
mMask = static_cast<NS_PixMap*>(inMask->client_data);
size_t maskSize = inMask->header.widthBytes * inMask->header.height;
::BlockMoveData ( oldMask->image_buffer, mMask->image_buffer, maskSize );
inMask->client_data = oldMask;
}
DoneDrawingPixmap ( inImage, inMask, &state );
} // CacheThese
#pragma mark -
CImageCache :: CImageCache ( )
{
}
CImageCache :: ~CImageCache ( )
{
//¥ need to dispose of all the images....but since this is only called at shutdown....
}
//
// RequestIcon
// THROWS bad_alloc
//
// Make a request for an icon. Will start loading if not present, and the return result
// will be |kPutOnWaitingList|. If the data is already there in the cache, |kDataPresent|
// is returned. If the url being asked for is the empty string (""), return |kEmptyURL|.
//
CImageCache::ELoadResult
CImageCache :: RequestIcon ( const string & inURL, const LListener* inClient )
{
ELoadResult result;
// first check to make sure URL is not ""
if ( ! inURL.length() )
return kEmptyURL;
ImageCacheData* data = mCache[inURL];
if ( data ) {
if ( !data->ImageAvailable() && data->IconContext() ) {
// We're still loading. The cache is already on the notification list, so just
// add the caller.
data->AddListener ( const_cast<LListener*>(inClient) );
result = kPutOnWaitingList;
}
else
result = kDataPresent;
}
else {
// not in cache yet, need to start loading this icon. Add the caller as a
// listener to the data class and add us a listener to the context.
data = new ImageCacheData(inURL);
if ( !data )
throw bad_alloc();
URL_Struct* urlStruct = NET_CreateURLStruct (inURL.c_str(), NET_DONT_RELOAD);
if ( !urlStruct )
throw bad_alloc();
mCache[inURL] = data;
data->IconContext()->AddListener ( this );
data->AddListener ( const_cast<LListener*>(inClient) );
data->IconContext()->SwitchLoadURL ( urlStruct, FO_PRESENT );
result = kPutOnWaitingList;
}
return result;
} // RequestIcon
//
// FetchImageData
// THROWS invalid_argument
//
// For images that are already loaded (RequestIcon() returned |kDataPresent|, get the data.
//
void
CImageCache :: FetchImageData ( const string & inURL, NS_PixMap** outImage, NS_PixMap** outMask )
{
ImageCacheData* data = mCache[inURL];
if ( data ) {
*outImage = data->Image();
*outMask = data->Mask();
}
else
throw invalid_argument(inURL);
} // FetchImageData
//
// DeferredDelete
//
// A mocha callback, called when mocha is done with the given context. We use this below to
// defer the deletion of an icon context. When we get here, we know imageLib is done with it
// so it is safe to delete it.
//
static void DeferredDelete ( CNSContext * context )
{
context->NoMoreUsers();
} // DeferredDelete
//
// ContextFinished
//
// Housecleaning. When a static image is finished, the context should go away
// but the data should remain.
//
void
CImageCache :: ContextFinished ( const MWContext* inContext )
{
// find the context in the cache (we don't have the url). Remember the cache
// contains CIconContext's and we are given an MWContext. Easy enough, though,
// because the MWContext contains a pointer back to the FE class in |fe.newContext|
typedef map<string, ImageCacheData*>::const_iterator CI;
CI it = mCache.begin();
for ( ; it != mCache.end(); ++it ) {
CIconContext* currContext = (*it).second->IconContext();
if ( currContext == inContext->fe.newContext )
break;
}
// Assert_(it != mCache.end());
// null out the icon context stored w/in it. Don't delete it because it is
// still being used by imageLib???
if ( it != mCache.end() ) {
(*it).second->IconContext()->RemoveAllListeners();
// We cannot delete the context now, because imageLib is still in the middle of
// using it. To get around this, we tell mocha to call us back when it is done with the
// context, which should be about the time of the next event loop. By then, all is well
// and the DeferredDelete() routine above will delete the context correctly.
XP_RemoveContextFromList ( *(*it).second->IconContext() );
ET_RemoveWindowContext( *(*it).second->IconContext(), (ETVoidPtrFunc) DeferredDelete, (*it).second->IconContext() );
(*it).second->IconContextHasGoneAway();
}
}
//
// ListenerGoingAway
//
// a view might be going away before an image is finished loading. If that is
// the case, it needs to be removed from the context's listener list so we don't
// go telling dead objects their image has arrived.
//
void
CImageCache :: ListenerGoingAway ( const string & inURL, LListener* inObjectGoingAway )
{
ImageCacheData* data = mCache[inURL];
if ( data && data->IconContext() )
data->IconContext()->RemoveListener ( inObjectGoingAway );
} // ListenerGoingAway
//
// Flush
//
// clean out the cache when it gets too big.
//
void
CImageCache :: Flush()
{
}
//
// ListenToMessage
//
// Listen to messages from the icon context so we can display the image when imageLib gives us
// something to display. We have to make sure that we deep copy the FE portions of the image
// and mask so that when imagelib cleans out the memory cache, we still have the data we need
// to draw things.
//
void
CImageCache :: ListenToMessage ( MessageT inMessage, void* inData )
{
switch ( inMessage ) {
case CIconContext::msg_ImageReadyToDraw:
// NOTE: don't dispose |data| because it is on stack
IconImageData* data = static_cast<IconImageData*>(inData);
Assert_(data != NULL);
if ( data ) {
try {
ImageCacheData* iconData = mCache[data->url];
if ( iconData ) {
// remember whatever we need and tell clients to draw
iconData->CacheThese ( data->image, data->mask );
iconData->BroadcastMessage ( CIconContext::msg_ImageReadyToDraw, NULL );
}
}
catch ( bad_alloc & ) {
//¥¥¥ couldn't allocate memory for item in cache, so don't cache it. Do we
//¥¥¥ need to tell anyone about this (unregister listeners, perhaps?)
}
}
break;
}
} // ListenToMessage